LCOV - code coverage report
Current view: top level - src/bin/pg_basebackup - astreamer_inject.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 82.7 % 75 62
Test Date: 2026-07-08 06:15:42 Functions: 100.0 % 5 5
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 66.7 % 21 14

             Branch data     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                 :           4 : astreamer_recovery_injector_new(astreamer *next,
      66                 :             :                                 bool is_recovery_guc_supported,
      67                 :             :                                 PQExpBuffer recoveryconfcontents)
      68                 :             : {
      69                 :             :     astreamer_recovery_injector *streamer;
      70                 :             : 
      71                 :           4 :     streamer = palloc0_object(astreamer_recovery_injector);
      72                 :           4 :     *((const astreamer_ops **) &streamer->base.bbs_ops) =
      73                 :             :         &astreamer_recovery_injector_ops;
      74                 :           4 :     streamer->base.bbs_next = next;
      75                 :           4 :     streamer->is_recovery_guc_supported = is_recovery_guc_supported;
      76                 :           4 :     streamer->recoveryconfcontents = recoveryconfcontents;
      77                 :             : 
      78                 :           4 :     return &streamer->base;
      79                 :             : }
      80                 :             : 
      81                 :             : /*
      82                 :             :  * Handle each chunk of tar content while injecting recovery configuration.
      83                 :             :  */
      84                 :             : static void
      85                 :       13444 : 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                 :       13444 :     mystreamer = (astreamer_recovery_injector *) streamer;
      93                 :             :     Assert(member != NULL || context == ASTREAMER_ARCHIVE_TRAILER);
      94                 :             : 
      95   [ +  +  +  +  :       13444 :     switch (context)
                      - ]
      96                 :             :     {
      97                 :        4231 :         case ASTREAMER_MEMBER_HEADER:
      98                 :             :             /* Must copy provided data so we have the option to modify it. */
      99                 :        4231 :             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         [ +  - ]:        4231 :             if (mystreamer->is_recovery_guc_supported)
     106                 :             :             {
     107                 :        4231 :                 mystreamer->skip_file =
     108                 :        4231 :                     (strcmp(member->pathname, "standby.signal") == 0);
     109                 :        4231 :                 mystreamer->is_postgresql_auto_conf =
     110                 :        4231 :                     (strcmp(member->pathname, "postgresql.auto.conf") == 0);
     111         [ +  + ]:        4231 :                 if (mystreamer->is_postgresql_auto_conf)
     112                 :             :                 {
     113                 :             :                     /* Remember we saw it so we don't add it again. */
     114                 :           4 :                     mystreamer->found_postgresql_auto_conf = true;
     115                 :             : 
     116                 :             :                     /* Increment length by data to be injected. */
     117                 :           4 :                     mystreamer->member.size +=
     118                 :           4 :                         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                 :           4 :                     data = NULL;
     126                 :           4 :                     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         [ -  + ]:        4231 :             if (mystreamer->skip_file)
     135                 :           0 :                 return;
     136                 :        4231 :             break;
     137                 :             : 
     138                 :        4978 :         case ASTREAMER_MEMBER_CONTENTS:
     139                 :             :             /* Do not forward if the file is to be skipped. */
     140         [ -  + ]:        4978 :             if (mystreamer->skip_file)
     141                 :           0 :                 return;
     142                 :        4978 :             break;
     143                 :             : 
     144                 :        4231 :         case ASTREAMER_MEMBER_TRAILER:
     145                 :             :             /* Do not forward it the file is to be skipped. */
     146         [ -  + ]:        4231 :             if (mystreamer->skip_file)
     147                 :           0 :                 return;
     148                 :             : 
     149                 :             :             /* Append provided content to whatever we already sent. */
     150         [ +  + ]:        4231 :             if (mystreamer->is_postgresql_auto_conf)
     151                 :           4 :                 astreamer_content(mystreamer->base.bbs_next, member,
     152                 :           4 :                                   mystreamer->recoveryconfcontents->data,
     153                 :           4 :                                   mystreamer->recoveryconfcontents->len,
     154                 :             :                                   ASTREAMER_MEMBER_CONTENTS);
     155                 :        4231 :             break;
     156                 :             : 
     157                 :           4 :         case ASTREAMER_ARCHIVE_TRAILER:
     158         [ +  - ]:           4 :             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         [ -  + ]:           4 :                 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                 :           4 :                 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                 :           4 :             break;
     186                 :             : 
     187                 :           0 :         default:
     188                 :             :             /* Shouldn't happen. */
     189                 :           0 :             pg_fatal("unexpected state while injecting recovery settings");
     190                 :             :     }
     191                 :             : 
     192                 :       13444 :     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                 :           4 : astreamer_recovery_injector_finalize(astreamer *streamer)
     201                 :             : {
     202                 :           4 :     astreamer_finalize(streamer->bbs_next);
     203                 :           4 : }
     204                 :             : 
     205                 :             : /*
     206                 :             :  * Free memory associated with this astreamer.
     207                 :             :  */
     208                 :             : static void
     209                 :           4 : astreamer_recovery_injector_free(astreamer *streamer)
     210                 :             : {
     211                 :           4 :     astreamer_free(streamer->bbs_next);
     212                 :           4 :     pfree(streamer);
     213                 :           4 : }
     214                 :             : 
     215                 :             : /*
     216                 :             :  * Inject a member into the archive with specified contents.
     217                 :             :  */
     218                 :             : void
     219                 :           4 : astreamer_inject_file(astreamer *streamer, char *pathname, char *data,
     220                 :             :                       int len)
     221                 :             : {
     222                 :             :     astreamer_member member;
     223                 :             : 
     224                 :           4 :     strlcpy(member.pathname, pathname, MAXPGPATH);
     225                 :           4 :     member.size = len;
     226                 :           4 :     member.mode = pg_file_create_mode;
     227                 :           4 :     member.is_regular = true;
     228                 :           4 :     member.is_directory = false;
     229                 :           4 :     member.is_symlink = false;
     230                 :           4 :     member.linktarget[0] = '\0';
     231                 :             : 
     232                 :             :     /*
     233                 :             :      * There seems to be no principled argument for these values, but they are
     234                 :             :      * what PostgreSQL has historically used.
     235                 :             :      */
     236                 :           4 :     member.uid = 04000;
     237                 :           4 :     member.gid = 02000;
     238                 :             : 
     239                 :             :     /*
     240                 :             :      * We don't know here how to generate valid member headers and trailers
     241                 :             :      * for the archiving format in use, so if those are needed, some successor
     242                 :             :      * astreamer will have to generate them using the data from 'member'.
     243                 :             :      */
     244                 :           4 :     astreamer_content(streamer, &member, NULL, 0,
     245                 :             :                       ASTREAMER_MEMBER_HEADER);
     246                 :           4 :     astreamer_content(streamer, &member, data, len,
     247                 :             :                       ASTREAMER_MEMBER_CONTENTS);
     248                 :           4 :     astreamer_content(streamer, &member, NULL, 0,
     249                 :             :                       ASTREAMER_MEMBER_TRAILER);
     250                 :           4 : }
        

Generated by: LCOV version 2.0-1