Age Owner TLA 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 *
520 rhaas 78 CBC 12 : bbstreamer_plain_writer_new(char *pathname, FILE *file)
79 : {
80 : bbstreamer_plain_writer *streamer;
81 :
82 12 : streamer = palloc0(sizeof(bbstreamer_plain_writer));
83 12 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
84 : &bbstreamer_plain_writer_ops;
85 :
86 12 : streamer->pathname = pstrdup(pathname);
87 12 : streamer->file = file;
88 :
89 12 : if (file == NULL)
90 : {
91 12 : streamer->file = fopen(pathname, "wb");
92 12 : if (streamer->file == NULL)
366 tgl 93 UBC 0 : pg_fatal("could not create file \"%s\": %m", pathname);
520 rhaas 94 CBC 12 : streamer->should_close_file = true;
95 : }
96 :
97 12 : return &streamer->base;
98 : }
99 :
100 : /*
101 : * Write archive content to file.
102 : */
103 : static void
104 13709 : 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 13709 : mystreamer = (bbstreamer_plain_writer *) streamer;
111 :
112 13709 : if (len == 0)
520 rhaas 113 UBC 0 : return;
114 :
520 rhaas 115 CBC 13709 : errno = 0;
116 13709 : if (fwrite(data, len, 1, mystreamer->file) != 1)
117 : {
118 : /* if write didn't set errno, assume problem is no disk space */
520 rhaas 119 UBC 0 : if (errno == 0)
120 0 : errno = ENOSPC;
366 tgl 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
520 rhaas 131 CBC 12 : bbstreamer_plain_writer_finalize(bbstreamer *streamer)
132 : {
133 : bbstreamer_plain_writer *mystreamer;
134 :
135 12 : mystreamer = (bbstreamer_plain_writer *) streamer;
136 :
137 12 : if (mystreamer->should_close_file && fclose(mystreamer->file) != 0)
366 tgl 138 UBC 0 : pg_fatal("could not close file \"%s\": %m",
139 : mystreamer->pathname);
140 :
520 rhaas 141 CBC 12 : mystreamer->file = NULL;
142 12 : mystreamer->should_close_file = false;
143 12 : }
144 :
145 : /*
146 : * Free memory associated with this bbstreamer.
147 : */
148 : static void
149 12 : bbstreamer_plain_writer_free(bbstreamer *streamer)
150 : {
151 : bbstreamer_plain_writer *mystreamer;
152 :
153 12 : mystreamer = (bbstreamer_plain_writer *) streamer;
154 :
155 12 : Assert(!mystreamer->should_close_file);
156 12 : Assert(mystreamer->base.bbs_next == NULL);
157 :
158 12 : pfree(mystreamer->pathname);
159 12 : pfree(mystreamer);
160 12 : }
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 113 : 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 113 : streamer = palloc0(sizeof(bbstreamer_extractor));
190 113 : *((const bbstreamer_ops **) &streamer->base.bbs_ops) =
191 : &bbstreamer_extractor_ops;
192 113 : streamer->basepath = pstrdup(basepath);
193 113 : streamer->link_map = link_map;
194 113 : streamer->report_output_file = report_output_file;
195 :
196 113 : return &streamer->base;
197 : }
198 :
199 : /*
200 : * Extract archive contents to the filesystem.
201 : */
202 : static void
203 393918 : bbstreamer_extractor_content(bbstreamer *streamer, bbstreamer_member *member,
204 : const char *data, int len,
205 : bbstreamer_archive_context context)
206 : {
207 393918 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
208 : int fnamelen;
209 :
210 393918 : Assert(member != NULL || context == BBSTREAMER_ARCHIVE_TRAILER);
211 393918 : Assert(context != BBSTREAMER_UNKNOWN);
212 :
213 393918 : switch (context)
214 : {
215 98785 : case BBSTREAMER_MEMBER_HEADER:
216 98785 : Assert(mystreamer->file == NULL);
217 :
218 : /* Prepend basepath. */
219 98785 : snprintf(mystreamer->filename, sizeof(mystreamer->filename),
220 98785 : "%s/%s", mystreamer->basepath, member->pathname);
221 :
222 : /* Remove any trailing slash. */
223 98785 : fnamelen = strlen(mystreamer->filename);
224 98785 : if (mystreamer->filename[fnamelen - 1] == '/')
225 2513 : mystreamer->filename[fnamelen - 1] = '\0';
226 :
227 : /* Dispatch based on file type. */
228 98785 : if (member->is_directory)
229 2500 : extract_directory(mystreamer->filename, member->mode);
230 96285 : else if (member->is_link)
231 : {
232 13 : const char *linktarget = member->linktarget;
233 :
234 13 : if (mystreamer->link_map)
235 13 : linktarget = mystreamer->link_map(linktarget);
236 13 : extract_link(mystreamer->filename, linktarget);
237 : }
238 : else
239 96272 : mystreamer->file =
240 96272 : create_file_for_extract(mystreamer->filename,
241 : member->mode);
242 :
243 : /* Report output file change. */
244 98785 : if (mystreamer->report_output_file)
245 98785 : mystreamer->report_output_file(mystreamer->filename);
246 98785 : break;
247 :
248 196240 : case BBSTREAMER_MEMBER_CONTENTS:
249 196240 : if (mystreamer->file == NULL)
520 rhaas 250 UBC 0 : break;
251 :
520 rhaas 252 CBC 196240 : errno = 0;
253 196240 : if (len > 0 && fwrite(data, len, 1, mystreamer->file) != 1)
254 : {
255 : /* if write didn't set errno, assume problem is no disk space */
520 rhaas 256 UBC 0 : if (errno == 0)
257 0 : errno = ENOSPC;
366 tgl 258 0 : pg_fatal("could not write to file \"%s\": %m",
259 : mystreamer->filename);
260 : }
520 rhaas 261 CBC 196240 : break;
262 :
263 98783 : case BBSTREAMER_MEMBER_TRAILER:
264 98783 : if (mystreamer->file == NULL)
265 2513 : break;
266 96270 : fclose(mystreamer->file);
267 96270 : mystreamer->file = NULL;
268 96270 : break;
269 :
270 110 : case BBSTREAMER_ARCHIVE_TRAILER:
271 110 : break;
272 :
520 rhaas 273 UBC 0 : default:
274 : /* Shouldn't happen. */
366 tgl 275 0 : pg_fatal("unexpected state while extracting archive");
276 : }
520 rhaas 277 CBC 393918 : }
278 :
279 : /*
280 : * Create a directory.
281 : */
282 : static void
283 2500 : extract_directory(const char *filename, mode_t mode)
284 : {
285 2500 : if (mkdir(filename, pg_dir_create_mode) != 0)
286 : {
287 : /*
288 : * When streaming WAL, pg_wal (or pg_xlog for pre-9.6 clusters) will
289 : * have been created by the wal receiver process. Also, when the WAL
290 : * directory location was specified, pg_wal (or pg_xlog) has already
291 : * been created as a symbolic link before starting the actual backup.
292 : * So just ignore creation failures on related directories.
293 : */
294 270 : if (!((pg_str_endswith(filename, "/pg_wal") ||
295 180 : pg_str_endswith(filename, "/pg_xlog") ||
296 90 : pg_str_endswith(filename, "/archive_status")) &&
297 180 : errno == EEXIST))
366 tgl 298 UBC 0 : pg_fatal("could not create directory \"%s\": %m",
299 : filename);
300 : }
301 :
302 : #ifndef WIN32
520 rhaas 303 CBC 2500 : if (chmod(filename, mode))
366 tgl 304 UBC 0 : pg_fatal("could not set permissions on directory \"%s\": %m",
305 : filename);
306 : #endif
520 rhaas 307 CBC 2500 : }
308 :
309 : /*
310 : * Create a symbolic link.
311 : *
312 : * It's most likely a link in pg_tblspc directory, to the location of a
313 : * tablespace. Apply any tablespace mapping given on the command line
314 : * (--tablespace-mapping). (We blindly apply the mapping without checking that
315 : * the link really is inside pg_tblspc. We don't expect there to be other
316 : * symlinks in a data directory, but if there are, you can call it an
317 : * undocumented feature that you can map them too.)
318 : */
319 : static void
320 13 : extract_link(const char *filename, const char *linktarget)
321 : {
322 13 : if (symlink(linktarget, filename) != 0)
366 tgl 323 UBC 0 : pg_fatal("could not create symbolic link from \"%s\" to \"%s\": %m",
324 : filename, linktarget);
520 rhaas 325 CBC 13 : }
326 :
327 : /*
328 : * Create a regular file.
329 : *
330 : * Return the resulting handle so we can write the content to the file.
331 : */
332 : static FILE *
333 96272 : create_file_for_extract(const char *filename, mode_t mode)
334 : {
335 : FILE *file;
336 :
337 96272 : file = fopen(filename, "wb");
338 96272 : if (file == NULL)
366 tgl 339 UBC 0 : pg_fatal("could not create file \"%s\": %m", filename);
340 :
341 : #ifndef WIN32
520 rhaas 342 CBC 96272 : if (chmod(filename, mode))
366 tgl 343 UBC 0 : pg_fatal("could not set permissions on file \"%s\": %m",
344 : filename);
345 : #endif
346 :
520 rhaas 347 CBC 96272 : return file;
348 : }
349 :
350 : /*
351 : * End-of-stream processing for extracting an archive.
352 : *
353 : * There's nothing to do here but sanity checking.
354 : */
355 : static void
356 110 : bbstreamer_extractor_finalize(bbstreamer *streamer)
357 : {
519 tomas.vondra 358 110 : bbstreamer_extractor *mystreamer PG_USED_FOR_ASSERTS_ONLY
359 : = (bbstreamer_extractor *) streamer;
360 :
520 rhaas 361 110 : Assert(mystreamer->file == NULL);
362 110 : }
363 :
364 : /*
365 : * Free memory.
366 : */
367 : static void
368 110 : bbstreamer_extractor_free(bbstreamer *streamer)
369 : {
370 110 : bbstreamer_extractor *mystreamer = (bbstreamer_extractor *) streamer;
371 :
372 110 : pfree(mystreamer->basepath);
373 110 : pfree(mystreamer);
374 110 : }
|