/*-------------------------------------------------------------------------
*
* basebackup_to_shell . c
* target base backup files to a shell command
*
* Copyright ( c ) 2016 - 2025 , PostgreSQL Global Development Group
*
* contrib / basebackup_to_shell / basebackup_to_shell . c
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
#include "postgres.h"
#include "access/xact.h"
#include "backup/basebackup_target.h"
#include "common/percentrepl.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/guc.h"
PG_MODULE_MAGIC_EXT(
.name = "basebackup_to_shell" ,
.version = PG_VERSION
);
typedef struct bbsink_shell
{
/* Common information for all types of sink. */
bbsink base;
/* User-supplied target detail string. */
char *target_detail;
/* Shell command pattern being used for this backup. */
char *shell_command;
/* The command that is currently running. */
char *current_command;
/* Pipe to the running command. */
FILE *pipe;
} bbsink_shell;
static void *shell_check_detail(char *target, char *target_detail);
static bbsink *shell_get_sink(bbsink *next_sink, void *detail_arg);
static void bbsink_shell_begin_archive(bbsink *sink,
const char *archive_name);
static void bbsink_shell_archive_contents(bbsink *sink, size_t len);
static void bbsink_shell_end_archive(bbsink *sink);
static void bbsink_shell_begin_manifest(bbsink *sink);
static void bbsink_shell_manifest_contents(bbsink *sink, size_t len);
static void bbsink_shell_end_manifest(bbsink *sink);
static const bbsink_ops bbsink_shell_ops = {
.begin_backup = bbsink_forward_begin_backup,
.begin_archive = bbsink_shell_begin_archive,
.archive_contents = bbsink_shell_archive_contents,
.end_archive = bbsink_shell_end_archive,
.begin_manifest = bbsink_shell_begin_manifest,
.manifest_contents = bbsink_shell_manifest_contents,
.end_manifest = bbsink_shell_end_manifest,
.end_backup = bbsink_forward_end_backup,
.cleanup = bbsink_forward_cleanup
};
static char *shell_command = "" ;
static char *shell_required_role = "" ;
void
_PG_init(void )
{
DefineCustomStringVariable("basebackup_to_shell.command" ,
"Shell command to be executed for each backup file." ,
NULL,
&shell_command,
"" ,
PGC_SIGHUP,
0 ,
NULL, NULL, NULL);
DefineCustomStringVariable("basebackup_to_shell.required_role" ,
"Backup user must be a member of this role to use shell backup target." ,
NULL,
&shell_required_role,
"" ,
PGC_SIGHUP,
0 ,
NULL, NULL, NULL);
MarkGUCPrefixReserved("basebackup_to_shell" );
BaseBackupAddTarget("shell" , shell_check_detail, shell_get_sink);
}
/*
* We choose to defer sanity checking until shell_get_sink ( ) , and so
* just pass the target detail through without doing anything . However , we do
* permissions checks here , before any real work has been done .
*/
static void *
shell_check_detail(char *target, char *target_detail)
{
if (shell_required_role[0 ] != '\0' )
{
Oid roleid;
StartTransactionCommand();
roleid = get_role_oid(shell_required_role, true );
if (!has_privs_of_role(GetUserId(), roleid))
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("permission denied to use basebackup_to_shell" )));
CommitTransactionCommand();
}
return target_detail;
}
/*
* Set up a bbsink to implement this base backup target .
*
* This is also a convenient place to sanity check that a target detail was
* given if and only if % d is present .
*/
static bbsink *
shell_get_sink(bbsink *next_sink, void *detail_arg)
{
bbsink_shell *sink;
bool has_detail_escape = false ;
char *c;
/*
* Set up the bbsink .
*
* We remember the current value of basebackup_to_shell . shell_command to
* be certain that it can ' t change under us during the backup .
*/
sink = palloc0(sizeof (bbsink_shell));
*((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_shell_ops;
sink->base.bbs_next = next_sink;
sink->target_detail = detail_arg;
sink->shell_command = pstrdup(shell_command);
/* Reject an empty shell command. */
if (sink->shell_command[0 ] == '\0' )
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("shell command for backup is not configured" ));
/* Determine whether the shell command we're using contains %d. */
for (c = sink->shell_command; *c != '\0' ; ++c)
{
if (c[0 ] == '%' && c[1 ] != '\0' )
{
if (c[1 ] == 'd' )
has_detail_escape = true ;
++c;
}
}
/* There should be a target detail if %d was used, and not otherwise. */
if (has_detail_escape && sink->target_detail == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("a target detail is required because the configured command includes %%d" ),
errhint("Try \" pg_basebackup --target shell:DETAIL ...\"" )));
else if (!has_detail_escape && sink->target_detail != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("a target detail is not permitted because the configured command does not include %%d" )));
/*
* Since we ' re passing the string provided by the user to popen ( ) , it will
* be interpreted by the shell , which is a potential security
* vulnerability , since the user invoking this module is not necessarily a
* superuser . To stay out of trouble , we must disallow any shell
* metacharacters here ; to be conservative and keep things simple , we
* allow only alphanumerics .
*/
if (sink->target_detail != NULL)
{
char *d;
bool scary = false ;
for (d = sink->target_detail; *d != '\0' ; ++d)
{
if (*d >= 'a' && *d <= 'z' )
continue ;
if (*d >= 'A' && *d <= 'Z' )
continue ;
if (*d >= '0' && *d <= '9' )
continue ;
scary = true ;
break ;
}
if (scary)
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("target detail must contain only alphanumeric characters" ));
}
return &sink->base;
}
/*
* Construct the exact shell command that we ' re actually going to run ,
* making substitutions as appropriate for escape sequences .
*/
static char *
shell_construct_command(const char *base_command, const char *filename,
const char *target_detail)
{
return replace_percent_placeholders(base_command, "basebackup_to_shell.command" ,
"df" , target_detail, filename);
}
/*
* Finish executing the shell command once all data has been written .
*/
static void
shell_finish_command(bbsink_shell *sink)
{
int pclose_rc;
/* There should be a command running. */
Assert(sink->current_command != NULL);
Assert(sink->pipe != NULL);
/* Close down the pipe we opened. */
pclose_rc = ClosePipeStream(sink->pipe);
if (pclose_rc == -1 )
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not close pipe to external command: %m" )));
else if (pclose_rc != 0 )
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("shell command \" %s\" failed" ,
sink->current_command),
errdetail_internal("%s" , wait_result_to_str(pclose_rc))));
}
/* Clean up. */
sink->pipe = NULL;
pfree(sink->current_command);
sink->current_command = NULL;
}
/*
* Start up the shell command , substituting % f in for the current filename .
*/
static void
shell_run_command(bbsink_shell *sink, const char *filename)
{
/* There should not be anything already running. */
Assert(sink->current_command == NULL);
Assert(sink->pipe == NULL);
/* Construct a suitable command. */
sink->current_command = shell_construct_command(sink->shell_command,
filename,
sink->target_detail);
/* Run it. */
sink->pipe = OpenPipeStream(sink->current_command, PG_BINARY_W);
if (sink->pipe == NULL)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not execute command \" %s\": %m" ,
sink->current_command)));
}
/*
* Send accumulated data to the running shell command .
*/
static void
shell_send_data(bbsink_shell *sink, size_t len)
{
/* There should be a command running. */
Assert(sink->current_command != NULL);
Assert(sink->pipe != NULL);
/* Try to write the data. */
if (fwrite(sink->base.bbs_buffer, len, 1 , sink->pipe) != 1 ||
ferror(sink->pipe))
{
if (errno == EPIPE)
{
/*
* The error we ' re about to throw would shut down the command
* anyway , but we may get a more meaningful error message by doing
* this . If not , we ' ll fall through to the generic error below .
*/
shell_finish_command(sink);
errno = EPIPE;
}
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not write to shell backup program: %m" )));
}
}
/*
* At start of archive , start up the shell command and forward to next sink .
*/
static void
bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_run_command(mysink, archive_name);
bbsink_forward_begin_archive(sink, archive_name);
}
/*
* Send archive contents to command ' s stdin and forward to next sink .
*/
static void
bbsink_shell_archive_contents(bbsink *sink, size_t len)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_send_data(mysink, len);
bbsink_forward_archive_contents(sink, len);
}
/*
* At end of archive , shut down the shell command and forward to next sink .
*/
static void
bbsink_shell_end_archive(bbsink *sink)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_finish_command(mysink);
bbsink_forward_end_archive(sink);
}
/*
* At start of manifest , start up the shell command and forward to next sink .
*/
static void
bbsink_shell_begin_manifest(bbsink *sink)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_run_command(mysink, "backup_manifest" );
bbsink_forward_begin_manifest(sink);
}
/*
* Send manifest contents to command ' s stdin and forward to next sink .
*/
static void
bbsink_shell_manifest_contents(bbsink *sink, size_t len)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_send_data(mysink, len);
bbsink_forward_manifest_contents(sink, len);
}
/*
* At end of manifest , shut down the shell command and forward to next sink .
*/
static void
bbsink_shell_end_manifest(bbsink *sink)
{
bbsink_shell *mysink = (bbsink_shell *) sink;
shell_finish_command(mysink);
bbsink_forward_end_manifest(sink);
}
Messung V0.5 in Prozent C=92 H=97 G=94
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland