242 lines
6.6 KiB
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-2024, 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;
|
|
}
|