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
47 7986 : open_target_file(const char *path, bool trunc)
48 : {
49 : int mode;
50 :
51 7986 : if (!path_is_safe_for_extraction(path))
52 0 : pg_fatal("target file path is unsafe for open: \"%s\"", path);
53 :
54 7986 : if (dry_run)
55 269 : return;
56 :
57 7717 : if (dstfd != -1 && !trunc &&
58 3230 : strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
59 826 : 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;
68 6891 : dstfd = open(dstpath, mode, pg_file_create_mode);
69 6891 : if (dstfd < 0)
70 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
78 6919 : close_target_file(void)
79 : {
80 6919 : if (dstfd == -1)
81 29 : return;
82 :
83 6890 : if (close(dstfd) != 0)
84 0 : pg_fatal("could not close target file \"%s\": %m",
85 : dstpath);
86 :
87 6890 : dstfd = -1;
88 : }
89 :
90 : void
91 50187 : 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 50187 : fetch_done += size;
98 50187 : progress_report(false);
99 :
100 50187 : if (dry_run)
101 4691 : return;
102 :
103 45496 : if (lseek(dstfd, begin, SEEK_SET) == -1)
104 0 : pg_fatal("could not seek in target file \"%s\": %m",
105 : dstpath);
106 :
107 45496 : writeleft = size;
108 45496 : p = buf;
109 90929 : while (writeleft > 0)
110 : {
111 : ssize_t writelen;
112 :
113 45433 : errno = 0;
114 45433 : writelen = write(dstfd, p, writeleft);
115 45433 : if (writelen < 0)
116 : {
117 : /* if write didn't set errno, assume problem is no disk space */
118 0 : if (errno == 0)
119 0 : errno = ENOSPC;
120 0 : pg_fatal("could not write file \"%s\": %m",
121 : dstpath);
122 : }
123 :
124 45433 : p += writelen;
125 45433 : 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 : Assert(entry->action == FILE_ACTION_REMOVE);
136 : Assert(entry->target_exists);
137 :
138 759 : switch (entry->target_type)
139 : {
140 18 : case FILE_TYPE_DIRECTORY:
141 18 : remove_target_dir(entry->path);
142 18 : break;
143 :
144 741 : case FILE_TYPE_REGULAR:
145 741 : remove_target_file(entry->path, false);
146 741 : break;
147 :
148 0 : case FILE_TYPE_SYMLINK:
149 0 : remove_target_symlink(entry->path);
150 0 : break;
151 :
152 0 : case FILE_TYPE_UNDEFINED:
153 0 : pg_fatal("undefined file type for \"%s\"", entry->path);
154 : break;
155 : }
156 759 : }
157 :
158 : void
159 9 : create_target(file_entry_t *entry)
160 : {
161 : Assert(entry->action == FILE_ACTION_CREATE);
162 : Assert(!entry->target_exists);
163 :
164 9 : switch (entry->source_type)
165 : {
166 9 : case FILE_TYPE_DIRECTORY:
167 9 : create_target_dir(entry->path);
168 9 : break;
169 :
170 0 : case FILE_TYPE_SYMLINK:
171 0 : create_target_symlink(entry->path, entry->source_link_target);
172 0 : break;
173 :
174 0 : case FILE_TYPE_REGULAR:
175 : /* can't happen. Regular files are created with open_target_file. */
176 0 : pg_fatal("invalid action (CREATE) for regular file");
177 : break;
178 :
179 0 : case FILE_TYPE_UNDEFINED:
180 0 : pg_fatal("undefined file type for \"%s\"", entry->path);
181 : break;
182 : }
183 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
190 741 : remove_target_file(const char *path, bool missing_ok)
191 : {
192 : char dstpath[MAXPGPATH];
193 :
194 741 : if (!path_is_safe_for_extraction(path))
195 0 : pg_fatal("target file path is unsafe for removal: \"%s\"", path);
196 :
197 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 : {
203 0 : if (errno == ENOENT && missing_ok)
204 0 : return;
205 :
206 0 : pg_fatal("could not remove file \"%s\": %m",
207 : dstpath);
208 : }
209 : }
210 :
211 : void
212 4 : truncate_target_file(const char *path, off_t newsize)
213 : {
214 : char dstpath[MAXPGPATH];
215 : int fd;
216 :
217 4 : if (!path_is_safe_for_extraction(path))
218 0 : pg_fatal("target file path is unsafe for truncation: \"%s\"", path);
219 :
220 4 : if (dry_run)
221 1 : return;
222 :
223 3 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
224 :
225 3 : fd = open(dstpath, O_WRONLY, pg_file_create_mode);
226 3 : if (fd < 0)
227 0 : pg_fatal("could not open file \"%s\" for truncation: %m",
228 : dstpath);
229 :
230 3 : if (ftruncate(fd, newsize) != 0)
231 0 : pg_fatal("could not truncate file \"%s\" to %u: %m",
232 : dstpath, (unsigned int) newsize);
233 :
234 3 : close(fd);
235 : }
236 :
237 : static void
238 9 : create_target_dir(const char *path)
239 : {
240 : char dstpath[MAXPGPATH];
241 :
242 9 : if (!path_is_safe_for_extraction(path))
243 0 : pg_fatal("target directory path is unsafe for directory creation: \"%s\"",
244 : path);
245 :
246 9 : if (dry_run)
247 0 : return;
248 :
249 9 : snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
250 9 : if (mkdir(dstpath, pg_dir_create_mode) != 0)
251 0 : pg_fatal("could not create directory \"%s\": %m",
252 : dstpath);
253 : }
254 :
255 : static void
256 18 : remove_target_dir(const char *path)
257 : {
258 : char dstpath[MAXPGPATH];
259 :
260 18 : if (!path_is_safe_for_extraction(path))
261 0 : pg_fatal("target directory path is unsafe for directory removal: \"%s\"",
262 : path);
263 :
264 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)
269 0 : pg_fatal("could not remove directory \"%s\": %m",
270 : dstpath);
271 : }
272 :
273 : static void
274 0 : create_target_symlink(const char *path, const char *link)
275 : {
276 : char dstpath[MAXPGPATH];
277 :
278 0 : if (!path_is_safe_for_extraction(path))
279 0 : pg_fatal("target symlink path is unsafe for creation: \"%s\"", path);
280 :
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)
286 0 : pg_fatal("could not create symbolic link at \"%s\": %m",
287 : dstpath);
288 : }
289 :
290 : static void
291 0 : remove_target_symlink(const char *path)
292 : {
293 : char dstpath[MAXPGPATH];
294 :
295 0 : if (!path_is_safe_for_extraction(path))
296 0 : pg_fatal("target symlink path is unsafe for removal: \"%s\"", path);
297 :
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)
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
317 14 : sync_target_dir(void)
318 : {
319 14 : if (!do_sync || dry_run)
320 13 : return;
321 :
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 *
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)
349 0 : pg_fatal("could not open file \"%s\" for reading: %m",
350 : fullpath);
351 :
352 59 : if (fstat(fd, &statbuf) < 0)
353 0 : pg_fatal("could not stat file \"%s\": %m",
354 : fullpath);
355 :
356 59 : len = statbuf.st_size;
357 :
358 59 : buffer = pg_malloc(len + 1);
359 :
360 59 : r = read(fd, buffer, len);
361 59 : if (r != len)
362 : {
363 0 : if (r < 0)
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 : }
370 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
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)
411 0 : pg_fatal("could not open directory \"%s\": %m",
412 : fullparentpath);
413 :
414 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 : {
428 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 :
444 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)
464 0 : pg_fatal("could not read symbolic link \"%s\": %m",
465 : fullpath);
466 2 : if (len >= sizeof(link_target))
467 0 : pg_fatal("symbolic link \"%s\" target is too long",
468 : fullpath);
469 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 : */
478 2 : if ((parentpath && strcmp(parentpath, PG_TBLSPC_DIR) == 0) ||
479 2 : strcmp(path, "pg_wal") == 0)
480 2 : recurse_dir(datadir, path, callback);
481 : }
482 : }
483 :
484 702 : if (errno)
485 0 : pg_fatal("could not read directory \"%s\": %m",
486 : fullparentpath);
487 :
488 702 : if (closedir(xldir))
489 0 : pg_fatal("could not close directory \"%s\": %m",
490 : fullparentpath);
491 702 : }
|