Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * bbstreamer_file.c
4 : : *
5 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
6 : : *
7 : : * IDENTIFICATION
8 : : * src/bin/pg_basebackup/bbstreamer_file.c
9 : : *-------------------------------------------------------------------------
10 : : */
11 : :
12 : : #include "postgres_fe.h"
13 : :
14 : : #include <unistd.h>
15 : :
16 : : #include "bbstreamer.h"
17 : : #include "common/file_perm.h"
18 : : #include "common/logging.h"
19 : : #include "common/string.h"
20 : :
21 : : typedef struct bbstreamer_plain_writer
22 : : {
23 : : bbstreamer base;
24 : : char *pathname;
25 : : FILE *file;
26 : : bool should_close_file;
27 : : } bbstreamer_plain_writer;
28 : :
29 : : typedef struct bbstreamer_extractor
30 : : {
31 : : bbstreamer base;
32 : : char *basepath;
33 : : const char *(*link_map) (const char *);
34 : : void (*report_output_file) (const char *);
35 : : char filename[MAXPGPATH];
36 : : FILE *file;
37 : : } bbstreamer_extractor;
38 : :
39 : : static void bbstreamer_plain_writer_content(bbstreamer *streamer,
40 : : bbstreamer_member *member,
41 : : const char *data, int len,
42 : : bbstreamer_archive_context context);
43 : : static void bbstreamer_plain_writer_finalize(bbstreamer *streamer);
44 : : static void bbstreamer_plain_writer_free(bbstreamer *streamer);
45 : :
46 : : const bbstreamer_ops bbstreamer_plain_writer_ops = {
47 : : .content = bbstreamer_plain_writer_content,
48 : : .finalize = bbstreamer_plain_writer_finalize,
49 : : .free = bbstreamer_plain_writer_free
50 : : };
51 : :
52 : : static void bbstreamer_extractor_content(bbstreamer *streamer,
53 : : bbstreamer_member *member,
54 : : const char *data, int len,
55 : : bbstreamer_archive_context context);
56 : : static void bbstreamer_extractor_finalize(bbstreamer *streamer);
57 : : static void bbstreamer_extractor_free(bbstreamer *streamer);
58 : : static void extract_directory(const char *filename, mode_t mode);
59 : : static void extract_link(const char *filename, const char *linktarget);
60 : : static FILE *create_file_for_extract(const char *filename, mode_t mode);
61 : :
62 : : const bbstreamer_ops bbstreamer_extractor_ops = {
63 : : .content = bbstreamer_extractor_content,
64 : : .finalize = bbstreamer_extractor_finalize,
65 : : .free = bbstreamer_extractor_free
66 : : };
67 : :
68 : : /*
69 : : * Create a bbstreamer that just writes data to a file.
70 : : *
71 : : * The caller must specify a pathname and may specify a file. The pathname is
72 : : * used for error-reporting purposes either way. If file is NULL, the pathname
73 : : * also identifies the file to which the data should be written: it is opened
74 : : * for writing and closed when done. If file is not NULL, the data is written
75 : : * there.
76 : : */
77 : : bbstreamer *
891 rhaas@postgresql.org 78 :CBC 14 : bbstreamer_plain_writer_new(char *pathname, FILE *file)
79 : : {
80 : : bbstreamer_plain_writer *streamer;
81 : :
82 : 14 : streamer = palloc0(sizeof(bbstreamer_plain_writer));
83 : 14 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
84 : : &bbstreamer_plain_writer_ops;
85 : :
86 : 14 : streamer->pathname = pstrdup(pathname);
87 : 14 : streamer->file = file;
88 : :
89 [ + - ]: 14 : if (file == NULL)
90 : : {
91 : 14 : streamer->file = fopen(pathname, "wb");
92 [ - + ]: 14 : if (streamer->file == NULL)
737 tgl@sss.pgh.pa.us 93 :UBC 0 : pg_fatal("could not create file \"%s\": %m", pathname);
891 rhaas@postgresql.org 94 :CBC 14 : streamer->should_close_file = true;
95 : : }
96 : :
97 : 14 : return &streamer->base;
98 : : }
99 : :
100 : : /*
101 : : * Write archive content to file.
102 : : */
103 : : static void
104 : 15866 : bbstreamer_plain_writer_content(bbstreamer *streamer,
105 : : bbstreamer_member *member, const char *data,
106 : : int len, bbstreamer_archive_context context)
107 : : {
108 : : bbstreamer_plain_writer *mystreamer;
109 : :
110 : 15866 : mystreamer = (bbstreamer_plain_writer *) streamer;
111 : :
112 [ - + ]: 15866 : if (len == 0)
891 rhaas@postgresql.org 113 :UBC 0 : return;
114 : :
891 rhaas@postgresql.org 115 :CBC 15866 : errno = 0;
116 [ - + ]: 15866 : if (fwrite(data, len, 1, mystreamer->file) != 1)
117 : : {
118 : : /* if write didn't set errno, assume problem is no disk space */
891 rhaas@postgresql.org 119 [ # # ]:UBC 0 : if (errno == 0)
120 : 0 : errno = ENOSPC;
737 tgl@sss.pgh.pa.us 121 : 0 : pg_fatal("could not write to file \"%s\": %m",
122 : : mystreamer->pathname);
123 : : }
124 : : }
125 : :
126 : : /*
127 : : * End-of-archive processing when writing to a plain file consists of closing
128 : : * the file if we opened it, but not if the caller provided it.
129 : : */
130 : : static void
891 rhaas@postgresql.org 131 :CBC 14 : bbstreamer_plain_writer_finalize(bbstreamer *streamer)
132 : : {
133 : : bbstreamer_plain_writer *mystreamer;
134 : :
135 : 14 : mystreamer = (bbstreamer_plain_writer *) streamer;
136 : :
137 [ + - - + ]: 14 : if (mystreamer->should_close_file && fclose(mystreamer->file) != 0)
737 tgl@sss.pgh.pa.us 138 :UBC 0 : pg_fatal("could not close file \"%s\": %m",
139 : : mystreamer->pathname);
140 : :
891 rhaas@postgresql.org 141 :CBC 14 : mystreamer->file = NULL;
142 : 14 : mystreamer->should_close_file = false;
143 : 14 : }
144 : :
145 : : /*
146 : : * Free memory associated with this bbstreamer.
147 : : */
148 : : static void
149 : 14 : bbstreamer_plain_writer_free(bbstreamer *streamer)
150 : : {
151 : : bbstreamer_plain_writer *mystreamer;
152 : :
153 : 14 : mystreamer = (bbstreamer_plain_writer *) streamer;
154 : :
155 [ - + ]: 14 : Assert(!mystreamer->should_close_file);
156 [ - + ]: 14 : Assert(mystreamer->base.bbs_next == NULL);
157 : :
158 : 14 : pfree(mystreamer->pathname);
159 : 14 : pfree(mystreamer);
160 : 14 : }
161 : :
162 : : /*
163 : : * Create a bbstreamer that extracts an archive.
164 : : *
165 : : * All pathnames in the archive are interpreted relative to basepath.
166 : : *
167 : : * Unlike e.g. bbstreamer_plain_writer_new() we can't do anything useful here
168 : : * with untyped chunks; we need typed chunks which follow the rules described
169 : : * in bbstreamer.h. Assuming we have that, we don't need to worry about the
170 : : * original archive format; it's enough to just look at the member information
171 : : * provided and write to the corresponding file.
172 : : *
173 : : * 'link_map' is a function that will be applied to the target of any
174 : : * symbolic link, and which should return a replacement pathname to be used
175 : : * in its place. If NULL, the symbolic link target is used without
176 : : * modification.
177 : : *
178 : : * 'report_output_file' is a function that will be called each time we open a
179 : : * new output file. The pathname to that file is passed as an argument. If
180 : : * NULL, the call is skipped.
181 : : */
182 : : bbstreamer *
183 : 152 : bbstreamer_extractor_new(const char *basepath,
184 : : const char *(*link_map) (const char *),
185 : : void (*report_output_file) (const char *))
186 : : {
187 : : bbstreamer_extractor *streamer;
188 : :
189 : 152 : streamer = palloc0(sizeof(bbstreamer_extractor));
190 : 152 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
191 : : &bbstreamer_extractor_ops;
192 : 152 : streamer->basepath = pstrdup(basepath);
193 : 152 : streamer->link_map = link_map;
194 : 152 : streamer->report_output_file = report_output_file;
195 : :
196 : 152 : return &streamer->base;
197 : : }
198 : :
199 : : /*
200 : : * Extract archive contents to the filesystem.
201 : : */
202 : : static void
203 : 475038 : bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
204 : : const char *data, int len,
205 : : bbstreamer_archive_context context)
206 : : {
207 : 475038 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
208 : : int fnamelen;
209 : :
210 [ + + - + ]: 475038 : Assert(member != NULL || context == BBSTREAMER_ARCHIVE_TRAILER);
211 [ - + ]: 475038 : Assert(context != BBSTREAMER_UNKNOWN);
212 : :
213 [ + + + + : 475038 : switch (context)
- ]
214 : : {
215 : 125224 : case BBSTREAMER_MEMBER_HEADER:
216 [ - + ]: 125224 : Assert(mystreamer->file == NULL);
217 : :
218 : : /* Prepend basepath. */
219 : 125224 : snprintf(mystreamer->filename, sizeof(mystreamer->filename),
220 : 125224 : "%s/%s", mystreamer->basepath, member->pathname);
221 : :
222 : : /* Remove any trailing slash. */
223 : 125224 : fnamelen = strlen(mystreamer->filename);
224 [ + + ]: 125224 : if (mystreamer->filename[fnamelen - 1] == '/')
225 : 3251 : mystreamer->filename[fnamelen - 1] = '\0';
226 : :
227 : : /* Dispatch based on file type. */
228 [ + + ]: 125224 : if (member->is_directory)
229 : 3237 : extract_directory(mystreamer->filename, member->mode);
230 [ + + ]: 121987 : else if (member->is_link)
231 : : {
232 : 14 : const char *linktarget = member->linktarget;
233 : :
234 [ + - ]: 14 : if (mystreamer->link_map)
235 : 14 : linktarget = mystreamer->link_map(linktarget);
236 : 14 : extract_link(mystreamer->filename, linktarget);
237 : : }
238 : : else
239 : 121973 : mystreamer->file =
240 : 121973 : create_file_for_extract(mystreamer->filename,
241 : : member->mode);
242 : :
243 : : /* Report output file change. */
244 [ + - ]: 125224 : if (mystreamer->report_output_file)
245 : 125224 : mystreamer->report_output_file(mystreamer->filename);
246 : 125224 : break;
247 : :
248 : 224444 : case BBSTREAMER_MEMBER_CONTENTS:
249 [ - + ]: 224444 : if (mystreamer->file == NULL)
891 rhaas@postgresql.org 250 :UBC 0 : break;
251 : :
891 rhaas@postgresql.org 252 :CBC 224444 : errno = 0;
253 [ + + - + ]: 224444 : if (len > 0 && fwrite(data, len, 1, mystreamer->file) != 1)
254 : : {
255 : : /* if write didn't set errno, assume problem is no disk space */
891 rhaas@postgresql.org 256 [ # # ]:UBC 0 : if (errno == 0)
257 : 0 : errno = ENOSPC;
737 tgl@sss.pgh.pa.us 258 : 0 : pg_fatal("could not write to file \"%s\": %m",
259 : : mystreamer->filename);
260 : : }
891 rhaas@postgresql.org 261 :CBC 224444 : break;
262 : :
263 : 125221 : case BBSTREAMER_MEMBER_TRAILER:
264 [ + + ]: 125221 : if (mystreamer->file == NULL)
265 : 3251 : break;
266 : 121970 : fclose(mystreamer->file);
267 : 121970 : mystreamer->file = NULL;
268 : 121970 : break;
269 : :
270 : 149 : case BBSTREAMER_ARCHIVE_TRAILER:
271 : 149 : break;
272 : :
891 rhaas@postgresql.org 273 :UBC 0 : default:
274 : : /* Shouldn't happen. */
737 tgl@sss.pgh.pa.us 275 : 0 : pg_fatal("unexpected state while extracting archive");
276 : : }
891 rhaas@postgresql.org 277 :CBC 475038 : }
278 : :
279 : : /*
280 : : * Should we tolerate an already-existing directory?
281 : : *
282 : : * When streaming WAL, pg_wal (or pg_xlog for pre-9.6 clusters) will have been
283 : : * created by the wal receiver process. Also, when the WAL directory location
284 : : * was specified, pg_wal (or pg_xlog) has already been created as a symbolic
285 : : * link before starting the actual backup. So just ignore creation failures
286 : : * on related directories.
287 : : *
288 : : * If in-place tablespaces are used, pg_tblspc and subdirectories may already
289 : : * exist when we get here. So tolerate that case, too.
290 : : */
291 : : static bool
362 292 : 364 : should_allow_existing_directory(const char *pathname)
293 : : {
294 : 364 : const char *filename = last_dir_separator(pathname) + 1;
295 : :
296 [ + + ]: 364 : if (strcmp(filename, "pg_wal") == 0 ||
297 [ + - ]: 250 : strcmp(filename, "pg_xlog") == 0 ||
298 [ + + ]: 250 : strcmp(filename, "archive_status") == 0 ||
116 rhaas@postgresql.org 299 [ + + ]:GNC 136 : strcmp(filename, "summaries") == 0 ||
362 rhaas@postgresql.org 300 [ + + ]:CBC 22 : strcmp(filename, "pg_tblspc") == 0)
301 : 350 : return true;
302 : :
303 [ + - ]: 14 : if (strspn(filename, "0123456789") == strlen(filename))
304 : : {
305 : 14 : const char *pg_tblspc = strstr(pathname, "/pg_tblspc/");
306 : :
307 [ + - + - ]: 14 : return pg_tblspc != NULL && pg_tblspc + 11 == filename;
308 : : }
309 : :
362 rhaas@postgresql.org 310 :UBC 0 : return false;
311 : : }
312 : :
313 : : /*
314 : : * Create a directory.
315 : : */
316 : : static void
891 rhaas@postgresql.org 317 :CBC 3237 : extract_directory(const char *filename, mode_t mode)
318 : : {
362 319 [ + + ]: 3237 : if (mkdir(filename, pg_dir_create_mode) != 0 &&
320 [ + - - + ]: 364 : (errno != EEXIST || !should_allow_existing_directory(filename)))
362 rhaas@postgresql.org 321 :UBC 0 : pg_fatal("could not create directory \"%s\": %m",
322 : : filename);
323 : :
324 : : #ifndef WIN32
891 rhaas@postgresql.org 325 [ - + ]:CBC 3237 : if (chmod(filename, mode))
737 tgl@sss.pgh.pa.us 326 :UBC 0 : pg_fatal("could not set permissions on directory \"%s\": %m",
327 : : filename);
328 : : #endif
891 rhaas@postgresql.org 329 :CBC 3237 : }
330 : :
331 : : /*
332 : : * Create a symbolic link.
333 : : *
334 : : * It's most likely a link in pg_tblspc directory, to the location of a
335 : : * tablespace. Apply any tablespace mapping given on the command line
336 : : * (--tablespace-mapping). (We blindly apply the mapping without checking that
337 : : * the link really is inside pg_tblspc. We don't expect there to be other
338 : : * symlinks in a data directory, but if there are, you can call it an
339 : : * undocumented feature that you can map them too.)
340 : : */
341 : : static void
342 : 14 : extract_link(const char *filename, const char *linktarget)
343 : : {
344 [ - + ]: 14 : if (symlink(linktarget, filename) != 0)
737 tgl@sss.pgh.pa.us 345 :UBC 0 : pg_fatal("could not create symbolic link from \"%s\" to \"%s\": %m",
346 : : filename, linktarget);
891 rhaas@postgresql.org 347 :CBC 14 : }
348 : :
349 : : /*
350 : : * Create a regular file.
351 : : *
352 : : * Return the resulting handle so we can write the content to the file.
353 : : */
354 : : static FILE *
355 : 121973 : create_file_for_extract(const char *filename, mode_t mode)
356 : : {
357 : : FILE *file;
358 : :
359 : 121973 : file = fopen(filename, "wb");
360 [ - + ]: 121973 : if (file == NULL)
737 tgl@sss.pgh.pa.us 361 :UBC 0 : pg_fatal("could not create file \"%s\": %m", filename);
362 : :
363 : : #ifndef WIN32
891 rhaas@postgresql.org 364 [ - + ]:CBC 121973 : if (chmod(filename, mode))
737 tgl@sss.pgh.pa.us 365 :UBC 0 : pg_fatal("could not set permissions on file \"%s\": %m",
366 : : filename);
367 : : #endif
368 : :
891 rhaas@postgresql.org 369 :CBC 121973 : return file;
370 : : }
371 : :
372 : : /*
373 : : * End-of-stream processing for extracting an archive.
374 : : *
375 : : * There's nothing to do here but sanity checking.
376 : : */
377 : : static void
378 : 149 : bbstreamer_extractor_finalize(bbstreamer *streamer)
379 : : {
890 tomas.vondra@postgre 380 : 149 : bbstreamer_extractor *mystreamer PG_USED_FOR_ASSERTS_ONLY
381 : : = (bbstreamer_extractor *) streamer;
382 : :
891 rhaas@postgresql.org 383 [ - + ]: 149 : Assert(mystreamer->file == NULL);
384 : 149 : }
385 : :
386 : : /*
387 : : * Free memory.
388 : : */
389 : : static void
390 : 149 : bbstreamer_extractor_free(bbstreamer *streamer)
391 : : {
392 : 149 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
393 : :
394 : 149 : pfree(mystreamer->basepath);
395 : 149 : pfree(mystreamer);
396 : 149 : }
|