Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * bbstreamer_inject.c
4 : *
5 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
6 : *
7 : * IDENTIFICATION
8 : * src/bin/pg_basebackup/bbstreamer_inject.c
9 : *-------------------------------------------------------------------------
10 : */
11 :
12 : #include "postgres_fe.h"
13 :
14 : #include "bbstreamer.h"
15 : #include "common/file_perm.h"
16 : #include "common/logging.h"
17 :
18 : typedef struct bbstreamer_recovery_injector
19 : {
20 : bbstreamer 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 : bbstreamer_member member;
27 : } bbstreamer_recovery_injector;
28 :
29 : static void bbstreamer_recovery_injector_content(bbstreamer *streamer,
30 : bbstreamer_member *member,
31 : const char *data, int len,
32 : bbstreamer_archive_context context);
33 : static void bbstreamer_recovery_injector_finalize(bbstreamer *streamer);
34 : static void bbstreamer_recovery_injector_free(bbstreamer *streamer);
35 :
36 : const bbstreamer_ops bbstreamer_recovery_injector_ops = {
37 : .content = bbstreamer_recovery_injector_content,
38 : .finalize = bbstreamer_recovery_injector_finalize,
39 : .free = bbstreamer_recovery_injector_free
40 : };
41 :
42 : /*
43 : * Create a bbstreamer that can edit recoverydata into an archive stream.
44 : *
45 : * The input should be a series of typed chunks (not BBSTREAMER_UNKNOWN) as
46 : * per the conventions described in bbstreamer.h; the chunks forwarded to
47 : * the next bbstreamer will be similarly typed, but the
48 : * BBSTREAMER_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 : extern bbstreamer *
520 rhaas 65 CBC 2 : bbstreamer_recovery_injector_new(bbstreamer *next,
66 : bool is_recovery_guc_supported,
67 : PQExpBuffer recoveryconfcontents)
68 : {
69 : bbstreamer_recovery_injector *streamer;
70 :
71 2 : streamer = palloc0(sizeof(bbstreamer_recovery_injector));
72 2 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
73 : &bbstreamer_recovery_injector_ops;
74 2 : streamer->base.bbs_next = next;
75 2 : streamer->is_recovery_guc_supported = is_recovery_guc_supported;
76 2 : streamer->recoveryconfcontents = recoveryconfcontents;
77 :
78 2 : return &streamer->base;
79 : }
80 :
81 : /*
82 : * Handle each chunk of tar content while injecting recovery configuration.
83 : */
84 : static void
85 6340 : bbstreamer_recovery_injector_content(bbstreamer *streamer,
86 : bbstreamer_member *member,
87 : const char *data, int len,
88 : bbstreamer_archive_context context)
89 : {
90 : bbstreamer_recovery_injector *mystreamer;
91 :
92 6340 : mystreamer = (bbstreamer_recovery_injector *) streamer;
93 6340 : Assert(member != NULL || context == BBSTREAMER_ARCHIVE_TRAILER);
94 :
95 6340 : switch (context)
96 : {
97 1986 : case BBSTREAMER_MEMBER_HEADER:
98 : /* Must copy provided data so we have the option to modify it. */
99 1986 : memcpy(&mystreamer->member, member, sizeof(bbstreamer_member));
100 :
101 : /*
102 : * On v12+, skip standby.signal and edit postgresql.auto.conf; on
103 : * older versions, skip recovery.conf.
104 : */
105 1986 : if (mystreamer->is_recovery_guc_supported)
106 : {
107 1986 : mystreamer->skip_file =
108 1986 : (strcmp(member->pathname, "standby.signal") == 0);
109 1986 : mystreamer->is_postgresql_auto_conf =
110 1986 : (strcmp(member->pathname, "postgresql.auto.conf") == 0);
111 1986 : if (mystreamer->is_postgresql_auto_conf)
112 : {
113 : /* Remember we saw it so we don't add it again. */
114 2 : mystreamer->found_postgresql_auto_conf = true;
115 :
116 : /* Increment length by data to be injected. */
117 2 : mystreamer->member.size +=
118 2 : mystreamer->recoveryconfcontents->len;
119 :
120 : /*
121 : * Zap data and len because the archive header is no
122 : * longer valid; some subsequent bbstreamer must
123 : * regenerate it if it's necessary.
124 : */
125 2 : data = NULL;
126 2 : len = 0;
127 : }
128 : }
129 : else
520 rhaas 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. */
520 rhaas 134 CBC 1986 : if (mystreamer->skip_file)
520 rhaas 135 UBC 0 : return;
520 rhaas 136 CBC 1986 : break;
137 :
138 2366 : case BBSTREAMER_MEMBER_CONTENTS:
139 : /* Do not forward if the file is to be skipped. */
140 2366 : if (mystreamer->skip_file)
520 rhaas 141 UBC 0 : return;
520 rhaas 142 CBC 2366 : break;
143 :
144 1986 : case BBSTREAMER_MEMBER_TRAILER:
145 : /* Do not forward it the file is to be skipped. */
146 1986 : if (mystreamer->skip_file)
520 rhaas 147 UBC 0 : return;
148 :
149 : /* Append provided content to whatever we already sent. */
520 rhaas 150 CBC 1986 : if (mystreamer->is_postgresql_auto_conf)
151 2 : bbstreamer_content(mystreamer->base.bbs_next, member,
152 2 : mystreamer->recoveryconfcontents->data,
153 2 : mystreamer->recoveryconfcontents->len,
154 : BBSTREAMER_MEMBER_CONTENTS);
155 1986 : break;
156 :
157 2 : case BBSTREAMER_ARCHIVE_TRAILER:
158 2 : 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 2 : if (!mystreamer->found_postgresql_auto_conf)
520 rhaas 166 UBC 0 : bbstreamer_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. */
520 rhaas 172 CBC 2 : bbstreamer_inject_file(mystreamer->base.bbs_next,
173 : "standby.signal", "", 0);
174 : }
175 : else
176 : {
177 : /* Inject recovery.conf file with specified contents. */
520 rhaas 178 UBC 0 : bbstreamer_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. */
520 rhaas 185 CBC 2 : break;
186 :
520 rhaas 187 UBC 0 : default:
188 : /* Shouldn't happen. */
366 tgl 189 0 : pg_fatal("unexpected state while injecting recovery settings");
190 : }
191 :
520 rhaas 192 CBC 6340 : bbstreamer_content(mystreamer->base.bbs_next, &mystreamer->member,
193 : data, len, context);
194 : }
195 :
196 : /*
197 : * End-of-stream processing for this bbstreamer.
198 : */
199 : static void
200 2 : bbstreamer_recovery_injector_finalize(bbstreamer *streamer)
201 : {
202 2 : bbstreamer_finalize(streamer->bbs_next);
203 2 : }
204 :
205 : /*
206 : * Free memory associated with this bbstreamer.
207 : */
208 : static void
209 2 : bbstreamer_recovery_injector_free(bbstreamer *streamer)
210 : {
211 2 : bbstreamer_free(streamer->bbs_next);
212 2 : pfree(streamer);
213 2 : }
214 :
215 : /*
216 : * Inject a member into the archive with specified contents.
217 : */
218 : void
219 2 : bbstreamer_inject_file(bbstreamer *streamer, char *pathname, char *data,
220 : int len)
221 : {
222 : bbstreamer_member member;
223 :
224 2 : strlcpy(member.pathname, pathname, MAXPGPATH);
225 2 : member.size = len;
226 2 : member.mode = pg_file_create_mode;
227 2 : member.is_directory = false;
228 2 : member.is_link = false;
229 2 : 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 2 : member.uid = 04000;
236 2 : 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 : * bbstreamer will have to generate them using the data from 'member'.
242 : */
243 2 : bbstreamer_content(streamer, &member, NULL, 0,
244 : BBSTREAMER_MEMBER_HEADER);
245 2 : bbstreamer_content(streamer, &member, data, len,
246 : BBSTREAMER_MEMBER_CONTENTS);
247 2 : bbstreamer_content(streamer, &member, NULL, 0,
248 : BBSTREAMER_MEMBER_TRAILER);
249 2 : }
|