LCOV - code coverage report
Current view: top level - src/backend/storage/file - copydir.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 46 87 52.9 %
Date: 2025-04-24 12:15:10 Functions: 2 3 66.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * copydir.c
       4             :  *    copies a directory
       5             :  *
       6             :  * Portions Copyright (c) 1996-2025, 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         280 : 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         280 :     if (MakePGDirectory(todir) != 0)
      56           0 :         ereport(ERROR,
      57             :                 (errcode_for_file_access(),
      58             :                  errmsg("could not create directory \"%s\": %m", todir)));
      59             : 
      60         280 :     xldir = AllocateDir(fromdir);
      61             : 
      62       84932 :     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       84652 :         CHECK_FOR_INTERRUPTS();
      68             : 
      69       84652 :         if (strcmp(xlde->d_name, ".") == 0 ||
      70       84372 :             strcmp(xlde->d_name, "..") == 0)
      71         560 :             continue;
      72             : 
      73       84092 :         snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
      74       84092 :         snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
      75             : 
      76       84092 :         xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
      77             : 
      78       84092 :         if (xlde_type == PGFILETYPE_DIR)
      79             :         {
      80             :             /* recurse to handle subdirectories */
      81           0 :             if (recurse)
      82           0 :                 copydir(fromfile, tofile, true);
      83             :         }
      84       84092 :         else if (xlde_type == PGFILETYPE_REG)
      85             :         {
      86       84092 :             if (file_copy_method == FILE_COPY_METHOD_CLONE)
      87           0 :                 clone_file(fromfile, tofile);
      88             :             else
      89       84092 :                 copy_file(fromfile, tofile);
      90             :         }
      91             :     }
      92         280 :     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         280 :     if (!enableFsync)
      99         280 :         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       84110 : 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       84110 :     buffer = palloc(COPY_BUF_SIZE);
     159             : 
     160             :     /*
     161             :      * Open the files
     162             :      */
     163       84110 :     srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
     164       84110 :     if (srcfd < 0)
     165           0 :         ereport(ERROR,
     166             :                 (errcode_for_file_access(),
     167             :                  errmsg("could not open file \"%s\": %m", fromfile)));
     168             : 
     169       84110 :     dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
     170       84110 :     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       84110 :     flush_offset = 0;
     179      168580 :     for (offset = 0;; offset += nbytes)
     180             :     {
     181             :         /* If we got a cancel signal during the copy of the file, quit */
     182      168580 :         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      168580 :         if (offset - flush_offset >= FLUSH_DISTANCE)
     190             :         {
     191          64 :             pg_flush_data(dstfd, flush_offset, offset - flush_offset);
     192          64 :             flush_offset = offset;
     193             :         }
     194             : 
     195      168580 :         pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
     196      168580 :         nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
     197      168580 :         pgstat_report_wait_end();
     198      168580 :         if (nbytes < 0)
     199           0 :             ereport(ERROR,
     200             :                     (errcode_for_file_access(),
     201             :                      errmsg("could not read file \"%s\": %m", fromfile)));
     202      168580 :         if (nbytes == 0)
     203       84110 :             break;
     204       84470 :         errno = 0;
     205       84470 :         pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
     206       84470 :         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       84470 :         pgstat_report_wait_end();
     216             :     }
     217             : 
     218       84110 :     if (offset > flush_offset)
     219       69550 :         pg_flush_data(dstfd, flush_offset, offset - flush_offset);
     220             : 
     221       84110 :     if (CloseTransientFile(dstfd) != 0)
     222           0 :         ereport(ERROR,
     223             :                 (errcode_for_file_access(),
     224             :                  errmsg("could not close file \"%s\": %m", tofile)));
     225             : 
     226       84110 :     if (CloseTransientFile(srcfd) != 0)
     227           0 :         ereport(ERROR,
     228             :                 (errcode_for_file_access(),
     229             :                  errmsg("could not close file \"%s\": %m", fromfile)));
     230             : 
     231       84110 :     pfree(buffer);
     232       84110 : }
     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 1.14