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 *-------------------------------------------------------------------------
13 #include "access/xact.h"
14 #include "backup/basebackup_target.h"
15 #include "common/percentrepl.h"
16 #include "miscadmin.h"
17 #include "storage/fd.h"
18 #include "utils/acl.h"
19 #include "utils/guc.h"
23 typedef struct bbsink_shell
25 /* Common information for all types of sink. */
28 /* User-supplied target detail string. */
31 /* Shell command pattern being used for this backup. */
34 /* The command that is currently running. */
35 char *current_command
;
37 /* Pipe to the running command. */
41 static void *shell_check_detail(char *target
, char *target_detail
);
42 static bbsink
*shell_get_sink(bbsink
*next_sink
, void *detail_arg
);
44 static void bbsink_shell_begin_archive(bbsink
*sink
,
45 const char *archive_name
);
46 static void bbsink_shell_archive_contents(bbsink
*sink
, size_t len
);
47 static void bbsink_shell_end_archive(bbsink
*sink
);
48 static void bbsink_shell_begin_manifest(bbsink
*sink
);
49 static void bbsink_shell_manifest_contents(bbsink
*sink
, size_t len
);
50 static void bbsink_shell_end_manifest(bbsink
*sink
);
52 static const bbsink_ops bbsink_shell_ops
= {
53 .begin_backup
= bbsink_forward_begin_backup
,
54 .begin_archive
= bbsink_shell_begin_archive
,
55 .archive_contents
= bbsink_shell_archive_contents
,
56 .end_archive
= bbsink_shell_end_archive
,
57 .begin_manifest
= bbsink_shell_begin_manifest
,
58 .manifest_contents
= bbsink_shell_manifest_contents
,
59 .end_manifest
= bbsink_shell_end_manifest
,
60 .end_backup
= bbsink_forward_end_backup
,
61 .cleanup
= bbsink_forward_cleanup
64 static char *shell_command
= "";
65 static char *shell_required_role
= "";
70 DefineCustomStringVariable("basebackup_to_shell.command",
71 "Shell command to be executed for each backup file.",
79 DefineCustomStringVariable("basebackup_to_shell.required_role",
80 "Backup user must be a member of this role to use shell backup target.",
88 MarkGUCPrefixReserved("basebackup_to_shell");
90 BaseBackupAddTarget("shell", shell_check_detail
, shell_get_sink
);
94 * We choose to defer sanity checking until shell_get_sink(), and so
95 * just pass the target detail through without doing anything. However, we do
96 * permissions checks here, before any real work has been done.
99 shell_check_detail(char *target
, char *target_detail
)
101 if (shell_required_role
[0] != '\0')
105 StartTransactionCommand();
106 roleid
= get_role_oid(shell_required_role
, true);
107 if (!has_privs_of_role(GetUserId(), roleid
))
109 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
110 errmsg("permission denied to use basebackup_to_shell")));
111 CommitTransactionCommand();
114 return target_detail
;
118 * Set up a bbsink to implement this base backup target.
120 * This is also a convenient place to sanity check that a target detail was
121 * given if and only if %d is present.
124 shell_get_sink(bbsink
*next_sink
, void *detail_arg
)
127 bool has_detail_escape
= false;
133 * We remember the current value of basebackup_to_shell.shell_command to
134 * be certain that it can't change under us during the backup.
136 sink
= palloc0(sizeof(bbsink_shell
));
137 *((const bbsink_ops
**) &sink
->base
.bbs_ops
) = &bbsink_shell_ops
;
138 sink
->base
.bbs_next
= next_sink
;
139 sink
->target_detail
= detail_arg
;
140 sink
->shell_command
= pstrdup(shell_command
);
142 /* Reject an empty shell command. */
143 if (sink
->shell_command
[0] == '\0')
145 errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
146 errmsg("shell command for backup is not configured"));
148 /* Determine whether the shell command we're using contains %d. */
149 for (c
= sink
->shell_command
; *c
!= '\0'; ++c
)
151 if (c
[0] == '%' && c
[1] != '\0')
154 has_detail_escape
= true;
159 /* There should be a target detail if %d was used, and not otherwise. */
160 if (has_detail_escape
&& sink
->target_detail
== NULL
)
162 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
163 errmsg("a target detail is required because the configured command includes %%d"),
164 errhint("Try \"pg_basebackup --target shell:DETAIL ...\"")));
165 else if (!has_detail_escape
&& sink
->target_detail
!= NULL
)
167 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
168 errmsg("a target detail is not permitted because the configured command does not include %%d")));
171 * Since we're passing the string provided by the user to popen(), it will
172 * be interpreted by the shell, which is a potential security
173 * vulnerability, since the user invoking this module is not necessarily a
174 * superuser. To stay out of trouble, we must disallow any shell
175 * metacharacters here; to be conservative and keep things simple, we
176 * allow only alphanumerics.
178 if (sink
->target_detail
!= NULL
)
183 for (d
= sink
->target_detail
; *d
!= '\0'; ++d
)
185 if (*d
>= 'a' && *d
<= 'z')
187 if (*d
>= 'A' && *d
<= 'Z')
189 if (*d
>= '0' && *d
<= '9')
197 errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
198 errmsg("target detail must contain only alphanumeric characters"));
205 * Construct the exact shell command that we're actually going to run,
206 * making substitutions as appropriate for escape sequences.
209 shell_construct_command(const char *base_command
, const char *filename
,
210 const char *target_detail
)
212 return replace_percent_placeholders(base_command
, "basebackup_to_shell.command",
213 "df", target_detail
, filename
);
217 * Finish executing the shell command once all data has been written.
220 shell_finish_command(bbsink_shell
*sink
)
224 /* There should be a command running. */
225 Assert(sink
->current_command
!= NULL
);
226 Assert(sink
->pipe
!= NULL
);
228 /* Close down the pipe we opened. */
229 pclose_rc
= ClosePipeStream(sink
->pipe
);
232 (errcode_for_file_access(),
233 errmsg("could not close pipe to external command: %m")));
234 else if (pclose_rc
!= 0)
237 (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION
),
238 errmsg("shell command \"%s\" failed",
239 sink
->current_command
),
240 errdetail_internal("%s", wait_result_to_str(pclose_rc
))));
245 pfree(sink
->current_command
);
246 sink
->current_command
= NULL
;
250 * Start up the shell command, substituting %f in for the current filename.
253 shell_run_command(bbsink_shell
*sink
, const char *filename
)
255 /* There should not be anything already running. */
256 Assert(sink
->current_command
== NULL
);
257 Assert(sink
->pipe
== NULL
);
259 /* Construct a suitable command. */
260 sink
->current_command
= shell_construct_command(sink
->shell_command
,
262 sink
->target_detail
);
265 sink
->pipe
= OpenPipeStream(sink
->current_command
, PG_BINARY_W
);
266 if (sink
->pipe
== NULL
)
268 (errcode_for_file_access(),
269 errmsg("could not execute command \"%s\": %m",
270 sink
->current_command
)));
274 * Send accumulated data to the running shell command.
277 shell_send_data(bbsink_shell
*sink
, size_t len
)
279 /* There should be a command running. */
280 Assert(sink
->current_command
!= NULL
);
281 Assert(sink
->pipe
!= NULL
);
283 /* Try to write the data. */
284 if (fwrite(sink
->base
.bbs_buffer
, len
, 1, sink
->pipe
) != 1 ||
290 * The error we're about to throw would shut down the command
291 * anyway, but we may get a more meaningful error message by doing
292 * this. If not, we'll fall through to the generic error below.
294 shell_finish_command(sink
);
298 (errcode_for_file_access(),
299 errmsg("could not write to shell backup program: %m")));
304 * At start of archive, start up the shell command and forward to next sink.
307 bbsink_shell_begin_archive(bbsink
*sink
, const char *archive_name
)
309 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
311 shell_run_command(mysink
, archive_name
);
312 bbsink_forward_begin_archive(sink
, archive_name
);
316 * Send archive contents to command's stdin and forward to next sink.
319 bbsink_shell_archive_contents(bbsink
*sink
, size_t len
)
321 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
323 shell_send_data(mysink
, len
);
324 bbsink_forward_archive_contents(sink
, len
);
328 * At end of archive, shut down the shell command and forward to next sink.
331 bbsink_shell_end_archive(bbsink
*sink
)
333 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
335 shell_finish_command(mysink
);
336 bbsink_forward_end_archive(sink
);
340 * At start of manifest, start up the shell command and forward to next sink.
343 bbsink_shell_begin_manifest(bbsink
*sink
)
345 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
347 shell_run_command(mysink
, "backup_manifest");
348 bbsink_forward_begin_manifest(sink
);
352 * Send manifest contents to command's stdin and forward to next sink.
355 bbsink_shell_manifest_contents(bbsink
*sink
, size_t len
)
357 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
359 shell_send_data(mysink
, len
);
360 bbsink_forward_manifest_contents(sink
, len
);
364 * At end of manifest, shut down the shell command and forward to next sink.
367 bbsink_shell_end_manifest(bbsink
*sink
)
369 bbsink_shell
*mysink
= (bbsink_shell
*) sink
;
371 shell_finish_command(mysink
);
372 bbsink_forward_end_manifest(sink
);