LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - astreamer_inject.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 80 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 5 0
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            0 : astreamer_recovery_injector_new(astreamer *next,
      66              :                                                                 bool is_recovery_guc_supported,
      67              :                                                                 PQExpBuffer recoveryconfcontents)
      68              : {
      69            0 :         astreamer_recovery_injector *streamer;
      70              : 
      71            0 :         streamer = palloc0_object(astreamer_recovery_injector);
      72            0 :         *((const astreamer_ops **) &streamer->base.bbs_ops) =
      73              :                 &astreamer_recovery_injector_ops;
      74            0 :         streamer->base.bbs_next = next;
      75            0 :         streamer->is_recovery_guc_supported = is_recovery_guc_supported;
      76            0 :         streamer->recoveryconfcontents = recoveryconfcontents;
      77              : 
      78            0 :         return &streamer->base;
      79            0 : }
      80              : 
      81              : /*
      82              :  * Handle each chunk of tar content while injecting recovery configuration.
      83              :  */
      84              : static void
      85            0 : astreamer_recovery_injector_content(astreamer *streamer,
      86              :                                                                         astreamer_member *member,
      87              :                                                                         const char *data, int len,
      88              :                                                                         astreamer_archive_context context)
      89              : {
      90            0 :         astreamer_recovery_injector *mystreamer;
      91              : 
      92            0 :         mystreamer = (astreamer_recovery_injector *) streamer;
      93            0 :         Assert(member != NULL || context == ASTREAMER_ARCHIVE_TRAILER);
      94              : 
      95            0 :         switch (context)
      96              :         {
      97              :                 case ASTREAMER_MEMBER_HEADER:
      98              :                         /* Must copy provided data so we have the option to modify it. */
      99            0 :                         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            0 :                         if (mystreamer->is_recovery_guc_supported)
     106              :                         {
     107            0 :                                 mystreamer->skip_file =
     108            0 :                                         (strcmp(member->pathname, "standby.signal") == 0);
     109            0 :                                 mystreamer->is_postgresql_auto_conf =
     110            0 :                                         (strcmp(member->pathname, "postgresql.auto.conf") == 0);
     111            0 :                                 if (mystreamer->is_postgresql_auto_conf)
     112              :                                 {
     113              :                                         /* Remember we saw it so we don't add it again. */
     114            0 :                                         mystreamer->found_postgresql_auto_conf = true;
     115              : 
     116              :                                         /* Increment length by data to be injected. */
     117            0 :                                         mystreamer->member.size +=
     118            0 :                                                 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            0 :                                         data = NULL;
     126            0 :                                         len = 0;
     127            0 :                                 }
     128            0 :                         }
     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            0 :                         if (mystreamer->skip_file)
     135            0 :                                 return;
     136            0 :                         break;
     137              : 
     138              :                 case ASTREAMER_MEMBER_CONTENTS:
     139              :                         /* Do not forward if the file is to be skipped. */
     140            0 :                         if (mystreamer->skip_file)
     141            0 :                                 return;
     142            0 :                         break;
     143              : 
     144              :                 case ASTREAMER_MEMBER_TRAILER:
     145              :                         /* Do not forward it the file is to be skipped. */
     146            0 :                         if (mystreamer->skip_file)
     147            0 :                                 return;
     148              : 
     149              :                         /* Append provided content to whatever we already sent. */
     150            0 :                         if (mystreamer->is_postgresql_auto_conf)
     151            0 :                                 astreamer_content(mystreamer->base.bbs_next, member,
     152            0 :                                                                   mystreamer->recoveryconfcontents->data,
     153            0 :                                                                   mystreamer->recoveryconfcontents->len,
     154              :                                                                   ASTREAMER_MEMBER_CONTENTS);
     155            0 :                         break;
     156              : 
     157              :                 case ASTREAMER_ARCHIVE_TRAILER:
     158            0 :                         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            0 :                                 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            0 :                                 astreamer_inject_file(mystreamer->base.bbs_next,
     173              :                                                                           "standby.signal", "", 0);
     174            0 :                         }
     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            0 :                         break;
     186              : 
     187              :                 default:
     188              :                         /* Shouldn't happen. */
     189            0 :                         pg_fatal("unexpected state while injecting recovery settings");
     190            0 :         }
     191              : 
     192            0 :         astreamer_content(mystreamer->base.bbs_next, &mystreamer->member,
     193            0 :                                           data, len, context);
     194            0 : }
     195              : 
     196              : /*
     197              :  * End-of-stream processing for this astreamer.
     198              :  */
     199              : static void
     200            0 : astreamer_recovery_injector_finalize(astreamer *streamer)
     201              : {
     202            0 :         astreamer_finalize(streamer->bbs_next);
     203            0 : }
     204              : 
     205              : /*
     206              :  * Free memory associated with this astreamer.
     207              :  */
     208              : static void
     209            0 : astreamer_recovery_injector_free(astreamer *streamer)
     210              : {
     211            0 :         astreamer_free(streamer->bbs_next);
     212            0 :         pfree(streamer);
     213            0 : }
     214              : 
     215              : /*
     216              :  * Inject a member into the archive with specified contents.
     217              :  */
     218              : void
     219            0 : astreamer_inject_file(astreamer *streamer, char *pathname, char *data,
     220              :                                           int len)
     221              : {
     222            0 :         astreamer_member member;
     223              : 
     224            0 :         strlcpy(member.pathname, pathname, MAXPGPATH);
     225            0 :         member.size = len;
     226            0 :         member.mode = pg_file_create_mode;
     227            0 :         member.is_directory = false;
     228            0 :         member.is_link = false;
     229            0 :         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            0 :         member.uid = 04000;
     236            0 :         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            0 :         astreamer_content(streamer, &member, NULL, 0,
     244              :                                           ASTREAMER_MEMBER_HEADER);
     245            0 :         astreamer_content(streamer, &member, data, len,
     246              :                                           ASTREAMER_MEMBER_CONTENTS);
     247            0 :         astreamer_content(streamer, &member, NULL, 0,
     248              :                                           ASTREAMER_MEMBER_TRAILER);
     249            0 : }
        

Generated by: LCOV version 2.3.2-1