postgresql/src/backend/backup/basebackup_target.c

242 lines
6.6 KiB
C

/*-------------------------------------------------------------------------
*
* basebackup_target.c
* Base backups can be "targeted", which means that they can be sent
* somewhere other than to the client which requested the backup.
* Furthermore, new targets can be defined by extensions. This file
* contains code to support that functionality.
*
* Portions Copyright (c) 2010-2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/backup/basebackup_target.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "backup/basebackup_target.h"
#include "utils/memutils.h"
typedef struct BaseBackupTargetType
{
char *name;
void *(*check_detail) (char *, char *);
bbsink *(*get_sink) (bbsink *, void *);
} BaseBackupTargetType;
struct BaseBackupTargetHandle
{
BaseBackupTargetType *type;
void *detail_arg;
};
static void initialize_target_list(void);
static bbsink *blackhole_get_sink(bbsink *next_sink, void *detail_arg);
static bbsink *server_get_sink(bbsink *next_sink, void *detail_arg);
static void *reject_target_detail(char *target, char *target_detail);
static void *server_check_detail(char *target, char *target_detail);
static BaseBackupTargetType builtin_backup_targets[] =
{
{
"blackhole", reject_target_detail, blackhole_get_sink
},
{
"server", server_check_detail, server_get_sink
},
{
NULL
}
};
static List *BaseBackupTargetTypeList = NIL;
/*
* Add a new base backup target type.
*
* This is intended for use by server extensions.
*/
void
BaseBackupAddTarget(char *name,
void *(*check_detail) (char *, char *),
bbsink *(*get_sink) (bbsink *, void *))
{
BaseBackupTargetType *newtype;
MemoryContext oldcontext;
ListCell *lc;
/* If the target list is not yet initialized, do that first. */
if (BaseBackupTargetTypeList == NIL)
initialize_target_list();
/* Search the target type list for an existing entry with this name. */
foreach(lc, BaseBackupTargetTypeList)
{
BaseBackupTargetType *ttype = lfirst(lc);
if (strcmp(ttype->name, name) == 0)
{
/*
* We found one, so update it.
*
* It is probably not a great idea to call BaseBackupAddTarget for
* the same name multiple times, but if it happens, this seems
* like the sanest behavior.
*/
ttype->check_detail = check_detail;
ttype->get_sink = get_sink;
return;
}
}
/*
* We use TopMemoryContext for allocations here to make sure that the data
* we need doesn't vanish under us; that's also why we copy the target
* name into a newly-allocated chunk of memory.
*/
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
newtype = palloc(sizeof(BaseBackupTargetType));
newtype->name = pstrdup(name);
newtype->check_detail = check_detail;
newtype->get_sink = get_sink;
BaseBackupTargetTypeList = lappend(BaseBackupTargetTypeList, newtype);
MemoryContextSwitchTo(oldcontext);
}
/*
* Look up a base backup target and validate the target_detail.
*
* Extensions that define new backup targets will probably define a new
* type of bbsink to match. Validation of the target_detail can be performed
* either in the check_detail routine called here, or in the bbsink
* constructor, which will be called from BaseBackupGetSink. It's mostly
* a matter of taste, but the check_detail function runs somewhat earlier.
*/
BaseBackupTargetHandle *
BaseBackupGetTargetHandle(char *target, char *target_detail)
{
ListCell *lc;
/* If the target list is not yet initialized, do that first. */
if (BaseBackupTargetTypeList == NIL)
initialize_target_list();
/* Search the target type list for a match. */
foreach(lc, BaseBackupTargetTypeList)
{
BaseBackupTargetType *ttype = lfirst(lc);
if (strcmp(ttype->name, target) == 0)
{
BaseBackupTargetHandle *handle;
/* Found the target. */
handle = palloc(sizeof(BaseBackupTargetHandle));
handle->type = ttype;
handle->detail_arg = ttype->check_detail(target, target_detail);
return handle;
}
}
/* Did not find the target. */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unrecognized target: \"%s\"", target)));
/* keep compiler quiet */
return NULL;
}
/*
* Construct a bbsink that will implement the backup target.
*
* The get_sink function does all the real work, so all we have to do here
* is call it with the correct arguments. Whatever the check_detail function
* returned is here passed through to the get_sink function. This lets those
* two functions communicate with each other, if they wish. If not, the
* check_detail function can simply return the target_detail and let the
* get_sink function take it from there.
*/
bbsink *
BaseBackupGetSink(BaseBackupTargetHandle *handle, bbsink *next_sink)
{
return handle->type->get_sink(next_sink, handle->detail_arg);
}
/*
* Load predefined target types into BaseBackupTargetTypeList.
*/
static void
initialize_target_list(void)
{
BaseBackupTargetType *ttype = builtin_backup_targets;
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
while (ttype->name != NULL)
{
BaseBackupTargetTypeList = lappend(BaseBackupTargetTypeList, ttype);
++ttype;
}
MemoryContextSwitchTo(oldcontext);
}
/*
* Normally, a get_sink function should construct and return a new bbsink that
* implements the backup target, but the 'blackhole' target just throws the
* data away. We could implement that by adding a bbsink that does nothing
* but forward, but it's even cheaper to implement that by not adding a bbsink
* at all.
*/
static bbsink *
blackhole_get_sink(bbsink *next_sink, void *detail_arg)
{
return next_sink;
}
/*
* Create a bbsink implementing a server-side backup.
*/
static bbsink *
server_get_sink(bbsink *next_sink, void *detail_arg)
{
return bbsink_server_new(next_sink, detail_arg);
}
/*
* Implement target-detail checking for a target that does not accept a
* detail.
*/
static void *
reject_target_detail(char *target, char *target_detail)
{
if (target_detail != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("target \"%s\" does not accept a target detail",
target)));
return NULL;
}
/*
* Implement target-detail checking for a server-side backup.
*
* target_detail should be the name of the directory to which the backup
* should be written, but we don't check that here. Rather, that check,
* as well as the necessary permissions checking, happens in bbsink_server_new.
*/
static void *
server_check_detail(char *target, char *target_detail)
{
if (target_detail == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("target \"%s\" requires a target detail",
target)));
return target_detail;
}