Coverage Report

Created: 2026-11-09 07:20

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