Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * file_ops.c
4 : : * Helper functions for operating on files.
5 : : *
6 : : * Most of the functions in this file are helper functions for writing to
7 : : * the target data directory. The functions check the --dry-run flag, and
8 : : * do nothing if it's enabled. You should avoid accessing the target files
9 : : * directly but if you do, make sure you honor the --dry-run mode!
10 : : *
11 : : * Portions Copyright (c) 2013-2026, PostgreSQL Global Development Group
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres_fe.h"
16 : :
17 : : #include <sys/stat.h>
18 : : #include <dirent.h>
19 : : #include <fcntl.h>
20 : : #include <unistd.h>
21 : :
22 : : #include "common/file_perm.h"
23 : : #include "common/file_utils.h"
24 : : #include "file_ops.h"
25 : : #include "filemap.h"
26 : : #include "pg_rewind.h"
27 : :
28 : : /*
29 : : * Currently open target file.
30 : : */
31 : : static int dstfd = -1;
32 : : static char dstpath[MAXPGPATH] = "";
33 : :
34 : : static void create_target_dir(const char *path);
35 : : static void remove_target_dir(const char *path);
36 : : static void create_target_symlink(const char *path, const char *link);
37 : : static void remove_target_symlink(const char *path);
38 : :
39 : : static void recurse_dir(const char *datadir, const char *parentpath,
40 : : process_file_callback_t callback);
41 : :
42 : : /*
43 : : * Open a target file for writing. If 'trunc' is true and the file already
44 : : * exists, it will be truncated.
45 : : */
46 : : void
4086 heikki.linnakangas@i 47 :CBC 8006 : open_target_file(const char *path, bool trunc)
48 : : {
49 : : int mode;
50 : :
19 michael@paquier.xyz 51 [ - + ]: 8006 : if (!path_is_safe_for_extraction(path))
19 michael@paquier.xyz 52 :UBC 0 : pg_fatal("target file path is unsafe for open: \"%s\"", path);
53 : :
4086 heikki.linnakangas@i 54 [ + + ]:CBC 8006 : if (dry_run)
55 : 269 : return;
56 : :
57 [ + + + + ]: 7737 : if (dstfd != -1 && !trunc &&
58 [ + + ]: 3250 : strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
59 : 846 : return; /* already open */
60 : :
61 : 6891 : close_target_file();
62 : :
63 : 6891 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
64 : :
65 : 6891 : mode = O_WRONLY | O_CREAT | PG_BINARY;
66 [ + + ]: 6891 : if (trunc)
67 : 4487 : mode |= O_TRUNC;
2975 sfrost@snowman.net 68 : 6891 : dstfd = open(dstpath, mode, pg_file_create_mode);
4086 heikki.linnakangas@i 69 [ - + ]: 6891 : if (dstfd < 0)
2616 peter@eisentraut.org 70 :UBC 0 : pg_fatal("could not open target file \"%s\": %m",
71 : : dstpath);
72 : : }
73 : :
74 : : /*
75 : : * Close target file, if it's open.
76 : : */
77 : : void
4086 heikki.linnakangas@i 78 :CBC 6919 : close_target_file(void)
79 : : {
80 [ + + ]: 6919 : if (dstfd == -1)
81 : 29 : return;
82 : :
83 [ - + ]: 6890 : if (close(dstfd) != 0)
2616 peter@eisentraut.org 84 :UBC 0 : pg_fatal("could not close target file \"%s\": %m",
85 : : dstpath);
86 : :
4086 heikki.linnakangas@i 87 :CBC 6890 : dstfd = -1;
88 : : }
89 : :
90 : : void
91 : 50228 : write_target_range(char *buf, off_t begin, size_t size)
92 : : {
93 : : size_t writeleft;
94 : : char *p;
95 : :
96 : : /* update progress report */
97 : 50228 : fetch_done += size;
98 : 50228 : progress_report(false);
99 : :
100 [ + + ]: 50228 : if (dry_run)
101 : 4691 : return;
102 : :
103 [ - + ]: 45537 : if (lseek(dstfd, begin, SEEK_SET) == -1)
2616 peter@eisentraut.org 104 :UBC 0 : pg_fatal("could not seek in target file \"%s\": %m",
105 : : dstpath);
106 : :
4086 heikki.linnakangas@i 107 :CBC 45537 : writeleft = size;
108 : 45537 : p = buf;
109 [ + + ]: 91011 : while (writeleft > 0)
110 : : {
111 : : ssize_t writelen;
112 : :
4006 fujii@postgresql.org 113 : 45474 : errno = 0;
4086 heikki.linnakangas@i 114 : 45474 : writelen = write(dstfd, p, writeleft);
115 [ - + ]: 45474 : if (writelen < 0)
116 : : {
117 : : /* if write didn't set errno, assume problem is no disk space */
4006 fujii@postgresql.org 118 [ # # ]:UBC 0 : if (errno == 0)
119 : 0 : errno = ENOSPC;
2616 peter@eisentraut.org 120 : 0 : pg_fatal("could not write file \"%s\": %m",
121 : : dstpath);
122 : : }
123 : :
4086 heikki.linnakangas@i 124 :CBC 45474 : p += writelen;
125 : 45474 : writeleft -= writelen;
126 : : }
127 : :
128 : : /* keep the file open, in case we need to copy more blocks in it */
129 : : }
130 : :
131 : :
132 : : void
133 : 759 : remove_target(file_entry_t *entry)
134 : : {
135 [ - + ]: 759 : Assert(entry->action == FILE_ACTION_REMOVE);
2033 136 [ - + ]: 759 : Assert(entry->target_exists);
137 : :
138 [ + + - - : 759 : switch (entry->target_type)
- ]
139 : : {
4086 140 : 18 : case FILE_TYPE_DIRECTORY:
141 : 18 : remove_target_dir(entry->path);
142 : 18 : break;
143 : :
144 : 741 : case FILE_TYPE_REGULAR:
2984 fujii@postgresql.org 145 : 741 : remove_target_file(entry->path, false);
4086 heikki.linnakangas@i 146 : 741 : break;
147 : :
4086 heikki.linnakangas@i 148 :UBC 0 : case FILE_TYPE_SYMLINK:
149 : 0 : remove_target_symlink(entry->path);
150 : 0 : break;
151 : :
2033 152 : 0 : case FILE_TYPE_UNDEFINED:
153 : 0 : pg_fatal("undefined file type for \"%s\"", entry->path);
154 : : break;
155 : : }
4086 heikki.linnakangas@i 156 :CBC 759 : }
157 : :
158 : : void
159 : 9 : create_target(file_entry_t *entry)
160 : : {
161 [ - + ]: 9 : Assert(entry->action == FILE_ACTION_CREATE);
2033 162 [ - + ]: 9 : Assert(!entry->target_exists);
163 : :
164 [ + - - - : 9 : switch (entry->source_type)
- ]
165 : : {
4086 166 : 9 : case FILE_TYPE_DIRECTORY:
167 : 9 : create_target_dir(entry->path);
168 : 9 : break;
169 : :
4086 heikki.linnakangas@i 170 :UBC 0 : case FILE_TYPE_SYMLINK:
2033 171 : 0 : create_target_symlink(entry->path, entry->source_link_target);
4086 172 : 0 : break;
173 : :
174 : 0 : case FILE_TYPE_REGULAR:
175 : : /* can't happen. Regular files are created with open_target_file. */
2616 peter@eisentraut.org 176 : 0 : pg_fatal("invalid action (CREATE) for regular file");
177 : : break;
178 : :
2033 heikki.linnakangas@i 179 : 0 : case FILE_TYPE_UNDEFINED:
180 : 0 : pg_fatal("undefined file type for \"%s\"", entry->path);
181 : : break;
182 : : }
4086 heikki.linnakangas@i 183 :CBC 9 : }
184 : :
185 : : /*
186 : : * Remove a file from target data directory. If missing_ok is true, it
187 : : * is fine for the target file to not exist.
188 : : */
189 : : void
2984 fujii@postgresql.org 190 : 741 : remove_target_file(const char *path, bool missing_ok)
191 : : {
192 : : char dstpath[MAXPGPATH];
193 : :
19 michael@paquier.xyz 194 [ - + ]: 741 : if (!path_is_safe_for_extraction(path))
19 michael@paquier.xyz 195 :UBC 0 : pg_fatal("target file path is unsafe for removal: \"%s\"", path);
196 : :
4086 heikki.linnakangas@i 197 [ + + ]:CBC 741 : if (dry_run)
198 : 7 : return;
199 : :
200 : 734 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
201 [ - + ]: 734 : if (unlink(dstpath) != 0)
202 : : {
2984 fujii@postgresql.org 203 [ # # # # ]:UBC 0 : if (errno == ENOENT && missing_ok)
204 : 0 : return;
205 : :
2616 peter@eisentraut.org 206 : 0 : pg_fatal("could not remove file \"%s\": %m",
207 : : dstpath);
208 : : }
209 : : }
210 : :
211 : : void
4086 heikki.linnakangas@i 212 :CBC 4 : truncate_target_file(const char *path, off_t newsize)
213 : : {
214 : : char dstpath[MAXPGPATH];
215 : : int fd;
216 : :
19 michael@paquier.xyz 217 [ - + ]: 4 : if (!path_is_safe_for_extraction(path))
19 michael@paquier.xyz 218 :UBC 0 : pg_fatal("target file path is unsafe for truncation: \"%s\"", path);
219 : :
4086 heikki.linnakangas@i 220 [ + + ]:CBC 4 : if (dry_run)
221 : 1 : return;
222 : :
223 : 3 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
224 : :
2975 sfrost@snowman.net 225 : 3 : fd = open(dstpath, O_WRONLY, pg_file_create_mode);
4086 heikki.linnakangas@i 226 [ - + ]: 3 : if (fd < 0)
2616 peter@eisentraut.org 227 :UBC 0 : pg_fatal("could not open file \"%s\" for truncation: %m",
228 : : dstpath);
229 : :
4086 heikki.linnakangas@i 230 [ - + ]:CBC 3 : if (ftruncate(fd, newsize) != 0)
2616 peter@eisentraut.org 231 :UBC 0 : pg_fatal("could not truncate file \"%s\" to %u: %m",
232 : : dstpath, (unsigned int) newsize);
233 : :
4086 heikki.linnakangas@i 234 :CBC 3 : close(fd);
235 : : }
236 : :
237 : : static void
238 : 9 : create_target_dir(const char *path)
239 : : {
240 : : char dstpath[MAXPGPATH];
241 : :
19 michael@paquier.xyz 242 [ - + ]: 9 : if (!path_is_safe_for_extraction(path))
19 michael@paquier.xyz 243 :UBC 0 : pg_fatal("target directory path is unsafe for directory creation: \"%s\"",
244 : : path);
245 : :
4086 heikki.linnakangas@i 246 [ - + ]:CBC 9 : if (dry_run)
4086 heikki.linnakangas@i 247 :UBC 0 : return;
248 : :
4086 heikki.linnakangas@i 249 :CBC 9 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
2975 sfrost@snowman.net 250 [ - + ]: 9 : if (mkdir(dstpath, pg_dir_create_mode) != 0)
2616 peter@eisentraut.org 251 :UBC 0 : pg_fatal("could not create directory \"%s\": %m",
252 : : dstpath);
253 : : }
254 : :
255 : : static void
4086 heikki.linnakangas@i 256 :CBC 18 : remove_target_dir(const char *path)
257 : : {
258 : : char dstpath[MAXPGPATH];
259 : :
19 michael@paquier.xyz 260 [ - + ]: 18 : if (!path_is_safe_for_extraction(path))
19 michael@paquier.xyz 261 :UBC 0 : pg_fatal("target directory path is unsafe for directory removal: \"%s\"",
262 : : path);
263 : :
4086 heikki.linnakangas@i 264 [ + + ]:CBC 18 : if (dry_run)
265 : 1 : return;
266 : :
267 : 17 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
268 [ - + ]: 17 : if (rmdir(dstpath) != 0)
2616 peter@eisentraut.org 269 :UBC 0 : pg_fatal("could not remove directory \"%s\": %m",
270 : : dstpath);
271 : : }
272 : :
273 : : static void
4086 heikki.linnakangas@i 274 : 0 : create_target_symlink(const char *path, const char *link)
275 : : {
276 : : char dstpath[MAXPGPATH];
277 : :
19 michael@paquier.xyz 278 [ # # ]: 0 : if (!path_is_safe_for_extraction(path))
279 : 0 : pg_fatal("target symlink path is unsafe for creation: \"%s\"", path);
280 : :
4086 heikki.linnakangas@i 281 [ # # ]: 0 : if (dry_run)
282 : 0 : return;
283 : :
284 : 0 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
285 [ # # ]: 0 : if (symlink(link, dstpath) != 0)
2616 peter@eisentraut.org 286 : 0 : pg_fatal("could not create symbolic link at \"%s\": %m",
287 : : dstpath);
288 : : }
289 : :
290 : : static void
4086 heikki.linnakangas@i 291 : 0 : remove_target_symlink(const char *path)
292 : : {
293 : : char dstpath[MAXPGPATH];
294 : :
19 michael@paquier.xyz 295 [ # # ]: 0 : if (!path_is_safe_for_extraction(path))
296 : 0 : pg_fatal("target symlink path is unsafe for removal: \"%s\"", path);
297 : :
4086 heikki.linnakangas@i 298 [ # # ]: 0 : if (dry_run)
299 : 0 : return;
300 : :
301 : 0 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
302 [ # # ]: 0 : if (unlink(dstpath) != 0)
2616 peter@eisentraut.org 303 : 0 : pg_fatal("could not remove symbolic link \"%s\": %m",
304 : : dstpath);
305 : : }
306 : :
307 : : /*
308 : : * Sync target data directory to ensure that modifications are safely on disk.
309 : : *
310 : : * We do this once, for the whole data directory, for performance reasons. At
311 : : * the end of pg_rewind's run, the kernel is likely to already have flushed
312 : : * most dirty buffers to disk. Additionally sync_pgdata uses a two-pass
313 : : * approach when fsync is specified (only initiating writeback in the first
314 : : * pass), which often reduces the overall amount of IO noticeably.
315 : : */
316 : : void
2033 heikki.linnakangas@i 317 :CBC 14 : sync_target_dir(void)
318 : : {
319 [ + + - + ]: 14 : if (!do_sync || dry_run)
320 : 13 : return;
321 : :
431 nathan@postgresql.or 322 : 1 : sync_pgdata(datadir_target, PG_VERSION_NUM, sync_method, true);
323 : : }
324 : :
325 : :
326 : : /*
327 : : * Read a file into memory. The file to be read is <datadir>/<path>.
328 : : * The file contents are returned in a malloc'd buffer, and *filesize
329 : : * is set to the length of the file.
330 : : *
331 : : * The returned buffer is always zero-terminated; the size of the returned
332 : : * buffer is actually *filesize + 1. That's handy when reading a text file.
333 : : * This function can be used to read binary files as well, you can just
334 : : * ignore the zero-terminator in that case.
335 : : */
336 : : char *
4086 heikki.linnakangas@i 337 : 59 : slurpFile(const char *datadir, const char *path, size_t *filesize)
338 : : {
339 : : int fd;
340 : : char *buffer;
341 : : struct stat statbuf;
342 : : char fullpath[MAXPGPATH];
343 : : int len;
344 : : int r;
345 : :
346 : 59 : snprintf(fullpath, sizeof(fullpath), "%s/%s", datadir, path);
347 : :
348 [ - + ]: 59 : if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) == -1)
2616 peter@eisentraut.org 349 :UBC 0 : pg_fatal("could not open file \"%s\" for reading: %m",
350 : : fullpath);
351 : :
4086 heikki.linnakangas@i 352 [ - + ]:CBC 59 : if (fstat(fd, &statbuf) < 0)
112 peter@eisentraut.org 353 :UBC 0 : pg_fatal("could not stat file \"%s\": %m",
354 : : fullpath);
355 : :
4086 heikki.linnakangas@i 356 :CBC 59 : len = statbuf.st_size;
357 : :
358 : 59 : buffer = pg_malloc(len + 1);
359 : :
2873 michael@paquier.xyz 360 : 59 : r = read(fd, buffer, len);
361 [ - + ]: 59 : if (r != len)
362 : : {
2873 michael@paquier.xyz 363 [ # # ]:UBC 0 : if (r < 0)
2616 peter@eisentraut.org 364 : 0 : pg_fatal("could not read file \"%s\": %m",
365 : : fullpath);
366 : : else
367 : 0 : pg_fatal("could not read file \"%s\": read %d of %zu",
368 : : fullpath, r, (Size) len);
369 : : }
4086 heikki.linnakangas@i 370 :CBC 59 : close(fd);
371 : :
372 : : /* Zero-terminate the buffer. */
373 : 59 : buffer[len] = '\0';
374 : :
375 [ + + ]: 59 : if (filesize)
376 : 48 : *filesize = len;
377 : 59 : return buffer;
378 : : }
379 : :
380 : : /*
381 : : * Traverse through all files in a data directory, calling 'callback'
382 : : * for each file.
383 : : */
384 : : void
2033 385 : 24 : traverse_datadir(const char *datadir, process_file_callback_t callback)
386 : : {
387 : 24 : recurse_dir(datadir, NULL, callback);
388 : 24 : }
389 : :
390 : : /*
391 : : * recursive part of traverse_datadir
392 : : *
393 : : * parentpath is the current subdirectory's path relative to datadir,
394 : : * or NULL at the top level.
395 : : */
396 : : static void
397 : 702 : recurse_dir(const char *datadir, const char *parentpath,
398 : : process_file_callback_t callback)
399 : : {
400 : : DIR *xldir;
401 : : struct dirent *xlde;
402 : : char fullparentpath[MAXPGPATH];
403 : :
404 [ + + ]: 702 : if (parentpath)
405 : 678 : snprintf(fullparentpath, MAXPGPATH, "%s/%s", datadir, parentpath);
406 : : else
407 : 24 : snprintf(fullparentpath, MAXPGPATH, "%s", datadir);
408 : :
409 : 702 : xldir = opendir(fullparentpath);
410 [ - + ]: 702 : if (xldir == NULL)
2033 heikki.linnakangas@i 411 :UBC 0 : pg_fatal("could not open directory \"%s\": %m",
412 : : fullparentpath);
413 : :
2033 heikki.linnakangas@i 414 [ + + ]:CBC 30747 : while (errno = 0, (xlde = readdir(xldir)) != NULL)
415 : : {
416 : : struct stat fst;
417 : : char fullpath[MAXPGPATH * 2];
418 : : char path[MAXPGPATH * 2];
419 : :
420 [ + + ]: 30045 : if (strcmp(xlde->d_name, ".") == 0 ||
421 [ + + ]: 29343 : strcmp(xlde->d_name, "..") == 0)
422 : 1404 : continue;
423 : :
424 : 28641 : snprintf(fullpath, sizeof(fullpath), "%s/%s", fullparentpath, xlde->d_name);
425 : :
426 [ - + ]: 28641 : if (lstat(fullpath, &fst) < 0)
427 : : {
2033 heikki.linnakangas@i 428 [ # # ]:UBC 0 : if (errno == ENOENT)
429 : : {
430 : : /*
431 : : * File doesn't exist anymore. This is ok, if the new primary
432 : : * is running and the file was just removed. If it was a data
433 : : * file, there should be a WAL record of the removal. If it
434 : : * was something else, it couldn't have been anyway.
435 : : *
436 : : * TODO: But complain if we're processing the target dir!
437 : : */
438 : : }
439 : : else
440 : 0 : pg_fatal("could not stat file \"%s\": %m",
441 : : fullpath);
442 : : }
443 : :
2033 heikki.linnakangas@i 444 [ + + ]:CBC 28641 : if (parentpath)
445 : 28033 : snprintf(path, sizeof(path), "%s/%s", parentpath, xlde->d_name);
446 : : else
447 : 608 : snprintf(path, sizeof(path), "%s", xlde->d_name);
448 : :
449 [ + + ]: 28641 : if (S_ISREG(fst.st_mode))
450 : 27963 : callback(path, FILE_TYPE_REGULAR, fst.st_size, NULL);
451 [ + + ]: 678 : else if (S_ISDIR(fst.st_mode))
452 : : {
453 : 676 : callback(path, FILE_TYPE_DIRECTORY, 0, NULL);
454 : : /* recurse to handle subdirectories */
455 : 676 : recurse_dir(datadir, path, callback);
456 : : }
457 [ + - ]: 2 : else if (S_ISLNK(fst.st_mode))
458 : : {
459 : : char link_target[MAXPGPATH];
460 : : int len;
461 : :
462 : 2 : len = readlink(fullpath, link_target, sizeof(link_target));
463 [ - + ]: 2 : if (len < 0)
2033 heikki.linnakangas@i 464 :UBC 0 : pg_fatal("could not read symbolic link \"%s\": %m",
465 : : fullpath);
2033 heikki.linnakangas@i 466 [ - + ]:CBC 2 : if (len >= sizeof(link_target))
2033 heikki.linnakangas@i 467 :UBC 0 : pg_fatal("symbolic link \"%s\" target is too long",
468 : : fullpath);
2033 heikki.linnakangas@i 469 :CBC 2 : link_target[len] = '\0';
470 : :
471 : 2 : callback(path, FILE_TYPE_SYMLINK, 0, link_target);
472 : :
473 : : /*
474 : : * If it's a symlink within pg_tblspc, we need to recurse into it,
475 : : * to process all the tablespaces. We also follow a symlink if
476 : : * it's for pg_wal. Symlinks elsewhere are ignored.
477 : : */
634 michael@paquier.xyz 478 [ - + - - ]: 2 : if ((parentpath && strcmp(parentpath, PG_TBLSPC_DIR) == 0) ||
2033 heikki.linnakangas@i 479 [ + - ]: 2 : strcmp(path, "pg_wal") == 0)
480 : 2 : recurse_dir(datadir, path, callback);
481 : : }
482 : : }
483 : :
484 [ - + ]: 702 : if (errno)
2033 heikki.linnakangas@i 485 :UBC 0 : pg_fatal("could not read directory \"%s\": %m",
486 : : fullparentpath);
487 : :
2033 heikki.linnakangas@i 488 [ - + ]:CBC 702 : if (closedir(xldir))
2033 heikki.linnakangas@i 489 :UBC 0 : pg_fatal("could not close directory \"%s\": %m",
490 : : fullparentpath);
2033 heikki.linnakangas@i 491 :CBC 702 : }
|