1 /*-------------------------------------------------------------------------
5 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
8 * src/bin/pg_basebackup/astreamer_inject.c
9 *-------------------------------------------------------------------------
12 #include "postgres_fe.h"
14 #include "astreamer_inject.h"
15 #include "common/file_perm.h"
16 #include "common/logging.h"
18 typedef struct astreamer_recovery_injector
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
;
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
);
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
43 * Create a astreamer that can edit recoverydata into an archive stream.
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.
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.
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
65 astreamer_recovery_injector_new(astreamer
*next
,
66 bool is_recovery_guc_supported
,
67 PQExpBuffer recoveryconfcontents
)
69 astreamer_recovery_injector
*streamer
;
71 streamer
= palloc0(sizeof(astreamer_recovery_injector
));
72 *((const astreamer_ops
**) &streamer
->base
.bbs_ops
) =
73 &astreamer_recovery_injector_ops
;
74 streamer
->base
.bbs_next
= next
;
75 streamer
->is_recovery_guc_supported
= is_recovery_guc_supported
;
76 streamer
->recoveryconfcontents
= recoveryconfcontents
;
78 return &streamer
->base
;
82 * Handle each chunk of tar content while injecting recovery configuration.
85 astreamer_recovery_injector_content(astreamer
*streamer
,
86 astreamer_member
*member
,
87 const char *data
, int len
,
88 astreamer_archive_context context
)
90 astreamer_recovery_injector
*mystreamer
;
92 mystreamer
= (astreamer_recovery_injector
*) streamer
;
93 Assert(member
!= NULL
|| context
== ASTREAMER_ARCHIVE_TRAILER
);
97 case ASTREAMER_MEMBER_HEADER
:
98 /* Must copy provided data so we have the option to modify it. */
99 memcpy(&mystreamer
->member
, member
, sizeof(astreamer_member
));
102 * On v12+, skip standby.signal and edit postgresql.auto.conf; on
103 * older versions, skip recovery.conf.
105 if (mystreamer
->is_recovery_guc_supported
)
107 mystreamer
->skip_file
=
108 (strcmp(member
->pathname
, "standby.signal") == 0);
109 mystreamer
->is_postgresql_auto_conf
=
110 (strcmp(member
->pathname
, "postgresql.auto.conf") == 0);
111 if (mystreamer
->is_postgresql_auto_conf
)
113 /* Remember we saw it so we don't add it again. */
114 mystreamer
->found_postgresql_auto_conf
= true;
116 /* Increment length by data to be injected. */
117 mystreamer
->member
.size
+=
118 mystreamer
->recoveryconfcontents
->len
;
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.
130 mystreamer
->skip_file
=
131 (strcmp(member
->pathname
, "recovery.conf") == 0);
133 /* Do not forward if the file is to be skipped. */
134 if (mystreamer
->skip_file
)
138 case ASTREAMER_MEMBER_CONTENTS
:
139 /* Do not forward if the file is to be skipped. */
140 if (mystreamer
->skip_file
)
144 case ASTREAMER_MEMBER_TRAILER
:
145 /* Do not forward it the file is to be skipped. */
146 if (mystreamer
->skip_file
)
149 /* Append provided content to whatever we already sent. */
150 if (mystreamer
->is_postgresql_auto_conf
)
151 astreamer_content(mystreamer
->base
.bbs_next
, member
,
152 mystreamer
->recoveryconfcontents
->data
,
153 mystreamer
->recoveryconfcontents
->len
,
154 ASTREAMER_MEMBER_CONTENTS
);
157 case ASTREAMER_ARCHIVE_TRAILER
:
158 if (mystreamer
->is_recovery_guc_supported
)
161 * If we didn't already find (and thus modify)
162 * postgresql.auto.conf, inject it as an additional archive
165 if (!mystreamer
->found_postgresql_auto_conf
)
166 astreamer_inject_file(mystreamer
->base
.bbs_next
,
167 "postgresql.auto.conf",
168 mystreamer
->recoveryconfcontents
->data
,
169 mystreamer
->recoveryconfcontents
->len
);
171 /* Inject empty standby.signal file. */
172 astreamer_inject_file(mystreamer
->base
.bbs_next
,
173 "standby.signal", "", 0);
177 /* Inject recovery.conf file with specified contents. */
178 astreamer_inject_file(mystreamer
->base
.bbs_next
,
180 mystreamer
->recoveryconfcontents
->data
,
181 mystreamer
->recoveryconfcontents
->len
);
184 /* Nothing to do here. */
188 /* Shouldn't happen. */
189 pg_fatal("unexpected state while injecting recovery settings");
192 astreamer_content(mystreamer
->base
.bbs_next
, &mystreamer
->member
,
197 * End-of-stream processing for this astreamer.
200 astreamer_recovery_injector_finalize(astreamer
*streamer
)
202 astreamer_finalize(streamer
->bbs_next
);
206 * Free memory associated with this astreamer.
209 astreamer_recovery_injector_free(astreamer
*streamer
)
211 astreamer_free(streamer
->bbs_next
);
216 * Inject a member into the archive with specified contents.
219 astreamer_inject_file(astreamer
*streamer
, char *pathname
, char *data
,
222 astreamer_member member
;
224 strlcpy(member
.pathname
, pathname
, MAXPGPATH
);
226 member
.mode
= pg_file_create_mode
;
227 member
.is_directory
= false;
228 member
.is_link
= false;
229 member
.linktarget
[0] = '\0';
232 * There seems to be no principled argument for these values, but they are
233 * what PostgreSQL has historically used.
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'.
243 astreamer_content(streamer
, &member
, NULL
, 0,
244 ASTREAMER_MEMBER_HEADER
);
245 astreamer_content(streamer
, &member
, data
, len
,
246 ASTREAMER_MEMBER_CONTENTS
);
247 astreamer_content(streamer
, &member
, NULL
, 0,
248 ASTREAMER_MEMBER_TRAILER
);