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 : }
|