LCOV - code coverage report
Current view: top level - src/backend/storage/file - copydir.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 52.9 % 87 46
Test Date: 2026-02-28 23:15:01 Functions: 66.7 % 3 2
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * copydir.c
       4              :  *    copies a directory
       5              :  *
       6              :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7              :  * Portions Copyright (c) 1994, Regents of the University of California
       8              :  *
       9              :  *  While "xcopy /e /i /q" works fine for copying directories, on Windows XP
      10              :  *  it requires a Window handle which prevents it from working when invoked
      11              :  *  as a service.
      12              :  *
      13              :  * IDENTIFICATION
      14              :  *    src/backend/storage/file/copydir.c
      15              :  *
      16              :  *-------------------------------------------------------------------------
      17              :  */
      18              : 
      19              : #include "postgres.h"
      20              : 
      21              : #ifdef HAVE_COPYFILE_H
      22              : #include <copyfile.h>
      23              : #endif
      24              : #include <fcntl.h>
      25              : #include <unistd.h>
      26              : 
      27              : #include "common/file_utils.h"
      28              : #include "miscadmin.h"
      29              : #include "pgstat.h"
      30              : #include "storage/copydir.h"
      31              : #include "storage/fd.h"
      32              : 
      33              : /* GUCs */
      34              : int         file_copy_method = FILE_COPY_METHOD_COPY;
      35              : 
      36              : static void clone_file(const char *fromfile, const char *tofile);
      37              : 
      38              : /*
      39              :  * copydir: copy a directory
      40              :  *
      41              :  * If recurse is false, subdirectories are ignored.  Anything that's not
      42              :  * a directory or a regular file is ignored.
      43              :  *
      44              :  * This function uses the file_copy_method GUC.  New uses of this function must
      45              :  * be documented in doc/src/sgml/config.sgml.
      46              :  */
      47              : void
      48          155 : copydir(const char *fromdir, const char *todir, bool recurse)
      49              : {
      50              :     DIR        *xldir;
      51              :     struct dirent *xlde;
      52              :     char        fromfile[MAXPGPATH * 2];
      53              :     char        tofile[MAXPGPATH * 2];
      54              : 
      55          155 :     if (MakePGDirectory(todir) != 0)
      56            0 :         ereport(ERROR,
      57              :                 (errcode_for_file_access(),
      58              :                  errmsg("could not create directory \"%s\": %m", todir)));
      59              : 
      60          155 :     xldir = AllocateDir(fromdir);
      61              : 
      62        47028 :     while ((xlde = ReadDir(xldir, fromdir)) != NULL)
      63              :     {
      64              :         PGFileType  xlde_type;
      65              : 
      66              :         /* If we got a cancel signal during the copy of the directory, quit */
      67        46873 :         CHECK_FOR_INTERRUPTS();
      68              : 
      69        46873 :         if (strcmp(xlde->d_name, ".") == 0 ||
      70        46718 :             strcmp(xlde->d_name, "..") == 0)
      71          310 :             continue;
      72              : 
      73        46563 :         snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
      74        46563 :         snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
      75              : 
      76        46563 :         xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
      77              : 
      78        46563 :         if (xlde_type == PGFILETYPE_DIR)
      79              :         {
      80              :             /* recurse to handle subdirectories */
      81            0 :             if (recurse)
      82            0 :                 copydir(fromfile, tofile, true);
      83              :         }
      84        46563 :         else if (xlde_type == PGFILETYPE_REG)
      85              :         {
      86        46563 :             if (file_copy_method == FILE_COPY_METHOD_CLONE)
      87            0 :                 clone_file(fromfile, tofile);
      88              :             else
      89        46563 :                 copy_file(fromfile, tofile);
      90              :         }
      91              :     }
      92          155 :     FreeDir(xldir);
      93              : 
      94              :     /*
      95              :      * Be paranoid here and fsync all files to ensure the copy is really done.
      96              :      * But if fsync is disabled, we're done.
      97              :      */
      98          155 :     if (!enableFsync)
      99          155 :         return;
     100              : 
     101            0 :     xldir = AllocateDir(todir);
     102              : 
     103            0 :     while ((xlde = ReadDir(xldir, todir)) != NULL)
     104              :     {
     105            0 :         if (strcmp(xlde->d_name, ".") == 0 ||
     106            0 :             strcmp(xlde->d_name, "..") == 0)
     107            0 :             continue;
     108              : 
     109            0 :         snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
     110              : 
     111              :         /*
     112              :          * We don't need to sync subdirectories here since the recursive
     113              :          * copydir will do it before it returns
     114              :          */
     115            0 :         if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
     116            0 :             fsync_fname(tofile, false);
     117              :     }
     118            0 :     FreeDir(xldir);
     119              : 
     120              :     /*
     121              :      * It's important to fsync the destination directory itself as individual
     122              :      * file fsyncs don't guarantee that the directory entry for the file is
     123              :      * synced. Recent versions of ext4 have made the window much wider but
     124              :      * it's been true for ext3 and other filesystems in the past.
     125              :      */
     126            0 :     fsync_fname(todir, true);
     127              : }
     128              : 
     129              : /*
     130              :  * copy one file
     131              :  */
     132              : void
     133        46572 : copy_file(const char *fromfile, const char *tofile)
     134              : {
     135              :     char       *buffer;
     136              :     int         srcfd;
     137              :     int         dstfd;
     138              :     int         nbytes;
     139              :     off_t       offset;
     140              :     off_t       flush_offset;
     141              : 
     142              :     /* Size of copy buffer (read and write requests) */
     143              : #define COPY_BUF_SIZE (8 * BLCKSZ)
     144              : 
     145              :     /*
     146              :      * Size of data flush requests.  It seems beneficial on most platforms to
     147              :      * do this every 1MB or so.  But macOS, at least with early releases of
     148              :      * APFS, is really unfriendly to small mmap/msync requests, so there do it
     149              :      * only every 32MB.
     150              :      */
     151              : #if defined(__darwin__)
     152              : #define FLUSH_DISTANCE (32 * 1024 * 1024)
     153              : #else
     154              : #define FLUSH_DISTANCE (1024 * 1024)
     155              : #endif
     156              : 
     157              :     /* Use palloc to ensure we get a maxaligned buffer */
     158        46572 :     buffer = palloc(COPY_BUF_SIZE);
     159              : 
     160              :     /*
     161              :      * Open the files
     162              :      */
     163        46572 :     srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
     164        46572 :     if (srcfd < 0)
     165            0 :         ereport(ERROR,
     166              :                 (errcode_for_file_access(),
     167              :                  errmsg("could not open file \"%s\": %m", fromfile)));
     168              : 
     169        46572 :     dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
     170        46572 :     if (dstfd < 0)
     171            0 :         ereport(ERROR,
     172              :                 (errcode_for_file_access(),
     173              :                  errmsg("could not create file \"%s\": %m", tofile)));
     174              : 
     175              :     /*
     176              :      * Do the data copying.
     177              :      */
     178        46572 :     flush_offset = 0;
     179        46572 :     for (offset = 0;; offset += nbytes)
     180              :     {
     181              :         /* If we got a cancel signal during the copy of the file, quit */
     182        93280 :         CHECK_FOR_INTERRUPTS();
     183              : 
     184              :         /*
     185              :          * We fsync the files later, but during the copy, flush them every so
     186              :          * often to avoid spamming the cache and hopefully get the kernel to
     187              :          * start writing them out before the fsync comes.
     188              :          */
     189        93280 :         if (offset - flush_offset >= FLUSH_DISTANCE)
     190              :         {
     191           32 :             pg_flush_data(dstfd, flush_offset, offset - flush_offset);
     192           32 :             flush_offset = offset;
     193              :         }
     194              : 
     195        93280 :         pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
     196        93280 :         nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
     197        93280 :         pgstat_report_wait_end();
     198        93280 :         if (nbytes < 0)
     199            0 :             ereport(ERROR,
     200              :                     (errcode_for_file_access(),
     201              :                      errmsg("could not read file \"%s\": %m", fromfile)));
     202        93280 :         if (nbytes == 0)
     203        46572 :             break;
     204        46708 :         errno = 0;
     205        46708 :         pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
     206        46708 :         if ((int) write(dstfd, buffer, nbytes) != nbytes)
     207              :         {
     208              :             /* if write didn't set errno, assume problem is no disk space */
     209            0 :             if (errno == 0)
     210            0 :                 errno = ENOSPC;
     211            0 :             ereport(ERROR,
     212              :                     (errcode_for_file_access(),
     213              :                      errmsg("could not write to file \"%s\": %m", tofile)));
     214              :         }
     215        46708 :         pgstat_report_wait_end();
     216              :     }
     217              : 
     218        46572 :     if (offset > flush_offset)
     219        38504 :         pg_flush_data(dstfd, flush_offset, offset - flush_offset);
     220              : 
     221        46572 :     if (CloseTransientFile(dstfd) != 0)
     222            0 :         ereport(ERROR,
     223              :                 (errcode_for_file_access(),
     224              :                  errmsg("could not close file \"%s\": %m", tofile)));
     225              : 
     226        46572 :     if (CloseTransientFile(srcfd) != 0)
     227            0 :         ereport(ERROR,
     228              :                 (errcode_for_file_access(),
     229              :                  errmsg("could not close file \"%s\": %m", fromfile)));
     230              : 
     231        46572 :     pfree(buffer);
     232        46572 : }
     233              : 
     234              : /*
     235              :  * clone one file
     236              :  */
     237              : static void
     238            0 : clone_file(const char *fromfile, const char *tofile)
     239              : {
     240              : #if defined(HAVE_COPYFILE) && defined(COPYFILE_CLONE_FORCE)
     241              :     if (copyfile(fromfile, tofile, NULL, COPYFILE_CLONE_FORCE) < 0)
     242              :         ereport(ERROR,
     243              :                 (errcode_for_file_access(),
     244              :                  errmsg("could not clone file \"%s\" to \"%s\": %m",
     245              :                         fromfile, tofile)));
     246              : #elif defined(HAVE_COPY_FILE_RANGE)
     247              :     int         srcfd;
     248              :     int         dstfd;
     249              :     ssize_t     nbytes;
     250              : 
     251            0 :     srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
     252            0 :     if (srcfd < 0)
     253            0 :         ereport(ERROR,
     254              :                 (errcode_for_file_access(),
     255              :                  errmsg("could not open file \"%s\": %m", fromfile)));
     256              : 
     257            0 :     dstfd = OpenTransientFile(tofile, O_WRONLY | O_CREAT | O_EXCL | PG_BINARY);
     258            0 :     if (dstfd < 0)
     259            0 :         ereport(ERROR,
     260              :                 (errcode_for_file_access(),
     261              :                  errmsg("could not create file \"%s\": %m", tofile)));
     262              : 
     263              :     do
     264              :     {
     265              :         /*
     266              :          * Don't copy too much at once, so we can check for interrupts from
     267              :          * time to time if it falls back to a slow copy.
     268              :          */
     269            0 :         CHECK_FOR_INTERRUPTS();
     270            0 :         pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_COPY);
     271            0 :         nbytes = copy_file_range(srcfd, NULL, dstfd, NULL, 1024 * 1024, 0);
     272            0 :         if (nbytes < 0 && errno != EINTR)
     273            0 :             ereport(ERROR,
     274              :                     (errcode_for_file_access(),
     275              :                      errmsg("could not clone file \"%s\" to \"%s\": %m",
     276              :                             fromfile, tofile)));
     277            0 :         pgstat_report_wait_end();
     278              :     }
     279            0 :     while (nbytes != 0);
     280              : 
     281            0 :     if (CloseTransientFile(dstfd) != 0)
     282            0 :         ereport(ERROR,
     283              :                 (errcode_for_file_access(),
     284              :                  errmsg("could not close file \"%s\": %m", tofile)));
     285              : 
     286            0 :     if (CloseTransientFile(srcfd) != 0)
     287            0 :         ereport(ERROR,
     288              :                 (errcode_for_file_access(),
     289              :                  errmsg("could not close file \"%s\": %m", fromfile)));
     290              : #else
     291              :     /* If there is no CLONE support this function should not be called. */
     292              :     pg_unreachable();
     293              : #endif
     294            0 : }
        

Generated by: LCOV version 2.0-1