LCOV - code coverage report
Current view: top level - src/bin/pg_rewind - file_ops.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 70.8 % 171 121
Test Date: 2026-02-17 17:20:33 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         7889 : open_target_file(const char *path, bool trunc)
      48              : {
      49              :     int         mode;
      50              : 
      51         7889 :     if (dry_run)
      52          269 :         return;
      53              : 
      54         7620 :     if (dstfd != -1 && !trunc &&
      55         3184 :         strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
      56          819 :         return;                 /* already open */
      57              : 
      58         6801 :     close_target_file();
      59              : 
      60         6801 :     snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
      61              : 
      62         6801 :     mode = O_WRONLY | O_CREAT | PG_BINARY;
      63         6801 :     if (trunc)
      64         4436 :         mode |= O_TRUNC;
      65         6801 :     dstfd = open(dstpath, mode, pg_file_create_mode);
      66         6801 :     if (dstfd < 0)
      67            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
      75         6829 : close_target_file(void)
      76              : {
      77         6829 :     if (dstfd == -1)
      78           29 :         return;
      79              : 
      80         6800 :     if (close(dstfd) != 0)
      81            0 :         pg_fatal("could not close target file \"%s\": %m",
      82              :                  dstpath);
      83              : 
      84         6800 :     dstfd = -1;
      85              : }
      86              : 
      87              : void
      88        54204 : 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        54204 :     fetch_done += size;
      95        54204 :     progress_report(false);
      96              : 
      97        54204 :     if (dry_run)
      98         4690 :         return;
      99              : 
     100        49514 :     if (lseek(dstfd, begin, SEEK_SET) == -1)
     101            0 :         pg_fatal("could not seek in target file \"%s\": %m",
     102              :                  dstpath);
     103              : 
     104        49514 :     writeleft = size;
     105        49514 :     p = buf;
     106        98972 :     while (writeleft > 0)
     107              :     {
     108              :         ssize_t     writelen;
     109              : 
     110        49458 :         errno = 0;
     111        49458 :         writelen = write(dstfd, p, writeleft);
     112        49458 :         if (writelen < 0)
     113              :         {
     114              :             /* if write didn't set errno, assume problem is no disk space */
     115            0 :             if (errno == 0)
     116            0 :                 errno = ENOSPC;
     117            0 :             pg_fatal("could not write file \"%s\": %m",
     118              :                      dstpath);
     119              :         }
     120              : 
     121        49458 :         p += writelen;
     122        49458 :         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          721 : remove_target(file_entry_t *entry)
     131              : {
     132              :     Assert(entry->action == FILE_ACTION_REMOVE);
     133              :     Assert(entry->target_exists);
     134              : 
     135          721 :     switch (entry->target_type)
     136              :     {
     137           18 :         case FILE_TYPE_DIRECTORY:
     138           18 :             remove_target_dir(entry->path);
     139           18 :             break;
     140              : 
     141          703 :         case FILE_TYPE_REGULAR:
     142          703 :             remove_target_file(entry->path, false);
     143          703 :             break;
     144              : 
     145            0 :         case FILE_TYPE_SYMLINK:
     146            0 :             remove_target_symlink(entry->path);
     147            0 :             break;
     148              : 
     149            0 :         case FILE_TYPE_UNDEFINED:
     150            0 :             pg_fatal("undefined file type for \"%s\"", entry->path);
     151              :             break;
     152              :     }
     153          721 : }
     154              : 
     155              : void
     156            9 : create_target(file_entry_t *entry)
     157              : {
     158              :     Assert(entry->action == FILE_ACTION_CREATE);
     159              :     Assert(!entry->target_exists);
     160              : 
     161            9 :     switch (entry->source_type)
     162              :     {
     163            9 :         case FILE_TYPE_DIRECTORY:
     164            9 :             create_target_dir(entry->path);
     165            9 :             break;
     166              : 
     167            0 :         case FILE_TYPE_SYMLINK:
     168            0 :             create_target_symlink(entry->path, entry->source_link_target);
     169            0 :             break;
     170              : 
     171            0 :         case FILE_TYPE_REGULAR:
     172              :             /* can't happen. Regular files are created with open_target_file. */
     173            0 :             pg_fatal("invalid action (CREATE) for regular file");
     174              :             break;
     175              : 
     176            0 :         case FILE_TYPE_UNDEFINED:
     177            0 :             pg_fatal("undefined file type for \"%s\"", entry->path);
     178              :             break;
     179              :     }
     180            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
     187          703 : remove_target_file(const char *path, bool missing_ok)
     188              : {
     189              :     char        dstpath[MAXPGPATH];
     190              : 
     191          703 :     if (dry_run)
     192            7 :         return;
     193              : 
     194          696 :     snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
     195          696 :     if (unlink(dstpath) != 0)
     196              :     {
     197            0 :         if (errno == ENOENT && missing_ok)
     198            0 :             return;
     199              : 
     200            0 :         pg_fatal("could not remove file \"%s\": %m",
     201              :                  dstpath);
     202              :     }
     203              : }
     204              : 
     205              : void
     206            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              : 
     216            3 :     fd = open(dstpath, O_WRONLY, pg_file_create_mode);
     217            3 :     if (fd < 0)
     218            0 :         pg_fatal("could not open file \"%s\" for truncation: %m",
     219              :                  dstpath);
     220              : 
     221            3 :     if (ftruncate(fd, newsize) != 0)
     222            0 :         pg_fatal("could not truncate file \"%s\" to %u: %m",
     223              :                  dstpath, (unsigned int) newsize);
     224              : 
     225            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)
     234            0 :         return;
     235              : 
     236            9 :     snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
     237            9 :     if (mkdir(dstpath, pg_dir_create_mode) != 0)
     238            0 :         pg_fatal("could not create directory \"%s\": %m",
     239              :                  dstpath);
     240              : }
     241              : 
     242              : static void
     243           18 : remove_target_dir(const char *path)
     244              : {
     245              :     char        dstpath[MAXPGPATH];
     246              : 
     247           18 :     if (dry_run)
     248            1 :         return;
     249              : 
     250           17 :     snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
     251           17 :     if (rmdir(dstpath) != 0)
     252            0 :         pg_fatal("could not remove directory \"%s\": %m",
     253              :                  dstpath);
     254              : }
     255              : 
     256              : static void
     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)
     266            0 :         pg_fatal("could not create symbolic link at \"%s\": %m",
     267              :                  dstpath);
     268              : }
     269              : 
     270              : static void
     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)
     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
     294           14 : sync_target_dir(void)
     295              : {
     296           14 :     if (!do_sync || dry_run)
     297           13 :         return;
     298              : 
     299            1 :     sync_pgdata(datadir_target, PG_VERSION_NUM, sync_method, true);
     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 *
     314           59 : 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           59 :     snprintf(fullpath, sizeof(fullpath), "%s/%s", datadir, path);
     324              : 
     325           59 :     if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) == -1)
     326            0 :         pg_fatal("could not open file \"%s\" for reading: %m",
     327              :                  fullpath);
     328              : 
     329           59 :     if (fstat(fd, &statbuf) < 0)
     330            0 :         pg_fatal("could not stat file \"%s\": %m",
     331              :                  fullpath);
     332              : 
     333           59 :     len = statbuf.st_size;
     334              : 
     335           59 :     buffer = pg_malloc(len + 1);
     336              : 
     337           59 :     r = read(fd, buffer, len);
     338           59 :     if (r != len)
     339              :     {
     340            0 :         if (r < 0)
     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              :     }
     347           59 :     close(fd);
     348              : 
     349              :     /* Zero-terminate the buffer. */
     350           59 :     buffer[len] = '\0';
     351              : 
     352           59 :     if (filesize)
     353           48 :         *filesize = len;
     354           59 :     return buffer;
     355              : }
     356              : 
     357              : /*
     358              :  * Traverse through all files in a data directory, calling 'callback'
     359              :  * for each file.
     360              :  */
     361              : void
     362           24 : traverse_datadir(const char *datadir, process_file_callback_t callback)
     363              : {
     364           24 :     recurse_dir(datadir, NULL, callback);
     365           24 : }
     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          702 : 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          702 :     if (parentpath)
     382          678 :         snprintf(fullparentpath, MAXPGPATH, "%s/%s", datadir, parentpath);
     383              :     else
     384           24 :         snprintf(fullparentpath, MAXPGPATH, "%s", datadir);
     385              : 
     386          702 :     xldir = opendir(fullparentpath);
     387          702 :     if (xldir == NULL)
     388            0 :         pg_fatal("could not open directory \"%s\": %m",
     389              :                  fullparentpath);
     390              : 
     391        29106 :     while (errno = 0, (xlde = readdir(xldir)) != NULL)
     392              :     {
     393              :         struct stat fst;
     394              :         char        fullpath[MAXPGPATH * 2];
     395              :         char        path[MAXPGPATH * 2];
     396              : 
     397        28404 :         if (strcmp(xlde->d_name, ".") == 0 ||
     398        27702 :             strcmp(xlde->d_name, "..") == 0)
     399         1404 :             continue;
     400              : 
     401        27000 :         snprintf(fullpath, sizeof(fullpath), "%s/%s", fullparentpath, xlde->d_name);
     402              : 
     403        27000 :         if (lstat(fullpath, &fst) < 0)
     404              :         {
     405            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              : 
     421        27000 :         if (parentpath)
     422        26416 :             snprintf(path, sizeof(path), "%s/%s", parentpath, xlde->d_name);
     423              :         else
     424          584 :             snprintf(path, sizeof(path), "%s", xlde->d_name);
     425              : 
     426        27000 :         if (S_ISREG(fst.st_mode))
     427        26322 :             callback(path, FILE_TYPE_REGULAR, fst.st_size, NULL);
     428          678 :         else if (S_ISDIR(fst.st_mode))
     429              :         {
     430          676 :             callback(path, FILE_TYPE_DIRECTORY, 0, NULL);
     431              :             /* recurse to handle subdirectories */
     432          676 :             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)
     441            0 :                 pg_fatal("could not read symbolic link \"%s\": %m",
     442              :                          fullpath);
     443            2 :             if (len >= sizeof(link_target))
     444            0 :                 pg_fatal("symbolic link \"%s\" target is too long",
     445              :                          fullpath);
     446            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_DIR) == 0) ||
     456            2 :                 strcmp(path, "pg_wal") == 0)
     457            2 :                 recurse_dir(datadir, path, callback);
     458              :         }
     459              :     }
     460              : 
     461          702 :     if (errno)
     462            0 :         pg_fatal("could not read directory \"%s\": %m",
     463              :                  fullparentpath);
     464              : 
     465          702 :     if (closedir(xldir))
     466            0 :         pg_fatal("could not close directory \"%s\": %m",
     467              :                  fullparentpath);
     468          702 : }
        

Generated by: LCOV version 2.0-1