PostgreSQL Source Code  git master
basebackup_to_shell.c
Go to the documentation of this file.
1 /*-------------------------------------------------------------------------
2  *
3  * basebackup_to_shell.c
4  * target base backup files to a shell command
5  *
6  * Copyright (c) 2016-2022, PostgreSQL Global Development Group
7  *
8  * contrib/basebackup_to_shell/basebackup_to_shell.c
9  *-------------------------------------------------------------------------
10  */
11 #include "postgres.h"
12 
13 #include "access/xact.h"
14 #include "miscadmin.h"
16 #include "storage/fd.h"
17 #include "utils/acl.h"
18 #include "utils/guc.h"
19 
21 
22 typedef struct bbsink_shell
23 {
24  /* Common information for all types of sink. */
26 
27  /* User-supplied target detail string. */
29 
30  /* Shell command pattern being used for this backup. */
32 
33  /* The command that is currently running. */
35 
36  /* Pipe to the running command. */
37  FILE *pipe;
39 
40 void _PG_init(void);
41 
42 static void *shell_check_detail(char *target, char *target_detail);
43 static bbsink *shell_get_sink(bbsink *next_sink, void *detail_arg);
44 
45 static void bbsink_shell_begin_archive(bbsink *sink,
46  const char *archive_name);
47 static void bbsink_shell_archive_contents(bbsink *sink, size_t len);
48 static void bbsink_shell_end_archive(bbsink *sink);
49 static void bbsink_shell_begin_manifest(bbsink *sink);
50 static void bbsink_shell_manifest_contents(bbsink *sink, size_t len);
51 static void bbsink_shell_end_manifest(bbsink *sink);
52 
53 static const bbsink_ops bbsink_shell_ops = {
55  .begin_archive = bbsink_shell_begin_archive,
56  .archive_contents = bbsink_shell_archive_contents,
57  .end_archive = bbsink_shell_end_archive,
58  .begin_manifest = bbsink_shell_begin_manifest,
59  .manifest_contents = bbsink_shell_manifest_contents,
60  .end_manifest = bbsink_shell_end_manifest,
61  .end_backup = bbsink_forward_end_backup,
62  .cleanup = bbsink_forward_cleanup
63 };
64 
65 static char *shell_command = "";
66 static char *shell_required_role = "";
67 
68 void
69 _PG_init(void)
70 {
71  DefineCustomStringVariable("basebackup_to_shell.command",
72  "Shell command to be executed for each backup file.",
73  NULL,
75  "",
76  PGC_SIGHUP,
77  0,
78  NULL, NULL, NULL);
79 
80  DefineCustomStringVariable("basebackup_to_shell.required_role",
81  "Backup user must be a member of this role to use shell backup target.",
82  NULL,
84  "",
85  PGC_SIGHUP,
86  0,
87  NULL, NULL, NULL);
88 
89  MarkGUCPrefixReserved("basebackup_to_shell");
90 
92 }
93 
94 /*
95  * We choose to defer sanity checking until shell_get_sink(), and so
96  * just pass the target detail through without doing anything. However, we do
97  * permissions checks here, before any real work has been done.
98  */
99 static void *
100 shell_check_detail(char *target, char *target_detail)
101 {
102  if (shell_required_role[0] != '\0')
103  {
104  Oid roleid;
105 
107  roleid = get_role_oid(shell_required_role, true);
108  if (!has_privs_of_role(GetUserId(), roleid))
109  ereport(ERROR,
110  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
111  errmsg("permission denied to use basebackup_to_shell")));
113  }
114 
115  return target_detail;
116 }
117 
118 /*
119  * Set up a bbsink to implement this base backup target.
120  *
121  * This is also a convenient place to sanity check that a target detail was
122  * given if and only if %d is present.
123  */
124 static bbsink *
125 shell_get_sink(bbsink *next_sink, void *detail_arg)
126 {
127  bbsink_shell *sink;
128  bool has_detail_escape = false;
129  char *c;
130 
131  /*
132  * Set up the bbsink.
133  *
134  * We remember the current value of basebackup_to_shell.shell_command to
135  * be certain that it can't change under us during the backup.
136  */
137  sink = palloc0(sizeof(bbsink_shell));
138  *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_shell_ops;
139  sink->base.bbs_next = next_sink;
140  sink->target_detail = detail_arg;
142 
143  /* Reject an empty shell command. */
144  if (sink->shell_command[0] == '\0')
145  ereport(ERROR,
146  errcode(ERRCODE_INVALID_PARAMETER_VALUE),
147  errmsg("shell command for backup is not configured"));
148 
149  /* Determine whether the shell command we're using contains %d. */
150  for (c = sink->shell_command; *c != '\0'; ++c)
151  {
152  if (c[0] == '%' && c[1] != '\0')
153  {
154  if (c[1] == 'd')
155  has_detail_escape = true;
156  ++c;
157  }
158  }
159 
160  /* There should be a target detail if %d was used, and not otherwise. */
161  if (has_detail_escape && sink->target_detail == NULL)
162  ereport(ERROR,
163  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
164  errmsg("a target detail is required because the configured command includes %%d"),
165  errhint("Try \"pg_basebackup --target shell:DETAIL ...\"")));
166  else if (!has_detail_escape && sink->target_detail != NULL)
167  ereport(ERROR,
168  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
169  errmsg("a target detail is not permitted because the configured command does not include %%d")));
170 
171  /*
172  * Since we're passing the string provided by the user to popen(), it will
173  * be interpreted by the shell, which is a potential security
174  * vulnerability, since the user invoking this module is not necessarily a
175  * superuser. To stay out of trouble, we must disallow any shell
176  * metacharacters here; to be conservative and keep things simple, we
177  * allow only alphanumerics.
178  */
179  if (sink->target_detail != NULL)
180  {
181  char *d;
182  bool scary = false;
183 
184  for (d = sink->target_detail; *d != '\0'; ++d)
185  {
186  if (*d >= 'a' && *d <= 'z')
187  continue;
188  if (*d >= 'A' && *d <= 'Z')
189  continue;
190  if (*d >= '0' && *d <= '9')
191  continue;
192  scary = true;
193  break;
194  }
195 
196  if (scary)
197  ereport(ERROR,
198  errcode(ERRCODE_INVALID_PARAMETER_VALUE),
199  errmsg("target detail must contain only alphanumeric characters"));
200  }
201 
202  return &sink->base;
203 }
204 
205 /*
206  * Construct the exact shell command that we're actually going to run,
207  * making substitutions as appropriate for escape sequences.
208  */
209 static char *
210 shell_construct_command(char *base_command, const char *filename,
211  char *target_detail)
212 {
214  char *c;
215 
217  for (c = base_command; *c != '\0'; ++c)
218  {
219  /* Anything other than '%' is copied verbatim. */
220  if (*c != '%')
221  {
223  continue;
224  }
225 
226  /* Any time we see '%' we eat the following character as well. */
227  ++c;
228 
229  /*
230  * The following character determines what we insert here, or may
231  * cause us to throw an error.
232  */
233  if (*c == '%')
234  {
235  /* '%%' is replaced by a single '%' */
236  appendStringInfoChar(&buf, '%');
237  }
238  else if (*c == 'f')
239  {
240  /* '%f' is replaced by the filename */
242  }
243  else if (*c == 'd')
244  {
245  /* '%d' is replaced by the target detail */
246  appendStringInfoString(&buf, target_detail);
247  }
248  else if (*c == '\0')
249  {
250  /* Incomplete escape sequence, expected a character afterward */
251  ereport(ERROR,
252  errcode(ERRCODE_SYNTAX_ERROR),
253  errmsg("shell command ends unexpectedly after escape character \"%%\""));
254  }
255  else
256  {
257  /* Unknown escape sequence */
258  ereport(ERROR,
259  errcode(ERRCODE_SYNTAX_ERROR),
260  errmsg("shell command contains unexpected escape sequence \"%c\"",
261  *c));
262  }
263  }
264 
265  return buf.data;
266 }
267 
268 /*
269  * Finish executing the shell command once all data has been written.
270  */
271 static void
273 {
274  int pclose_rc;
275 
276  /* There should be a command running. */
277  Assert(sink->current_command != NULL);
278  Assert(sink->pipe != NULL);
279 
280  /* Close down the pipe we opened. */
281  pclose_rc = ClosePipeStream(sink->pipe);
282  if (pclose_rc == -1)
283  ereport(ERROR,
285  errmsg("could not close pipe to external command: %m")));
286  else if (pclose_rc != 0)
287  {
288  ereport(ERROR,
289  (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
290  errmsg("shell command \"%s\" failed",
291  sink->current_command),
292  errdetail_internal("%s", wait_result_to_str(pclose_rc))));
293  }
294 
295  /* Clean up. */
296  sink->pipe = NULL;
297  pfree(sink->current_command);
298  sink->current_command = NULL;
299 }
300 
301 /*
302  * Start up the shell command, substituting %f in for the current filename.
303  */
304 static void
306 {
307  /* There should not be anything already running. */
308  Assert(sink->current_command == NULL);
309  Assert(sink->pipe == NULL);
310 
311  /* Construct a suitable command. */
313  filename,
314  sink->target_detail);
315 
316  /* Run it. */
318 }
319 
320 /*
321  * Send accumulated data to the running shell command.
322  */
323 static void
325 {
326  /* There should be a command running. */
327  Assert(sink->current_command != NULL);
328  Assert(sink->pipe != NULL);
329 
330  /* Try to write the data. */
331  if (fwrite(sink->base.bbs_buffer, len, 1, sink->pipe) != 1 ||
332  ferror(sink->pipe))
333  {
334  if (errno == EPIPE)
335  {
336  /*
337  * The error we're about to throw would shut down the command
338  * anyway, but we may get a more meaningful error message by doing
339  * this. If not, we'll fall through to the generic error below.
340  */
341  shell_finish_command(sink);
342  errno = EPIPE;
343  }
344  ereport(ERROR,
346  errmsg("could not write to shell backup program: %m")));
347  }
348 }
349 
350 /*
351  * At start of archive, start up the shell command and forward to next sink.
352  */
353 static void
354 bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
355 {
356  bbsink_shell *mysink = (bbsink_shell *) sink;
357 
358  shell_run_command(mysink, archive_name);
359  bbsink_forward_begin_archive(sink, archive_name);
360 }
361 
362 /*
363  * Send archive contents to command's stdin and forward to next sink.
364  */
365 static void
367 {
368  bbsink_shell *mysink = (bbsink_shell *) sink;
369 
370  shell_send_data(mysink, len);
372 }
373 
374 /*
375  * At end of archive, shut down the shell command and forward to next sink.
376  */
377 static void
379 {
380  bbsink_shell *mysink = (bbsink_shell *) sink;
381 
382  shell_finish_command(mysink);
384 }
385 
386 /*
387  * At start of manifest, start up the shell command and forward to next sink.
388  */
389 static void
391 {
392  bbsink_shell *mysink = (bbsink_shell *) sink;
393 
394  shell_run_command(mysink, "backup_manifest");
396 }
397 
398 /*
399  * Send manifest contents to command's stdin and forward to next sink.
400  */
401 static void
403 {
404  bbsink_shell *mysink = (bbsink_shell *) sink;
405 
406  shell_send_data(mysink, len);
408 }
409 
410 /*
411  * At end of manifest, shut down the shell command and forward to next sink.
412  */
413 static void
415 {
416  bbsink_shell *mysink = (bbsink_shell *) sink;
417 
418  shell_finish_command(mysink);
420 }
bool has_privs_of_role(Oid member, Oid role)
Definition: acl.c:4951
Oid get_role_oid(const char *rolname, bool missing_ok)
Definition: acl.c:5177
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)
void _PG_init(void)
static bbsink * shell_get_sink(bbsink *next_sink, void *detail_arg)
static void bbsink_shell_end_manifest(bbsink *sink)
PG_MODULE_MAGIC
static char * shell_construct_command(char *base_command, const char *filename, 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
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)
#define PG_BINARY_W
Definition: c.h:1271
int errdetail_internal(const char *fmt,...)
Definition: elog.c:1064
int errcode_for_file_access(void)
Definition: elog.c:716
int errhint(const char *fmt,...)
Definition: elog.c:1151
int errcode(int sqlerrcode)
Definition: elog.c:693
int errmsg(const char *fmt,...)
Definition: elog.c:904
#define ERROR
Definition: elog.h:33
#define ereport(elevel,...)
Definition: elog.h:143
int ClosePipeStream(FILE *file)
Definition: fd.c:2870
FILE * OpenPipeStream(const char *command, const char *mode)
Definition: fd.c:2564
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)
Definition: guc.c:9568
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:9629
@ PGC_SIGHUP
Definition: guc.h:72
Assert(fmt[strlen(fmt) - 1] !='\n')
char * pstrdup(const char *in)
Definition: mcxt.c:1305
void pfree(void *pointer)
Definition: mcxt.c:1175
void * palloc0(Size size)
Definition: mcxt.c:1099
Oid GetUserId(void)
Definition: miscinit.c:492
const void size_t len
static char * filename
Definition: pg_dumpall.c:94
static char * buf
Definition: pg_test_fsync.c:67
unsigned int Oid
Definition: postgres_ext.h:31
char * c
void appendStringInfoString(StringInfo str, const char *s)
Definition: stringinfo.c:176
void appendStringInfoChar(StringInfo str, char ch)
Definition: stringinfo.c:188
void initStringInfo(StringInfo str)
Definition: stringinfo.c:59
void(* begin_backup)(bbsink *sink)
bbsink * bbs_next
char * bbs_buffer
const bbsink_ops * bbs_ops
char * wait_result_to_str(int exitstatus)
Definition: wait_error.c:32
void StartTransactionCommand(void)
Definition: xact.c:2925
void CommitTransactionCommand(void)
Definition: xact.c:3022