diff options
Diffstat (limited to 'src/backend/backup/basebackup_target.c')
-rw-r--r-- | src/backend/backup/basebackup_target.c | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/src/backend/backup/basebackup_target.c b/src/backend/backup/basebackup_target.c new file mode 100644 index 0000000..4d15ca4 --- /dev/null +++ b/src/backend/backup/basebackup_target.c @@ -0,0 +1,241 @@ +/*------------------------------------------------------------------------- + * + * 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; +} |