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