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-2024, 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"
15 #include "common/percentrepl.h"
16 #include "miscadmin.h"
17 #include "storage/fd.h"
18 #include "utils/acl.h"
19 #include "utils/guc.h"
20 
22 
23 typedef struct bbsink_shell
24 {
25  /* Common information for all types of sink. */
27 
28  /* User-supplied target detail string. */
30 
31  /* Shell command pattern being used for this backup. */
33 
34  /* The command that is currently running. */
36 
37  /* Pipe to the running command. */
38  FILE *pipe;
40 
41 static void *shell_check_detail(char *target, char *target_detail);
42 static bbsink *shell_get_sink(bbsink *next_sink, void *detail_arg);
43 
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);
51 
52 static const bbsink_ops bbsink_shell_ops = {
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
62 };
63 
64 static char *shell_command = "";
65 static char *shell_required_role = "";
66 
67 void
68 _PG_init(void)
69 {
70  DefineCustomStringVariable("basebackup_to_shell.command",
71  "Shell command to be executed for each backup file.",
72  NULL,
74  "",
75  PGC_SIGHUP,
76  0,
77  NULL, NULL, NULL);
78 
79  DefineCustomStringVariable("basebackup_to_shell.required_role",
80  "Backup user must be a member of this role to use shell backup target.",
81  NULL,
83  "",
84  PGC_SIGHUP,
85  0,
86  NULL, NULL, NULL);
87 
88  MarkGUCPrefixReserved("basebackup_to_shell");
89 
91 }
92 
93 /*
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.
97  */
98 static void *
99 shell_check_detail(char *target, char *target_detail)
100 {
101  if (shell_required_role[0] != '\0')
102  {
103  Oid roleid;
104 
106  roleid = get_role_oid(shell_required_role, true);
107  if (!has_privs_of_role(GetUserId(), roleid))
108  ereport(ERROR,
109  (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
110  errmsg("permission denied to use basebackup_to_shell")));
112  }
113 
114  return target_detail;
115 }
116 
117 /*
118  * Set up a bbsink to implement this base backup target.
119  *
120  * This is also a convenient place to sanity check that a target detail was
121  * given if and only if %d is present.
122  */
123 static bbsink *
124 shell_get_sink(bbsink *next_sink, void *detail_arg)
125 {
126  bbsink_shell *sink;
127  bool has_detail_escape = false;
128  char *c;
129 
130  /*
131  * Set up the bbsink.
132  *
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.
135  */
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;
141 
142  /* Reject an empty shell command. */
143  if (sink->shell_command[0] == '\0')
144  ereport(ERROR,
145  errcode(ERRCODE_INVALID_PARAMETER_VALUE),
146  errmsg("shell command for backup is not configured"));
147 
148  /* Determine whether the shell command we're using contains %d. */
149  for (c = sink->shell_command; *c != '\0'; ++c)
150  {
151  if (c[0] == '%' && c[1] != '\0')
152  {
153  if (c[1] == 'd')
154  has_detail_escape = true;
155  ++c;
156  }
157  }
158 
159  /* There should be a target detail if %d was used, and not otherwise. */
160  if (has_detail_escape && sink->target_detail == NULL)
161  ereport(ERROR,
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)
166  ereport(ERROR,
167  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
168  errmsg("a target detail is not permitted because the configured command does not include %%d")));
169 
170  /*
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.
177  */
178  if (sink->target_detail != NULL)
179  {
180  char *d;
181  bool scary = false;
182 
183  for (d = sink->target_detail; *d != '\0'; ++d)
184  {
185  if (*d >= 'a' && *d <= 'z')
186  continue;
187  if (*d >= 'A' && *d <= 'Z')
188  continue;
189  if (*d >= '0' && *d <= '9')
190  continue;
191  scary = true;
192  break;
193  }
194 
195  if (scary)
196  ereport(ERROR,
197  errcode(ERRCODE_INVALID_PARAMETER_VALUE),
198  errmsg("target detail must contain only alphanumeric characters"));
199  }
200 
201  return &sink->base;
202 }
203 
204 /*
205  * Construct the exact shell command that we're actually going to run,
206  * making substitutions as appropriate for escape sequences.
207  */
208 static char *
209 shell_construct_command(const char *base_command, const char *filename,
210  const char *target_detail)
211 {
212  return replace_percent_placeholders(base_command, "basebackup_to_shell.command",
213  "df", target_detail, filename);
214 }
215 
216 /*
217  * Finish executing the shell command once all data has been written.
218  */
219 static void
221 {
222  int pclose_rc;
223 
224  /* There should be a command running. */
225  Assert(sink->current_command != NULL);
226  Assert(sink->pipe != NULL);
227 
228  /* Close down the pipe we opened. */
229  pclose_rc = ClosePipeStream(sink->pipe);
230  if (pclose_rc == -1)
231  ereport(ERROR,
233  errmsg("could not close pipe to external command: %m")));
234  else if (pclose_rc != 0)
235  {
236  ereport(ERROR,
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))));
241  }
242 
243  /* Clean up. */
244  sink->pipe = NULL;
245  pfree(sink->current_command);
246  sink->current_command = NULL;
247 }
248 
249 /*
250  * Start up the shell command, substituting %f in for the current filename.
251  */
252 static void
254 {
255  /* There should not be anything already running. */
256  Assert(sink->current_command == NULL);
257  Assert(sink->pipe == NULL);
258 
259  /* Construct a suitable command. */
261  filename,
262  sink->target_detail);
263 
264  /* Run it. */
266  if (sink->pipe == NULL)
267  ereport(ERROR,
269  errmsg("could not execute command \"%s\": %m",
270  sink->current_command)));
271 }
272 
273 /*
274  * Send accumulated data to the running shell command.
275  */
276 static void
278 {
279  /* There should be a command running. */
280  Assert(sink->current_command != NULL);
281  Assert(sink->pipe != NULL);
282 
283  /* Try to write the data. */
284  if (fwrite(sink->base.bbs_buffer, len, 1, sink->pipe) != 1 ||
285  ferror(sink->pipe))
286  {
287  if (errno == EPIPE)
288  {
289  /*
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.
293  */
294  shell_finish_command(sink);
295  errno = EPIPE;
296  }
297  ereport(ERROR,
299  errmsg("could not write to shell backup program: %m")));
300  }
301 }
302 
303 /*
304  * At start of archive, start up the shell command and forward to next sink.
305  */
306 static void
307 bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
308 {
309  bbsink_shell *mysink = (bbsink_shell *) sink;
310 
311  shell_run_command(mysink, archive_name);
312  bbsink_forward_begin_archive(sink, archive_name);
313 }
314 
315 /*
316  * Send archive contents to command's stdin and forward to next sink.
317  */
318 static void
320 {
321  bbsink_shell *mysink = (bbsink_shell *) sink;
322 
323  shell_send_data(mysink, len);
325 }
326 
327 /*
328  * At end of archive, shut down the shell command and forward to next sink.
329  */
330 static void
332 {
333  bbsink_shell *mysink = (bbsink_shell *) sink;
334 
335  shell_finish_command(mysink);
337 }
338 
339 /*
340  * At start of manifest, start up the shell command and forward to next sink.
341  */
342 static void
344 {
345  bbsink_shell *mysink = (bbsink_shell *) sink;
346 
347  shell_run_command(mysink, "backup_manifest");
349 }
350 
351 /*
352  * Send manifest contents to command's stdin and forward to next sink.
353  */
354 static void
356 {
357  bbsink_shell *mysink = (bbsink_shell *) sink;
358 
359  shell_send_data(mysink, len);
361 }
362 
363 /*
364  * At end of manifest, shut down the shell command and forward to next sink.
365  */
366 static void
368 {
369  bbsink_shell *mysink = (bbsink_shell *) sink;
370 
371  shell_finish_command(mysink);
373 }
bool has_privs_of_role(Oid member, Oid role)
Definition: acl.c:5128
Oid get_role_oid(const char *rolname, bool missing_ok)
Definition: acl.c:5414
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 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 char * shell_construct_command(const char *base_command, const char *filename, const char *target_detail)
static void bbsink_shell_manifest_contents(bbsink *sink, size_t len)
#define Assert(condition)
Definition: c.h:858
#define PG_BINARY_W
Definition: c.h:1276
int errdetail_internal(const char *fmt,...)
Definition: elog.c:1232
int errcode_for_file_access(void)
Definition: elog.c:882
int errhint(const char *fmt,...)
Definition: elog.c:1319
int errcode(int sqlerrcode)
Definition: elog.c:859
int errmsg(const char *fmt,...)
Definition: elog.c:1072
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
int ClosePipeStream(FILE *file)
Definition: fd.c:2991
FILE * OpenPipeStream(const char *command, const char *mode)
Definition: fd.c:2686
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:5171
void MarkGUCPrefixReserved(const char *className)
Definition: guc.c:5232
@ PGC_SIGHUP
Definition: guc.h:71
char * pstrdup(const char *in)
Definition: mcxt.c:1695
void pfree(void *pointer)
Definition: mcxt.c:1520
void * palloc0(Size size)
Definition: mcxt.c:1346
Oid GetUserId(void)
Definition: miscinit.c:514
char * replace_percent_placeholders(const char *instr, const char *param_name, const char *letters,...)
Definition: percentrepl.c:59
const void size_t len
static char * filename
Definition: pg_dumpall.c:119
unsigned int Oid
Definition: postgres_ext.h:31
char * c
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:33
void StartTransactionCommand(void)
Definition: xact.c:2995
void CommitTransactionCommand(void)
Definition: xact.c:3093