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 15:15:32 Functions: 100.0 % 14 14 13 1 12 2
Baseline: 15
Baseline Date: 2023-04-08 15:09:40
Legend: Lines: hit not hit

           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                 : 
      21 GIC           1 : PG_MODULE_MAGIC;
      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
      68 GIC           1 : _PG_init(void)
      69 ECB             : {
      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);
      78 ECB             : 
      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);
      87 ECB             : 
      88 GIC           1 :     MarkGUCPrefixReserved("basebackup_to_shell");
      89 ECB             : 
      90 CBC           1 :     BaseBackupAddTarget("shell", shell_check_detail, shell_get_sink);
      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                 :  */
      98 ECB             : static void *
      99 GIC           6 : shell_check_detail(char *target, char *target_detail)
     100 ECB             : {
     101 GIC           6 :     if (shell_required_role[0] != '\0')
     102                 :     {
     103                 :         Oid         roleid;
     104 ECB             : 
     105 CBC           3 :         StartTransactionCommand();
     106               3 :         roleid = get_role_oid(shell_required_role, true);
     107               3 :         if (!has_privs_of_role(GetUserId(), roleid))
     108 GIC           1 :             ereport(ERROR,
     109                 :                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     110 ECB             :                      errmsg("permission denied to use basebackup_to_shell")));
     111 GIC           2 :         CommitTransactionCommand();
     112                 :     }
     113 ECB             : 
     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                 :  */
     123 ECB             : static bbsink *
     124 GIC           5 : shell_get_sink(bbsink *next_sink, void *detail_arg)
     125                 : {
     126 ECB             :     bbsink_shell *sink;
     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.
     135 ECB             :      */
     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;
     140 GIC           5 :     sink->shell_command = pstrdup(shell_command);
     141                 : 
     142 ECB             :     /* Reject an empty shell command. */
     143 CBC           5 :     if (sink->shell_command[0] == '\0')
     144 GIC           1 :         ereport(ERROR,
     145                 :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     146                 :                 errmsg("shell command for backup is not configured"));
     147                 : 
     148 ECB             :     /* Determine whether the shell command we're using contains %d. */
     149 GIC         396 :     for (c = sink->shell_command; *c != '\0'; ++c)
     150 ECB             :     {
     151 GIC         392 :         if (c[0] == '%' && c[1] != '\0')
     152 ECB             :         {
     153 CBC           6 :             if (c[1] == 'd')
     154               2 :                 has_detail_escape = true;
     155 GIC           6 :             ++c;
     156                 :         }
     157                 :     }
     158                 : 
     159 ECB             :     /* There should be a target detail if %d was used, and not otherwise. */
     160 CBC           4 :     if (has_detail_escape && sink->target_detail == NULL)
     161 GIC           1 :         ereport(ERROR,
     162                 :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     163                 :                  errmsg("a target detail is required because the configured command includes %%d"),
     164 ECB             :                  errhint("Try \"pg_basebackup --target shell:DETAIL ...\"")));
     165 CBC           3 :     else if (!has_detail_escape && sink->target_detail != NULL)
     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.
     177 ECB             :      */
     178 GIC           2 :     if (sink->target_detail != NULL)
     179                 :     {
     180 ECB             :         char       *d;
     181 GIC           1 :         bool        scary = false;
     182 ECB             : 
     183 GIC           4 :         for (d = sink->target_detail; *d != '\0'; ++d)
     184 ECB             :         {
     185 CBC           3 :             if (*d >= 'a' && *d <= 'z')
     186 GBC           3 :                 continue;
     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;
     192 UIC           0 :             break;
     193                 :         }
     194 ECB             : 
     195 GBC           1 :         if (scary)
     196 UIC           0 :             ereport(ERROR,
     197                 :                     errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     198                 :                     errmsg("target detail must contain only alphanumeric characters"));
     199                 :     }
     200 ECB             : 
     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                 :  */
     208 ECB             : static char *
     209 GNC           4 : shell_construct_command(const char *base_command, const char *filename,
     210                 :                         const char *target_detail)
     211 ECB             : {
     212 GNC           4 :     return replace_percent_placeholders(base_command, "basebackup_to_shell.command",
     213                 :                                         "df", target_detail, filename);
     214 ECB             : }
     215                 : 
     216                 : /*
     217                 :  * Finish executing the shell command once all data has been written.
     218                 :  */
     219                 : static void
     220 CBC           4 : shell_finish_command(bbsink_shell *sink)
     221                 : {
     222                 :     int         pclose_rc;
     223 ECB             : 
     224                 :     /* There should be a command running. */
     225 GIC           4 :     Assert(sink->current_command != NULL);
     226               4 :     Assert(sink->pipe != NULL);
     227 ECB             : 
     228                 :     /* Close down the pipe we opened. */
     229 GIC           4 :     pclose_rc = ClosePipeStream(sink->pipe);
     230 GBC           4 :     if (pclose_rc == -1)
     231 UIC           0 :         ereport(ERROR,
     232                 :                 (errcode_for_file_access(),
     233                 :                  errmsg("could not close pipe to external command: %m")));
     234 GIC           4 :     else if (pclose_rc != 0)
     235                 :     {
     236 UIC           0 :         ereport(ERROR,
     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. */
     244 CBC           4 :     sink->pipe = NULL;
     245 GIC           4 :     pfree(sink->current_command);
     246               4 :     sink->current_command = NULL;
     247               4 : }
     248                 : 
     249                 : /*
     250 ECB             :  * Start up the shell command, substituting %f in for the current filename.
     251                 :  */
     252                 : static void
     253 GIC           4 : shell_run_command(bbsink_shell *sink, const char *filename)
     254 ECB             : {
     255                 :     /* There should not be anything already running. */
     256 CBC           4 :     Assert(sink->current_command == NULL);
     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,
     262 CBC           4 :                                                     sink->target_detail);
     263                 : 
     264 ECB             :     /* Run it. */
     265 GIC           4 :     sink->pipe = OpenPipeStream(sink->current_command, PG_BINARY_W);
     266 CBC           4 : }
     267 ECB             : 
     268                 : /*
     269                 :  * Send accumulated data to the running shell command.
     270                 :  */
     271                 : static void
     272 GIC        5416 : shell_send_data(bbsink_shell *sink, size_t len)
     273                 : {
     274 ECB             :     /* There should be a command running. */
     275 GIC        5416 :     Assert(sink->current_command != NULL);
     276 CBC        5416 :     Assert(sink->pipe != NULL);
     277                 : 
     278 ECB             :     /* Try to write the data. */
     279 CBC       10832 :     if (fwrite(sink->base.bbs_buffer, len, 1, sink->pipe) != 1 ||
     280            5416 :         ferror(sink->pipe))
     281                 :     {
     282 UIC           0 :         if (errno == EPIPE)
     283                 :         {
     284                 :             /*
     285                 :              * The error we're about to throw would shut down the command
     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.
     288                 :              */
     289 UIC           0 :             shell_finish_command(sink);
     290 LBC           0 :             errno = EPIPE;
     291 ECB             :         }
     292 LBC           0 :         ereport(ERROR,
     293                 :                 (errcode_for_file_access(),
     294                 :                  errmsg("could not write to shell backup program: %m")));
     295                 :     }
     296 GIC        5416 : }
     297                 : 
     298 ECB             : /*
     299                 :  * At start of archive, start up the shell command and forward to next sink.
     300                 :  */
     301                 : static void
     302 CBC           2 : bbsink_shell_begin_archive(bbsink *sink, const char *archive_name)
     303 ECB             : {
     304 CBC           2 :     bbsink_shell *mysink = (bbsink_shell *) sink;
     305                 : 
     306 GIC           2 :     shell_run_command(mysink, archive_name);
     307               2 :     bbsink_forward_begin_archive(sink, archive_name);
     308               2 : }
     309                 : 
     310 ECB             : /*
     311                 :  * Send archive contents to command's stdin and forward to next sink.
     312                 :  */
     313                 : static void
     314 CBC        5406 : bbsink_shell_archive_contents(bbsink *sink, size_t len)
     315 ECB             : {
     316 CBC        5406 :     bbsink_shell *mysink = (bbsink_shell *) sink;
     317                 : 
     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