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