LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - astreamer_inject.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 82.4 % 74 61
Test Date: 2026-03-03 14:15:12 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * astreamer_inject.c
       4              :  *
       5              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       6              :  *
       7              :  * IDENTIFICATION
       8              :  *        src/bin/pg_basebackup/astreamer_inject.c
       9              :  *-------------------------------------------------------------------------
      10              :  */
      11              : 
      12              : #include "postgres_fe.h"
      13              : 
      14              : #include "astreamer_inject.h"
      15              : #include "common/file_perm.h"
      16              : #include "common/logging.h"
      17              : 
      18              : typedef struct astreamer_recovery_injector
      19              : {
      20              :     astreamer   base;
      21              :     bool        skip_file;
      22              :     bool        is_recovery_guc_supported;
      23              :     bool        is_postgresql_auto_conf;
      24              :     bool        found_postgresql_auto_conf;
      25              :     PQExpBuffer recoveryconfcontents;
      26              :     astreamer_member member;
      27              : } astreamer_recovery_injector;
      28              : 
      29              : static void astreamer_recovery_injector_content(astreamer *streamer,
      30              :                                                 astreamer_member *member,
      31              :                                                 const char *data, int len,
      32              :                                                 astreamer_archive_context context);
      33              : static void astreamer_recovery_injector_finalize(astreamer *streamer);
      34              : static void astreamer_recovery_injector_free(astreamer *streamer);
      35              : 
      36              : static const astreamer_ops astreamer_recovery_injector_ops = {
      37              :     .content = astreamer_recovery_injector_content,
      38              :     .finalize = astreamer_recovery_injector_finalize,
      39              :     .free = astreamer_recovery_injector_free
      40              : };
      41              : 
      42              : /*
      43              :  * Create a astreamer that can edit recoverydata into an archive stream.
      44              :  *
      45              :  * The input should be a series of typed chunks (not ASTREAMER_UNKNOWN) as
      46              :  * per the conventions described in astreamer.h; the chunks forwarded to
      47              :  * the next astreamer will be similarly typed, but the
      48              :  * ASTREAMER_MEMBER_HEADER chunks may be zero-length in cases where we've
      49              :  * edited the archive stream.
      50              :  *
      51              :  * Our goal is to do one of the following three things with the content passed
      52              :  * via recoveryconfcontents: (1) if is_recovery_guc_supported is false, then
      53              :  * put the content into recovery.conf, replacing any existing archive member
      54              :  * by that name; (2) if is_recovery_guc_supported is true and
      55              :  * postgresql.auto.conf exists in the archive, then append the content
      56              :  * provided to the existing file; and (3) if is_recovery_guc_supported is
      57              :  * true but postgresql.auto.conf does not exist in the archive, then create
      58              :  * it with the specified content.
      59              :  *
      60              :  * In addition, if is_recovery_guc_supported is true, then we create a
      61              :  * zero-length standby.signal file, dropping any file with that name from
      62              :  * the archive.
      63              :  */
      64              : astreamer *
      65            3 : astreamer_recovery_injector_new(astreamer *next,
      66              :                                 bool is_recovery_guc_supported,
      67              :                                 PQExpBuffer recoveryconfcontents)
      68              : {
      69              :     astreamer_recovery_injector *streamer;
      70              : 
      71            3 :     streamer = palloc0_object(astreamer_recovery_injector);
      72            3 :     *((const astreamer_ops **) &streamer->base.bbs_ops) =
      73              :         &astreamer_recovery_injector_ops;
      74            3 :     streamer->base.bbs_next = next;
      75            3 :     streamer->is_recovery_guc_supported = is_recovery_guc_supported;
      76            3 :     streamer->recoveryconfcontents = recoveryconfcontents;
      77              : 
      78            3 :     return &streamer->base;
      79              : }
      80              : 
      81              : /*
      82              :  * Handle each chunk of tar content while injecting recovery configuration.
      83              :  */
      84              : static void
      85         9525 : astreamer_recovery_injector_content(astreamer *streamer,
      86              :                                     astreamer_member *member,
      87              :                                     const char *data, int len,
      88              :                                     astreamer_archive_context context)
      89              : {
      90              :     astreamer_recovery_injector *mystreamer;
      91              : 
      92         9525 :     mystreamer = (astreamer_recovery_injector *) streamer;
      93              :     Assert(member != NULL || context == ASTREAMER_ARCHIVE_TRAILER);
      94              : 
      95         9525 :     switch (context)
      96              :     {
      97         2988 :         case ASTREAMER_MEMBER_HEADER:
      98              :             /* Must copy provided data so we have the option to modify it. */
      99         2988 :             memcpy(&mystreamer->member, member, sizeof(astreamer_member));
     100              : 
     101              :             /*
     102              :              * On v12+, skip standby.signal and edit postgresql.auto.conf; on
     103              :              * older versions, skip recovery.conf.
     104              :              */
     105         2988 :             if (mystreamer->is_recovery_guc_supported)
     106              :             {
     107         2988 :                 mystreamer->skip_file =
     108         2988 :                     (strcmp(member->pathname, "standby.signal") == 0);
     109         2988 :                 mystreamer->is_postgresql_auto_conf =
     110         2988 :                     (strcmp(member->pathname, "postgresql.auto.conf") == 0);
     111         2988 :                 if (mystreamer->is_postgresql_auto_conf)
     112              :                 {
     113              :                     /* Remember we saw it so we don't add it again. */
     114            3 :                     mystreamer->found_postgresql_auto_conf = true;
     115              : 
     116              :                     /* Increment length by data to be injected. */
     117            3 :                     mystreamer->member.size +=
     118            3 :                         mystreamer->recoveryconfcontents->len;
     119              : 
     120              :                     /*
     121              :                      * Zap data and len because the archive header is no
     122              :                      * longer valid; some subsequent astreamer must regenerate
     123              :                      * it if it's necessary.
     124              :                      */
     125            3 :                     data = NULL;
     126            3 :                     len = 0;
     127              :                 }
     128              :             }
     129              :             else
     130            0 :                 mystreamer->skip_file =
     131            0 :                     (strcmp(member->pathname, "recovery.conf") == 0);
     132              : 
     133              :             /* Do not forward if the file is to be skipped. */
     134         2988 :             if (mystreamer->skip_file)
     135            0 :                 return;
     136         2988 :             break;
     137              : 
     138         3546 :         case ASTREAMER_MEMBER_CONTENTS:
     139              :             /* Do not forward if the file is to be skipped. */
     140         3546 :             if (mystreamer->skip_file)
     141            0 :                 return;
     142         3546 :             break;
     143              : 
     144         2988 :         case ASTREAMER_MEMBER_TRAILER:
     145              :             /* Do not forward it the file is to be skipped. */
     146         2988 :             if (mystreamer->skip_file)
     147            0 :                 return;
     148              : 
     149              :             /* Append provided content to whatever we already sent. */
     150         2988 :             if (mystreamer->is_postgresql_auto_conf)
     151            3 :                 astreamer_content(mystreamer->base.bbs_next, member,
     152            3 :                                   mystreamer->recoveryconfcontents->data,
     153            3 :                                   mystreamer->recoveryconfcontents->len,
     154              :                                   ASTREAMER_MEMBER_CONTENTS);
     155         2988 :             break;
     156              : 
     157            3 :         case ASTREAMER_ARCHIVE_TRAILER:
     158            3 :             if (mystreamer->is_recovery_guc_supported)
     159              :             {
     160              :                 /*
     161              :                  * If we didn't already find (and thus modify)
     162              :                  * postgresql.auto.conf, inject it as an additional archive
     163              :                  * member now.
     164              :                  */
     165            3 :                 if (!mystreamer->found_postgresql_auto_conf)
     166            0 :                     astreamer_inject_file(mystreamer->base.bbs_next,
     167              :                                           "postgresql.auto.conf",
     168            0 :                                           mystreamer->recoveryconfcontents->data,
     169            0 :                                           mystreamer->recoveryconfcontents->len);
     170              : 
     171              :                 /* Inject empty standby.signal file. */
     172            3 :                 astreamer_inject_file(mystreamer->base.bbs_next,
     173              :                                       "standby.signal", "", 0);
     174              :             }
     175              :             else
     176              :             {
     177              :                 /* Inject recovery.conf file with specified contents. */
     178            0 :                 astreamer_inject_file(mystreamer->base.bbs_next,
     179              :                                       "recovery.conf",
     180            0 :                                       mystreamer->recoveryconfcontents->data,
     181            0 :                                       mystreamer->recoveryconfcontents->len);
     182              :             }
     183              : 
     184              :             /* Nothing to do here. */
     185            3 :             break;
     186              : 
     187            0 :         default:
     188              :             /* Shouldn't happen. */
     189            0 :             pg_fatal("unexpected state while injecting recovery settings");
     190              :     }
     191              : 
     192         9525 :     astreamer_content(mystreamer->base.bbs_next, &mystreamer->member,
     193              :                       data, len, context);
     194              : }
     195              : 
     196              : /*
     197              :  * End-of-stream processing for this astreamer.
     198              :  */
     199              : static void
     200            3 : astreamer_recovery_injector_finalize(astreamer *streamer)
     201              : {
     202            3 :     astreamer_finalize(streamer->bbs_next);
     203            3 : }
     204              : 
     205              : /*
     206              :  * Free memory associated with this astreamer.
     207              :  */
     208              : static void
     209            3 : astreamer_recovery_injector_free(astreamer *streamer)
     210              : {
     211            3 :     astreamer_free(streamer->bbs_next);
     212            3 :     pfree(streamer);
     213            3 : }
     214              : 
     215              : /*
     216              :  * Inject a member into the archive with specified contents.
     217              :  */
     218              : void
     219            3 : astreamer_inject_file(astreamer *streamer, char *pathname, char *data,
     220              :                       int len)
     221              : {
     222              :     astreamer_member member;
     223              : 
     224            3 :     strlcpy(member.pathname, pathname, MAXPGPATH);
     225            3 :     member.size = len;
     226            3 :     member.mode = pg_file_create_mode;
     227            3 :     member.is_directory = false;
     228            3 :     member.is_link = false;
     229            3 :     member.linktarget[0] = '\0';
     230              : 
     231              :     /*
     232              :      * There seems to be no principled argument for these values, but they are
     233              :      * what PostgreSQL has historically used.
     234              :      */
     235            3 :     member.uid = 04000;
     236            3 :     member.gid = 02000;
     237              : 
     238              :     /*
     239              :      * We don't know here how to generate valid member headers and trailers
     240              :      * for the archiving format in use, so if those are needed, some successor
     241              :      * astreamer will have to generate them using the data from 'member'.
     242              :      */
     243            3 :     astreamer_content(streamer, &member, NULL, 0,
     244              :                       ASTREAMER_MEMBER_HEADER);
     245            3 :     astreamer_content(streamer, &member, data, len,
     246              :                       ASTREAMER_MEMBER_CONTENTS);
     247            3 :     astreamer_content(streamer, &member, NULL, 0,
     248              :                       ASTREAMER_MEMBER_TRAILER);
     249            3 : }
        

Generated by: LCOV version 2.0-1