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

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * astreamer_inject.c
       4             :  *
       5             :  * Portions Copyright (c) 1996-2024, 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           6 : astreamer_recovery_injector_new(astreamer *next,
      66             :                                 bool is_recovery_guc_supported,
      67             :                                 PQExpBuffer recoveryconfcontents)
      68             : {
      69             :     astreamer_recovery_injector *streamer;
      70             : 
      71           6 :     streamer = palloc0(sizeof(astreamer_recovery_injector));
      72           6 :     *((const astreamer_ops **) &streamer->base.bbs_ops) =
      73             :         &astreamer_recovery_injector_ops;
      74           6 :     streamer->base.bbs_next = next;
      75           6 :     streamer->is_recovery_guc_supported = is_recovery_guc_supported;
      76           6 :     streamer->recoveryconfcontents = recoveryconfcontents;
      77             : 
      78           6 :     return &streamer->base;
      79             : }
      80             : 
      81             : /*
      82             :  * Handle each chunk of tar content while injecting recovery configuration.
      83             :  */
      84             : static void
      85       19056 : 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       19056 :     mystreamer = (astreamer_recovery_injector *) streamer;
      93             :     Assert(member != NULL || context == ASTREAMER_ARCHIVE_TRAILER);
      94             : 
      95       19056 :     switch (context)
      96             :     {
      97        5988 :         case ASTREAMER_MEMBER_HEADER:
      98             :             /* Must copy provided data so we have the option to modify it. */
      99        5988 :             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        5988 :             if (mystreamer->is_recovery_guc_supported)
     106             :             {
     107        5988 :                 mystreamer->skip_file =
     108        5988 :                     (strcmp(member->pathname, "standby.signal") == 0);
     109        5988 :                 mystreamer->is_postgresql_auto_conf =
     110        5988 :                     (strcmp(member->pathname, "postgresql.auto.conf") == 0);
     111        5988 :                 if (mystreamer->is_postgresql_auto_conf)
     112             :                 {
     113             :                     /* Remember we saw it so we don't add it again. */
     114           6 :                     mystreamer->found_postgresql_auto_conf = true;
     115             : 
     116             :                     /* Increment length by data to be injected. */
     117           6 :                     mystreamer->member.size +=
     118           6 :                         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           6 :                     data = NULL;
     126           6 :                     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        5988 :             if (mystreamer->skip_file)
     135           0 :                 return;
     136        5988 :             break;
     137             : 
     138        7074 :         case ASTREAMER_MEMBER_CONTENTS:
     139             :             /* Do not forward if the file is to be skipped. */
     140        7074 :             if (mystreamer->skip_file)
     141           0 :                 return;
     142        7074 :             break;
     143             : 
     144        5988 :         case ASTREAMER_MEMBER_TRAILER:
     145             :             /* Do not forward it the file is to be skipped. */
     146        5988 :             if (mystreamer->skip_file)
     147           0 :                 return;
     148             : 
     149             :             /* Append provided content to whatever we already sent. */
     150        5988 :             if (mystreamer->is_postgresql_auto_conf)
     151           6 :                 astreamer_content(mystreamer->base.bbs_next, member,
     152           6 :                                   mystreamer->recoveryconfcontents->data,
     153           6 :                                   mystreamer->recoveryconfcontents->len,
     154             :                                   ASTREAMER_MEMBER_CONTENTS);
     155        5988 :             break;
     156             : 
     157           6 :         case ASTREAMER_ARCHIVE_TRAILER:
     158           6 :             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           6 :                 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           6 :                 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           6 :             break;
     186             : 
     187           0 :         default:
     188             :             /* Shouldn't happen. */
     189           0 :             pg_fatal("unexpected state while injecting recovery settings");
     190             :     }
     191             : 
     192       19056 :     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           6 : astreamer_recovery_injector_finalize(astreamer *streamer)
     201             : {
     202           6 :     astreamer_finalize(streamer->bbs_next);
     203           6 : }
     204             : 
     205             : /*
     206             :  * Free memory associated with this astreamer.
     207             :  */
     208             : static void
     209           6 : astreamer_recovery_injector_free(astreamer *streamer)
     210             : {
     211           6 :     astreamer_free(streamer->bbs_next);
     212           6 :     pfree(streamer);
     213           6 : }
     214             : 
     215             : /*
     216             :  * Inject a member into the archive with specified contents.
     217             :  */
     218             : void
     219           6 : astreamer_inject_file(astreamer *streamer, char *pathname, char *data,
     220             :                       int len)
     221             : {
     222             :     astreamer_member member;
     223             : 
     224           6 :     strlcpy(member.pathname, pathname, MAXPGPATH);
     225           6 :     member.size = len;
     226           6 :     member.mode = pg_file_create_mode;
     227           6 :     member.is_directory = false;
     228           6 :     member.is_link = false;
     229           6 :     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           6 :     member.uid = 04000;
     236           6 :     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           6 :     astreamer_content(streamer, &member, NULL, 0,
     244             :                       ASTREAMER_MEMBER_HEADER);
     245           6 :     astreamer_content(streamer, &member, data, len,
     246             :                       ASTREAMER_MEMBER_CONTENTS);
     247           6 :     astreamer_content(streamer, &member, NULL, 0,
     248             :                       ASTREAMER_MEMBER_TRAILER);
     249           6 : }

Generated by: LCOV version 1.14