/*------------------------------------------------------------------------- * * 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; }