LCOV - code coverage report
Current view: top level - src/bin/pg_rewind - file_ops.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 68.1 % 185 126
Test Date: 2026-05-30 11:16:13 Functions: 86.7 % 15 13
Legend: Lines:     hit not hit

            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 : }
        

Generated by: LCOV version 2.0-1