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