LCOV - differential code coverage report
Current view: top level - contrib/basebackup_to_shell - basebackup_to_shell.c (source / functions) Coverage Total Hit LBC UIC UBC GBC GIC GNC CBC EUB ECB DUB DCB
Current: Differential Code Coverage HEAD vs 15 Lines: 88.0 % 108 95 2 6 5 3 64 2 26 3 51 2 16
Current Date: 2023-04-08 17:13:01 Functions: 100.0 % 14 14 13 1 12 2
Baseline: 15 Line coverage date bins:
Baseline Date: 2023-04-08 15:09:40 (60,120] days: 100.0 % 2 2 2
Legend: Lines: hit not hit (240..) days: 87.7 % 106 93 2 6 5 3 64 26 3 51
Function coverage date bins:
(60,120] days: 100.0 % 1 1 1
(240..) days: 52.0 % 25 13 13 12

 Age         Owner                  TLA  Line data    Source code
                                  1                 : /*-------------------------------------------------------------------------
                                  2                 :  *
                                  3                 :  * basebackup_to_shell.c
                                  4                 :  *    target base backup files to a shell command
                                  5                 :  *
                                  6                 :  * Copyright (c) 2016-2023, 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 "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"
                                 20                 : 
  390 rhaas                      21 GIC           1 : PG_MODULE_MAGIC;
  390 rhaas                      22 ECB             : 
                                 23                 : typedef struct bbsink_shell
                                 24                 : {
                                 25                 :     /* Common information for all types of sink. */
                                 26                 :     bbsink      base;
                                 27                 : 
                                 28                 :     /* User-supplied target detail string. */
                                 29                 :     char       *target_detail;
                                 30                 : 
                                 31                 :     /* Shell command pattern being used for this backup. */
                                 32                 :     char       *shell_command;
                                 33                 : 
                                 34                 :     /* The command that is currently running. */
                                 35                 :     char       *current_command;
                                 36                 : 
                                 37                 :     /* Pipe to the running command. */
                                 38                 :     FILE       *pipe;
                                 39                 : } bbsink_shell;
                                 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 = {
                                 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
                                 62                 : };
                                 63                 : 
                                 64                 : static char *shell_command = "";
                                 65                 : static char *shell_required_role = "";
                                 66                 : 
                                 67                 : void
  390 rhaas                      68 GIC           1 : _PG_init(void)
  390 rhaas                      69 ECB             : {
  390 rhaas                      70 GIC           1 :     DefineCustomStringVariable("basebackup_to_shell.command",
                                 71                 :                                "Shell command to be executed for each backup file.",
                                 72                 :                                NULL,
                                 73                 :                                &shell_command,
                                 74                 :                                "",
                                 75                 :                                PGC_SIGHUP,
                                 76                 :                                0,
                                 77                 :                                NULL, NULL, NULL);
  390 rhaas                      78 ECB             : 
  390 rhaas                      79 GIC           1 :     DefineCustomStringVariable("basebackup_to_shell.required_role",
                                 80                 :                                "Backup user must be a member of this role to use shell backup target.",
                                 81                 :                                NULL,
                                 82                 :                                &shell_required_role,
                                 83                 :                                "",
                                 84                 :                                PGC_SIGHUP,
                                 85                 :                                0,
                                 86                 :                                NULL, NULL, NULL);
  390 rhaas                      87 ECB             : 
  342 michael                    88 GIC           1 :     MarkGUCPrefixReserved("basebackup_to_shell");
  342 michael                    89 ECB             : 
  390 rhaas                      90 CBC           1 :     BaseBackupAddTarget("shell", shell_check_detail, shell_get_sink);
  390 rhaas                      91 GIC           1 : }
                                 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                 :  */
  390 rhaas                      98 ECB             : static void *
  390 rhaas                      99 GIC           6 : shell_check_detail(char *target, char *target_detail)
  390 rhaas                     100 ECB             : {
  390 rhaas                     101 GIC           6 :     if (shell_required_role[0] != '\0')
                                102                 :     {
                                103                 :         Oid         roleid;
  390 rhaas                     104 ECB             : 
  390 rhaas                     105 CBC           3 :         StartTransactionCommand();
                                106               3 :         roleid = get_role_oid(shell_required_role, true);
  372 mail                      107               3 :         if (!has_privs_of_role(GetUserId(), roleid))
  390 rhaas                     108 GIC           1 :             ereport(ERROR,
                                109                 :                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
  390 rhaas                     110 ECB             :                      errmsg("permission denied to use basebackup_to_shell")));
  390 rhaas                     111 GIC           2 :         CommitTransactionCommand();
                                112                 :     }
  390 rhaas                     113 ECB             : 
  390 rhaas                     114 GIC           5 :     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                 :  */
  390 rhaas                     123 ECB             : static bbsink *
  390 rhaas                     124 GIC           5 : shell_get_sink(bbsink *next_sink, void *detail_arg)
                                125                 : {
  390 rhaas                     126 ECB             :     bbsink_shell *sink;
  332 tgl                       127 GIC           5 :     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.
  390 rhaas                     135 ECB             :      */
  390 rhaas                     136 CBC           5 :     sink = palloc0(sizeof(bbsink_shell));
                                137               5 :     *((const bbsink_ops **) &sink->base.bbs_ops) = &bbsink_shell_ops;
                                138               5 :     sink->base.bbs_next = next_sink;
                                139               5 :     sink->target_detail = detail_arg;
  390 rhaas                     140 GIC           5 :     sink->shell_command = pstrdup(shell_command);
                                141                 : 
  390 rhaas                     142 ECB             :     /* Reject an empty shell command. */
  390 rhaas                     143 CBC           5 :     if (sink->shell_command[0] == '\0')
  390 rhaas                     144 GIC           1 :         ereport(ERROR,
                                145                 :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                146                 :                 errmsg("shell command for backup is not configured"));
                                147                 : 
  390 rhaas                     148 ECB             :     /* Determine whether the shell command we're using contains %d. */
  390 rhaas                     149 GIC         396 :     for (c = sink->shell_command; *c != '\0'; ++c)
  390 rhaas                     150 ECB             :     {
  390 rhaas                     151 GIC         392 :         if (c[0] == '%' && c[1] != '\0')
  390 rhaas                     152 ECB             :         {
  390 rhaas                     153 CBC           6 :             if (c[1] == 'd')
                                154               2 :                 has_detail_escape = true;
  390 rhaas                     155 GIC           6 :             ++c;
                                156                 :         }
                                157                 :     }
                                158                 : 
  390 rhaas                     159 ECB             :     /* There should be a target detail if %d was used, and not otherwise. */
  390 rhaas                     160 CBC           4 :     if (has_detail_escape && sink->target_detail == NULL)
  390 rhaas                     161 GIC           1 :         ereport(ERROR,
                                162                 :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                163                 :                  errmsg("a target detail is required because the configured command includes %%d"),
  390 rhaas                     164 ECB             :                  errhint("Try \"pg_basebackup --target shell:DETAIL ...\"")));
  390 rhaas                     165 CBC           3 :     else if (!has_detail_escape && sink->target_detail != NULL)
  390 rhaas                     166 GIC           1 :         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.
  390 rhaas                     177 ECB             :      */
  390 rhaas                     178 GIC           2 :     if (sink->target_detail != NULL)
                                179                 :     {
  332 tgl                       180 ECB             :         char       *d;
  332 tgl                       181 GIC           1 :         bool        scary = false;
  390 rhaas                     182 ECB             : 
  390 rhaas                     183 GIC           4 :         for (d = sink->target_detail; *d != '\0'; ++d)
  390 rhaas                     184 ECB             :         {
  390 rhaas                     185 CBC           3 :             if (*d >= 'a' && *d <= 'z')
  390 rhaas                     186 GBC           3 :                 continue;
  390 rhaas                     187 UBC           0 :             if (*d >= 'A' && *d <= 'Z')
                                188               0 :                 continue;
                                189               0 :             if (*d >= '0' && *d <= '9')
                                190               0 :                 continue;
                                191               0 :             scary = true;
  390 rhaas                     192 UIC           0 :             break;
                                193                 :         }
  390 rhaas                     194 ECB             : 
  390 rhaas                     195 GBC           1 :         if (scary)
  390 rhaas                     196 UIC           0 :             ereport(ERROR,
                                197                 :                     errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                198                 :                     errmsg("target detail must contain only alphanumeric characters"));
                                199                 :     }
  390 rhaas                     200 ECB             : 
  390 rhaas                     201 GIC           2 :     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                 :  */
  390 rhaas                     208 ECB             : static char *
  117 peter                     209 GNC           4 : shell_construct_command(const char *base_command, const char *filename,
                                210                 :                         const char *target_detail)
  390 rhaas                     211 ECB             : {
   88 peter                     212 GNC           4 :     return replace_percent_placeholders(base_command, "basebackup_to_shell.command",
                                213                 :                                         "df", target_detail, filename);
  390 rhaas                     214 ECB             : }
                                215                 : 
                                216                 : /*
                                217                 :  * Finish executing the shell command once all data has been written.
                                218                 :  */
                                219                 : static void
  390 rhaas                     220 CBC           4 : shell_finish_command(bbsink_shell *sink)
                                221                 : {
                                222                 :     int         pclose_rc;
  390 rhaas                     223 ECB             : 
                                224                 :     /* There should be a command running. */
  390 rhaas                     225 GIC           4 :     Assert(sink->current_command != NULL);
                                226               4 :     Assert(sink->pipe != NULL);
  390 rhaas                     227 ECB             : 
                                228                 :     /* Close down the pipe we opened. */
  390 rhaas                     229 GIC           4 :     pclose_rc = ClosePipeStream(sink->pipe);
  390 rhaas                     230 GBC           4 :     if (pclose_rc == -1)
  390 rhaas                     231 UIC           0 :         ereport(ERROR,
                                232                 :                 (errcode_for_file_access(),
                                233                 :                  errmsg("could not close pipe to external command: %m")));
  390 rhaas                     234 GIC           4 :     else if (pclose_rc != 0)
                                235                 :     {
  390 rhaas                     236 UIC           0 :         ereport(ERROR,
  390 rhaas                     237 EUB             :                 (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. */
  390 rhaas                     244 CBC           4 :     sink->pipe = NULL;
  390 rhaas                     245 GIC           4 :     pfree(sink->current_command);
                                246               4 :     sink->current_command = NULL;
                                247               4 : }
                                248                 : 
                                249                 : /*
  390 rhaas                     250 ECB             :  * Start up the shell command, substituting %f in for the current filename.
                                251                 :  */
                                252                 : static void
  390 rhaas                     253 GIC           4 : shell_run_command(bbsink_shell *sink, const char *filename)
  390 rhaas                     254 ECB             : {
                                255                 :     /* There should not be anything already running. */
  390 rhaas                     256 CBC           4 :     Assert(sink->current_command == NULL);
  390 rhaas                     257 GIC           4 :     Assert(sink->pipe == NULL);
                                258                 : 
                                259                 :     /* Construct a suitable command. */
                                260               8 :     sink->current_command = shell_construct_command(sink->shell_command,
                                261                 :                                                     filename,
  390 rhaas                     262 CBC           4 :                                                     sink->target_detail);
                                263                 : 
  390 rhaas                     264 ECB             :     /* Run it. */
  390 rhaas                     265 GIC           4 :     sink->pipe = OpenPipeStream(sink->current_command, PG_BINARY_W);
  390 rhaas                     266 CBC           4 : }
  390 rhaas                     267 ECB             : 
                                268                 : /*
                                269                 :  * Send accumulated data to the running shell command.
                                270                 :  */
                                271                 : static void
  390 rhaas                     272 GIC        5416 : shell_send_data(bbsink_shell *sink, size_t len)
                                273                 : {
  390 rhaas                     274 ECB             :     /* There should be a command running. */
  390 rhaas                     275 GIC        5416 :     Assert(sink->current_command != NULL);
  390 rhaas                     276 CBC        5416 :     Assert(sink->pipe != NULL);
                                277                 : 
  390 rhaas                     278 ECB             :     /* Try to write the data. */
  390 rhaas                     279 CBC       10832 :     if (fwrite(sink->base.bbs_buffer, len, 1, sink->pipe) != 1 ||
                                280            5416 :         ferror(sink->pipe))
                                281                 :     {
  390 rhaas                     282 UIC           0 :         if (errno == EPIPE)
                                283                 :         {
                                284                 :             /*
                                285                 :              * The error we're about to throw would shut down the command
  332 tgl                       286 ECB             :              * anyway, but we may get a more meaningful error message by doing
                                287                 :              * this. If not, we'll fall through to the generic error below.
  390 rhaas                     288                 :              */
  390 rhaas                     289 UIC           0 :             shell_finish_command(sink);
  390 rhaas                     290 LBC           0 :             errno = EPIPE;
  390 rhaas                     291 ECB             :         }
  390 rhaas                     292 LBC           0 :         ereport(ERROR,
                                293                 :                 (errcode_for_file_access(),
                                294                 :                  errmsg("could not write to shell backup program: %m")));
                                295                 :     }
  390 rhaas                     296 GIC        5416 : }
                                297                 : 
  390 rhaas                     298 ECB             : /*
                                299                 :  * At start of archive, start up the shell command and forward to next sink.
                                300                 :  */
                                301                 : static void
  390 rhaas                     302 CBC           2 : bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
  390 rhaas                     303 ECB             : {
  390 rhaas                     304 CBC           2 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                305                 : 
  390 rhaas                     306 GIC           2 :     shell_run_command(mysink, archive_name);
                                307               2 :     bbsink_forward_begin_archive(sink, archive_name);
                                308               2 : }
                                309                 : 
  390 rhaas                     310 ECB             : /*
                                311                 :  * Send archive contents to command's stdin and forward to next sink.
                                312                 :  */
                                313                 : static void
  390 rhaas                     314 CBC        5406 : bbsink_shell_archive_contents(bbsink *sink, size_t len)
  390 rhaas                     315 ECB             : {
  390 rhaas                     316 CBC        5406 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                317                 : 
  390 rhaas                     318 GIC        5406 :     shell_send_data(mysink, len);
                                319            5406 :     bbsink_forward_archive_contents(sink, len);
                                320            5406 : }
                                321                 : 
                                322                 : /*
                                323                 :  * At end of archive, shut down the shell command and forward to next sink.
                                324                 :  */
                                325                 : static void
                                326               2 : bbsink_shell_end_archive(bbsink *sink)
                                327                 : {
                                328               2 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                329                 : 
                                330               2 :     shell_finish_command(mysink);
                                331               2 :     bbsink_forward_end_archive(sink);
                                332               2 : }
                                333                 : 
                                334                 : /*
                                335                 :  * At start of manifest, start up the shell command and forward to next sink.
                                336                 :  */
                                337                 : static void
                                338               2 : bbsink_shell_begin_manifest(bbsink *sink)
                                339                 : {
                                340               2 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                341                 : 
                                342               2 :     shell_run_command(mysink, "backup_manifest");
                                343               2 :     bbsink_forward_begin_manifest(sink);
                                344               2 : }
                                345                 : 
                                346                 : /*
                                347                 :  * Send manifest contents to command's stdin and forward to next sink.
                                348                 :  */
                                349                 : static void
                                350              10 : bbsink_shell_manifest_contents(bbsink *sink, size_t len)
                                351                 : {
                                352              10 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                353                 : 
                                354              10 :     shell_send_data(mysink, len);
                                355              10 :     bbsink_forward_manifest_contents(sink, len);
                                356              10 : }
                                357                 : 
                                358                 : /*
                                359                 :  * At end of manifest, shut down the shell command and forward to next sink.
                                360                 :  */
                                361                 : static void
                                362               2 : bbsink_shell_end_manifest(bbsink *sink)
                                363                 : {
                                364               2 :     bbsink_shell *mysink = (bbsink_shell *) sink;
                                365                 : 
                                366               2 :     shell_finish_command(mysink);
                                367               2 :     bbsink_forward_end_manifest(sink);
                                368               2 : }
        

Generated by: LCOV version v1.16-55-g56c0a2a