LCOV - code coverage report
Current view: top level - src/bin/pg_rewind - file_ops.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 68.1 % 185 126
Test Date: 2026-07-12 23:15:38 Functions: 86.7 % 15 13
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 54.6 % 130 71

             Branch data     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                 :        7989 : open_target_file(const char *path, bool trunc)
      48                 :             : {
      49                 :             :     int         mode;
      50                 :             : 
      51         [ -  + ]:        7989 :     if (!path_is_safe_for_extraction(path))
      52                 :           0 :         pg_fatal("target file path is unsafe for open: \"%s\"", path);
      53                 :             : 
      54         [ +  + ]:        7989 :     if (dry_run)
      55                 :         269 :         return;
      56                 :             : 
      57   [ +  +  +  + ]:        7720 :     if (dstfd != -1 && !trunc &&
      58         [ +  + ]:        3233 :         strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
      59                 :         828 :         return;                 /* already open */
      60                 :             : 
      61                 :        6892 :     close_target_file();
      62                 :             : 
      63                 :        6892 :     snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
      64                 :             : 
      65                 :        6892 :     mode = O_WRONLY | O_CREAT | PG_BINARY;
      66         [ +  + ]:        6892 :     if (trunc)
      67                 :        4487 :         mode |= O_TRUNC;
      68                 :        6892 :     dstfd = open(dstpath, mode, pg_file_create_mode);
      69         [ -  + ]:        6892 :     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                 :        6920 : close_target_file(void)
      79                 :             : {
      80         [ +  + ]:        6920 :     if (dstfd == -1)
      81                 :          29 :         return;
      82                 :             : 
      83         [ -  + ]:        6891 :     if (close(dstfd) != 0)
      84                 :           0 :         pg_fatal("could not close target file \"%s\": %m",
      85                 :             :                  dstpath);
      86                 :             : 
      87                 :        6891 :     dstfd = -1;
      88                 :             : }
      89                 :             : 
      90                 :             : void
      91                 :       50193 : 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                 :       50193 :     fetch_done += size;
      98                 :       50193 :     progress_report(false);
      99                 :             : 
     100         [ +  + ]:       50193 :     if (dry_run)
     101                 :        4691 :         return;
     102                 :             : 
     103         [ -  + ]:       45502 :     if (lseek(dstfd, begin, SEEK_SET) == -1)
     104                 :           0 :         pg_fatal("could not seek in target file \"%s\": %m",
     105                 :             :                  dstpath);
     106                 :             : 
     107                 :       45502 :     writeleft = size;
     108                 :       45502 :     p = buf;
     109         [ +  + ]:       90941 :     while (writeleft > 0)
     110                 :             :     {
     111                 :             :         ssize_t     writelen;
     112                 :             : 
     113                 :       45439 :         errno = 0;
     114                 :       45439 :         writelen = write(dstfd, p, writeleft);
     115         [ -  + ]:       45439 :         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                 :       45439 :         p += writelen;
     125                 :       45439 :         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