Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * bbstreamer_file.c
4 : *
5 : * Portions Copyright (c) 1996-2023, 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/logging.h"
18 : #include "common/file_perm.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 *
78 20 : bbstreamer_plain_writer_new(char *pathname, FILE *file)
79 : {
80 : bbstreamer_plain_writer *streamer;
81 :
82 20 : streamer = palloc0(sizeof(bbstreamer_plain_writer));
83 20 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
84 : &bbstreamer_plain_writer_ops;
85 :
86 20 : streamer->pathname = pstrdup(pathname);
87 20 : streamer->file = file;
88 :
89 20 : if (file == NULL)
90 : {
91 20 : streamer->file = fopen(pathname, "wb");
92 20 : if (streamer->file == NULL)
93 0 : pg_fatal("could not create file \"%s\": %m", pathname);
94 20 : streamer->should_close_file = true;
95 : }
96 :
97 20 : return &streamer->base;
98 : }
99 :
100 : /*
101 : * Write archive content to file.
102 : */
103 : static void
104 27228 : 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 27228 : mystreamer = (bbstreamer_plain_writer *) streamer;
111 :
112 27228 : if (len == 0)
113 0 : return;
114 :
115 27228 : errno = 0;
116 27228 : if (fwrite(data, len, 1, mystreamer->file) != 1)
117 : {
118 : /* if write didn't set errno, assume problem is no disk space */
119 0 : if (errno == 0)
120 0 : errno = ENOSPC;
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
131 20 : bbstreamer_plain_writer_finalize(bbstreamer *streamer)
132 : {
133 : bbstreamer_plain_writer *mystreamer;
134 :
135 20 : mystreamer = (bbstreamer_plain_writer *) streamer;
136 :
137 20 : if (mystreamer->should_close_file && fclose(mystreamer->file) != 0)
138 0 : pg_fatal("could not close file \"%s\": %m",
139 : mystreamer->pathname);
140 :
141 20 : mystreamer->file = NULL;
142 20 : mystreamer->should_close_file = false;
143 20 : }
144 :
145 : /*
146 : * Free memory associated with this bbstreamer.
147 : */
148 : static void
149 20 : bbstreamer_plain_writer_free(bbstreamer *streamer)
150 : {
151 : bbstreamer_plain_writer *mystreamer;
152 :
153 20 : mystreamer = (bbstreamer_plain_writer *) streamer;
154 :
155 : Assert(!mystreamer->should_close_file);
156 : Assert(mystreamer->base.bbs_next == NULL);
157 :
158 20 : pfree(mystreamer->pathname);
159 20 : pfree(mystreamer);
160 20 : }
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 252 : 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 252 : streamer = palloc0(sizeof(bbstreamer_extractor));
190 252 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
191 : &bbstreamer_extractor_ops;
192 252 : streamer->basepath = pstrdup(basepath);
193 252 : streamer->link_map = link_map;
194 252 : streamer->report_output_file = report_output_file;
195 :
196 252 : return &streamer->base;
197 : }
198 :
199 : /*
200 : * Extract archive contents to the filesystem.
201 : */
202 : static void
203 781296 : bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
204 : const char *data, int len,
205 : bbstreamer_archive_context context)
206 : {
207 781296 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
208 : int fnamelen;
209 :
210 : Assert(member != NULL || context == BBSTREAMER_ARCHIVE_TRAILER);
211 : Assert(context != BBSTREAMER_UNKNOWN);
212 :
213 781296 : switch (context)
214 : {
215 196630 : case BBSTREAMER_MEMBER_HEADER:
216 : Assert(mystreamer->file == NULL);
217 :
218 : /* Prepend basepath. */
219 196630 : snprintf(mystreamer->filename, sizeof(mystreamer->filename),
220 196630 : "%s/%s", mystreamer->basepath, member->pathname);
221 :
222 : /* Remove any trailing slash. */
223 196630 : fnamelen = strlen(mystreamer->filename);
224 196630 : if (mystreamer->filename[fnamelen - 1] == '/')
225 5060 : mystreamer->filename[fnamelen - 1] = '\0';
226 :
227 : /* Dispatch based on file type. */
228 196630 : if (member->is_directory)
229 5034 : extract_directory(mystreamer->filename, member->mode);
230 191596 : else if (member->is_link)
231 : {
232 26 : const char *linktarget = member->linktarget;
233 :
234 26 : if (mystreamer->link_map)
235 26 : linktarget = mystreamer->link_map(linktarget);
236 26 : extract_link(mystreamer->filename, linktarget);
237 : }
238 : else
239 191570 : mystreamer->file =
240 191570 : create_file_for_extract(mystreamer->filename,
241 : member->mode);
242 :
243 : /* Report output file change. */
244 196630 : if (mystreamer->report_output_file)
245 196630 : mystreamer->report_output_file(mystreamer->filename);
246 196630 : break;
247 :
248 387796 : case BBSTREAMER_MEMBER_CONTENTS:
249 387796 : if (mystreamer->file == NULL)
250 0 : break;
251 :
252 387796 : errno = 0;
253 387796 : if (len > 0 && fwrite(data, len, 1, mystreamer->file) != 1)
254 : {
255 : /* if write didn't set errno, assume problem is no disk space */
256 0 : if (errno == 0)
257 0 : errno = ENOSPC;
258 0 : pg_fatal("could not write to file \"%s\": %m",
259 : mystreamer->filename);
260 : }
261 387796 : break;
262 :
263 196624 : case BBSTREAMER_MEMBER_TRAILER:
264 196624 : if (mystreamer->file == NULL)
265 5060 : break;
266 191564 : fclose(mystreamer->file);
267 191564 : mystreamer->file = NULL;
268 191564 : break;
269 :
270 246 : case BBSTREAMER_ARCHIVE_TRAILER:
271 246 : break;
272 :
273 0 : default:
274 : /* Shouldn't happen. */
275 0 : pg_fatal("unexpected state while extracting archive");
276 : }
277 781296 : }
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
292 404 : should_allow_existing_directory(const char *pathname)
293 : {
294 404 : const char *filename = last_dir_separator(pathname) + 1;
295 :
296 404 : if (strcmp(filename, "pg_wal") == 0 ||
297 224 : strcmp(filename, "pg_xlog") == 0 ||
298 224 : strcmp(filename, "archive_status") == 0 ||
299 44 : strcmp(filename, "pg_tblspc") == 0)
300 376 : return true;
301 :
302 28 : if (strspn(filename, "0123456789") == strlen(filename))
303 : {
304 28 : const char *pg_tblspc = strstr(pathname, "/pg_tblspc/");
305 :
306 28 : return pg_tblspc != NULL && pg_tblspc + 11 == filename;
307 : }
308 :
309 0 : return false;
310 : }
311 :
312 : /*
313 : * Create a directory.
314 : */
315 : static void
316 5034 : extract_directory(const char *filename, mode_t mode)
317 : {
318 5034 : if (mkdir(filename, pg_dir_create_mode) != 0 &&
319 404 : (errno != EEXIST || !should_allow_existing_directory(filename)))
320 0 : pg_fatal("could not create directory \"%s\": %m",
321 : filename);
322 :
323 : #ifndef WIN32
324 5034 : if (chmod(filename, mode))
325 0 : pg_fatal("could not set permissions on directory \"%s\": %m",
326 : filename);
327 : #endif
328 5034 : }
329 :
330 : /*
331 : * Create a symbolic link.
332 : *
333 : * It's most likely a link in pg_tblspc directory, to the location of a
334 : * tablespace. Apply any tablespace mapping given on the command line
335 : * (--tablespace-mapping). (We blindly apply the mapping without checking that
336 : * the link really is inside pg_tblspc. We don't expect there to be other
337 : * symlinks in a data directory, but if there are, you can call it an
338 : * undocumented feature that you can map them too.)
339 : */
340 : static void
341 26 : extract_link(const char *filename, const char *linktarget)
342 : {
343 26 : if (symlink(linktarget, filename) != 0)
344 0 : pg_fatal("could not create symbolic link from \"%s\" to \"%s\": %m",
345 : filename, linktarget);
346 26 : }
347 :
348 : /*
349 : * Create a regular file.
350 : *
351 : * Return the resulting handle so we can write the content to the file.
352 : */
353 : static FILE *
354 191570 : create_file_for_extract(const char *filename, mode_t mode)
355 : {
356 : FILE *file;
357 :
358 191570 : file = fopen(filename, "wb");
359 191570 : if (file == NULL)
360 0 : pg_fatal("could not create file \"%s\": %m", filename);
361 :
362 : #ifndef WIN32
363 191570 : if (chmod(filename, mode))
364 0 : pg_fatal("could not set permissions on file \"%s\": %m",
365 : filename);
366 : #endif
367 :
368 191570 : return file;
369 : }
370 :
371 : /*
372 : * End-of-stream processing for extracting an archive.
373 : *
374 : * There's nothing to do here but sanity checking.
375 : */
376 : static void
377 246 : bbstreamer_extractor_finalize(bbstreamer *streamer)
378 : {
379 246 : bbstreamer_extractor *mystreamer PG_USED_FOR_ASSERTS_ONLY
380 : = (bbstreamer_extractor *) streamer;
381 :
382 : Assert(mystreamer->file == NULL);
383 246 : }
384 :
385 : /*
386 : * Free memory.
387 : */
388 : static void
389 246 : bbstreamer_extractor_free(bbstreamer *streamer)
390 : {
391 246 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
392 :
393 246 : pfree(mystreamer->basepath);
394 246 : pfree(mystreamer);
395 246 : }
|