1/*-------------------------------------------------------------------------
3 * basebackup_to_shell.c
4 * target base backup files to a shell command
6 * Copyright (c) 2016-2025, PostgreSQL Global Development Group
8 * contrib/basebackup_to_shell/basebackup_to_shell.c
9 *-------------------------------------------------------------------------
22 .
name =
"basebackup_to_shell",
28 /* Common information for all types of sink. */
31 /* User-supplied target detail string. */
34 /* Shell command pattern being used for this backup. */
37 /* The command that is currently running. */
40 /* Pipe to the running command. */
48 const char *archive_name);
74 "Shell command to be executed for each backup file.",
83 "Backup user must be a member of this role to use shell backup target.",
97 * We choose to defer sanity checking until shell_get_sink(), and so
98 * just pass the target detail through without doing anything. However, we do
99 * permissions checks here, before any real work has been done.
112 (
errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
113 errmsg(
"permission denied to use basebackup_to_shell")));
117 return target_detail;
121 * Set up a bbsink to implement this base backup target.
123 * This is also a convenient place to sanity check that a target detail was
124 * given if and only if %d is present.
130 bool has_detail_escape =
false;
136 * We remember the current value of basebackup_to_shell.shell_command to
137 * be certain that it can't change under us during the backup.
145 /* Reject an empty shell command. */
148 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
149 errmsg(
"shell command for backup is not configured"));
151 /* Determine whether the shell command we're using contains %d. */
154 if (
c[0] ==
'%' &&
c[1] !=
'0円')
157 has_detail_escape =
true;
162 /* There should be a target detail if %d was used, and not otherwise. */
165 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
166 errmsg(
"a target detail is required because the configured command includes %%d"),
167 errhint(
"Try \"pg_basebackup --target shell:DETAIL ...\"")));
170 (
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
171 errmsg(
"a target detail is not permitted because the configured command does not include %%d")));
174 * Since we're passing the string provided by the user to popen(), it will
175 * be interpreted by the shell, which is a potential security
176 * vulnerability, since the user invoking this module is not necessarily a
177 * superuser. To stay out of trouble, we must disallow any shell
178 * metacharacters here; to be conservative and keep things simple, we
179 * allow only alphanumerics.
188 if (*d >=
'a' && *d <=
'z')
190 if (*d >=
'A' && *d <=
'Z')
192 if (*d >=
'0' && *d <=
'9')
200 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
201 errmsg(
"target detail must contain only alphanumeric characters"));
208 * Construct the exact shell command that we're actually going to run,
209 * making substitutions as appropriate for escape sequences.
213 const char *target_detail)
220 * Finish executing the shell command once all data has been written.
227 /* There should be a command running. */
231 /* Close down the pipe we opened. */
236 errmsg(
"could not close pipe to external command: %m")));
237 else if (pclose_rc != 0)
240 (
errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
241 errmsg(
"shell command \"%s\" failed",
253 * Start up the shell command, substituting %f in for the current filename.
258 /* There should not be anything already running. */
262 /* Construct a suitable command. */
269 if (sink->
pipe == NULL)
272 errmsg(
"could not execute command \"%s\": %m",
277 * Send accumulated data to the running shell command.
282 /* There should be a command running. */
286 /* Try to write the data. */
293 * The error we're about to throw would shut down the command
294 * anyway, but we may get a more meaningful error message by doing
295 * this. If not, we'll fall through to the generic error below.
302 errmsg(
"could not write to shell backup program: %m")));
307 * At start of archive, start up the shell command and forward to next sink.
319 * Send archive contents to command's stdin and forward to next sink.
331 * At end of archive, shut down the shell command and forward to next sink.
343 * At start of manifest, start up the shell command and forward to next sink.
355 * Send manifest contents to command's stdin and forward to next sink.
367 * At end of manifest, shut down the shell command and forward to next sink.
bool has_privs_of_role(Oid member, Oid role)
Oid get_role_oid(const char *rolname, bool missing_ok)
void bbsink_forward_begin_backup(bbsink *sink)
void bbsink_forward_begin_manifest(bbsink *sink)
void bbsink_forward_end_backup(bbsink *sink, XLogRecPtr endptr, TimeLineID endtli)
void bbsink_forward_cleanup(bbsink *sink)
void bbsink_forward_manifest_contents(bbsink *sink, size_t len)
void bbsink_forward_end_archive(bbsink *sink)
void bbsink_forward_archive_contents(bbsink *sink, size_t len)
void bbsink_forward_begin_archive(bbsink *sink, const char *archive_name)
void bbsink_forward_end_manifest(bbsink *sink)
void BaseBackupAddTarget(char *name, void *(*check_detail)(char *, char *), bbsink *(*get_sink)(bbsink *, void *))
static void * shell_check_detail(char *target, char *target_detail)
static char * shell_required_role
static void shell_finish_command(bbsink_shell *sink)
static void bbsink_shell_begin_manifest(bbsink *sink)
static bbsink * shell_get_sink(bbsink *next_sink, void *detail_arg)
static void bbsink_shell_end_manifest(bbsink *sink)
static char * shell_construct_command(const char *base_command, const char *filename, const char *target_detail)
static void bbsink_shell_end_archive(bbsink *sink)
static void shell_send_data(bbsink_shell *sink, size_t len)
struct bbsink_shell bbsink_shell
PG_MODULE_MAGIC_EXT(.name="basebackup_to_shell",.version=PG_VERSION)
static void shell_run_command(bbsink_shell *sink, const char *filename)
static void bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
static void bbsink_shell_archive_contents(bbsink *sink, size_t len)
static char * shell_command
static const bbsink_ops bbsink_shell_ops
static void bbsink_shell_manifest_contents(bbsink *sink, size_t len)
int errdetail_internal(const char *fmt,...)
int errcode_for_file_access(void)
int errhint(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
FILE * OpenPipeStream(const char *command, const char *mode)
int ClosePipeStream(FILE *file)
void DefineCustomStringVariable(const char *name, const char *short_desc, const char *long_desc, char **valueAddr, const char *bootValue, GucContext context, int flags, GucStringCheckHook check_hook, GucStringAssignHook assign_hook, GucShowHook show_hook)
void MarkGUCPrefixReserved(const char *className)
Assert(PointerIsAligned(start, uint64))
char * pstrdup(const char *in)
void pfree(void *pointer)
void * palloc0(Size size)
char * replace_percent_placeholders(const char *instr, const char *param_name, const char *letters,...)
void(* begin_backup)(bbsink *sink)
const bbsink_ops * bbs_ops
char * wait_result_to_str(int exitstatus)
void StartTransactionCommand(void)
void CommitTransactionCommand(void)