Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * astreamer_inject.c
4 : : *
5 : : * Portions Copyright (c) 1996-2025, 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 *
397 rhaas@postgresql.org 65 :CBC 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(sizeof(astreamer_recovery_injector));
72 : 3 : *((const astreamer_ops **) &streamer->base.bbs_ops) =
73 : : &astreamer_recovery_injector_ops;
1401 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
397 85 : 9569 : 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 : 9569 : mystreamer = (astreamer_recovery_injector *) streamer;
93 [ + + - + ]: 9569 : Assert(member != NULL || context == ASTREAMER_ARCHIVE_TRAILER);
94 : :
1401 95 [ + + + + : 9569 : switch (context)
- ]
96 : : {
397 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 : : */
1401 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
1401 rhaas@postgresql.org 130 :UBC 0 : mystreamer->skip_file =
131 : 0 : (strcmp(member->pathname, "recovery.conf") == 0);
132 : :
133 : : /* Do not forward if the file is to be skipped. */
1401 rhaas@postgresql.org 134 [ - + ]:CBC 2988 : if (mystreamer->skip_file)
1401 rhaas@postgresql.org 135 :UBC 0 : return;
1401 rhaas@postgresql.org 136 :CBC 2988 : break;
137 : :
397 138 : 3590 : case ASTREAMER_MEMBER_CONTENTS:
139 : : /* Do not forward if the file is to be skipped. */
1401 140 [ - + ]: 3590 : if (mystreamer->skip_file)
1401 rhaas@postgresql.org 141 :UBC 0 : return;
1401 rhaas@postgresql.org 142 :CBC 3590 : break;
143 : :
397 144 : 2988 : case ASTREAMER_MEMBER_TRAILER:
145 : : /* Do not forward it the file is to be skipped. */
1401 146 [ - + ]: 2988 : if (mystreamer->skip_file)
1401 rhaas@postgresql.org 147 :UBC 0 : return;
148 : :
149 : : /* Append provided content to whatever we already sent. */
1401 rhaas@postgresql.org 150 [ + + ]:CBC 2988 : if (mystreamer->is_postgresql_auto_conf)
397 151 : 3 : astreamer_content(mystreamer->base.bbs_next, member,
152 : 3 : mystreamer->recoveryconfcontents->data,
153 : 3 : mystreamer->recoveryconfcontents->len,
154 : : ASTREAMER_MEMBER_CONTENTS);
1401 155 : 2988 : break;
156 : :
397 157 : 3 : case ASTREAMER_ARCHIVE_TRAILER:
1401 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)
397 rhaas@postgresql.org 166 :UBC 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. */
397 rhaas@postgresql.org 172 :CBC 3 : astreamer_inject_file(mystreamer->base.bbs_next,
173 : : "standby.signal", "", 0);
174 : : }
175 : : else
176 : : {
177 : : /* Inject recovery.conf file with specified contents. */
397 rhaas@postgresql.org 178 :UBC 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. */
1401 rhaas@postgresql.org 185 :CBC 3 : break;
186 : :
1401 rhaas@postgresql.org 187 :UBC 0 : default:
188 : : /* Shouldn't happen. */
1247 tgl@sss.pgh.pa.us 189 : 0 : pg_fatal("unexpected state while injecting recovery settings");
190 : : }
191 : :
397 rhaas@postgresql.org 192 :CBC 9569 : 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);
1401 203 : 3 : }
204 : :
205 : : /*
206 : : * Free memory associated with this astreamer.
207 : : */
208 : : static void
397 209 : 3 : astreamer_recovery_injector_free(astreamer *streamer)
210 : : {
211 : 3 : astreamer_free(streamer->bbs_next);
1401 212 : 3 : pfree(streamer);
213 : 3 : }
214 : :
215 : : /*
216 : : * Inject a member into the archive with specified contents.
217 : : */
218 : : void
397 219 : 3 : astreamer_inject_file(astreamer *streamer, char *pathname, char *data,
220 : : int len)
221 : : {
222 : : astreamer_member member;
223 : :
1401 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 : : */
397 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);
1401 249 : 3 : }
|