Age Owner Branch data TLA 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 : : #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
1152 michael@paquier.xyz 49 :CBC 155 : copydir(const char *fromdir, const char *todir, bool recurse)
50 : : {
51 : : DIR *xldir;
52 : : struct dirent *xlde;
53 : : char fromfile[MAXPGPATH * 2];
54 : : char tofile[MAXPGPATH * 2];
55 : :
2899 sfrost@snowman.net 56 [ - + ]: 155 : if (MakePGDirectory(todir) != 0)
7530 tgl@sss.pgh.pa.us 57 [ # # ]:UBC 0 : ereport(ERROR,
58 : : (errcode_for_file_access(),
59 : : errmsg("could not create directory \"%s\": %m", todir)));
60 : :
8056 tgl@sss.pgh.pa.us 61 :CBC 155 : xldir = AllocateDir(fromdir);
62 : :
7530 63 [ + + ]: 47028 : while ((xlde = ReadDir(xldir, fromdir)) != NULL)
64 : : {
65 : : PGFileType xlde_type;
66 : :
67 : : /* If we got a cancel signal during the copy of the directory, quit */
5731 bruce@momjian.us 68 [ - + ]: 46873 : CHECK_FOR_INTERRUPTS();
69 : :
7456 70 [ + + ]: 46873 : if (strcmp(xlde->d_name, ".") == 0 ||
7530 tgl@sss.pgh.pa.us 71 [ + + ]: 46718 : strcmp(xlde->d_name, "..") == 0)
7456 bruce@momjian.us 72 : 310 : continue;
73 : :
3260 peter_e@gmx.net 74 : 46563 : snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
75 : 46563 : snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
76 : :
1290 michael@paquier.xyz 77 : 46563 : xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
78 : :
79 [ - + ]: 46563 : if (xlde_type == PGFILETYPE_DIR)
80 : : {
81 : : /* recurse to handle subdirectories */
7530 tgl@sss.pgh.pa.us 82 [ # # ]:UBC 0 : if (recurse)
83 : 0 : copydir(fromfile, tofile, true);
84 : : }
1290 michael@paquier.xyz 85 [ + - ]:CBC 46563 : else if (xlde_type == PGFILETYPE_REG)
86 : : {
341 tmunro@postgresql.or 87 [ - + ]: 46563 : if (file_copy_method == FILE_COPY_METHOD_CLONE)
341 tmunro@postgresql.or 88 :UBC 0 : clone_file(fromfile, tofile);
89 : : else
341 tmunro@postgresql.or 90 :CBC 46563 : copy_file(fromfile, tofile);
91 : : }
92 : : }
5865 tgl@sss.pgh.pa.us 93 : 155 : 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 : : */
4985 99 [ + - ]: 155 : if (!enableFsync)
100 : 155 : return;
101 : :
5865 tgl@sss.pgh.pa.us 102 :UBC 0 : xldir = AllocateDir(todir);
103 : :
104 [ # # ]: 0 : while ((xlde = ReadDir(xldir, todir)) != NULL)
105 : : {
5872 stark@mit.edu 106 [ # # ]: 0 : if (strcmp(xlde->d_name, ".") == 0 ||
107 [ # # ]: 0 : strcmp(xlde->d_name, "..") == 0)
108 : 0 : continue;
109 : :
3260 peter_e@gmx.net 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 : : */
1290 michael@paquier.xyz 116 [ # # ]: 0 : if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
5859 stark@mit.edu 117 : 0 : fsync_fname(tofile, false);
118 : : }
5872 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 : : */
5859 127 : 0 : fsync_fname(todir, true);
128 : : }
129 : :
130 : : /*
131 : : * copy one file
132 : : */
133 : : void
1152 michael@paquier.xyz 134 :CBC 46572 : copy_file(const char *fromfile, const char *tofile)
135 : : {
136 : : char *buffer;
137 : : int srcfd;
138 : : int dstfd;
139 : : int nbytes;
140 : : off_t offset;
141 : : off_t flush_offset;
142 : :
143 : : /* Size of copy buffer (read and write requests) */
144 : : #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 : : #define FLUSH_DISTANCE (1024 * 1024)
156 : : #endif
157 : :
158 : : /* Use palloc to ensure we get a maxaligned buffer */
7499 tgl@sss.pgh.pa.us 159 : 46572 : buffer = palloc(COPY_BUF_SIZE);
160 : :
161 : : /*
162 : : * Open the files
163 : : */
3095 peter_e@gmx.net 164 : 46572 : srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
7530 tgl@sss.pgh.pa.us 165 [ - + ]: 46572 : if (srcfd < 0)
7530 tgl@sss.pgh.pa.us 166 [ # # ]:UBC 0 : ereport(ERROR,
167 : : (errcode_for_file_access(),
168 : : errmsg("could not open file \"%s\": %m", fromfile)));
169 : :
3095 peter_e@gmx.net 170 :CBC 46572 : dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
7530 tgl@sss.pgh.pa.us 171 [ - + ]: 46572 : if (dstfd < 0)
7530 tgl@sss.pgh.pa.us 172 [ # # ]:UBC 0 : ereport(ERROR,
173 : : (errcode_for_file_access(),
174 : : errmsg("could not create file \"%s\": %m", tofile)));
175 : :
176 : : /*
177 : : * Do the data copying.
178 : : */
3080 tgl@sss.pgh.pa.us 179 :CBC 46572 : flush_offset = 0;
5861 bruce@momjian.us 180 : 46572 : for (offset = 0;; offset += nbytes)
181 : : {
182 : : /* If we got a cancel signal during the copy of the file, quit */
5731 183 [ - + ]: 93738 : 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 : : */
3080 tgl@sss.pgh.pa.us 190 [ + + ]: 93738 : if (offset - flush_offset >= FLUSH_DISTANCE)
191 : : {
192 : 32 : pg_flush_data(dstfd, flush_offset, offset - flush_offset);
193 : 32 : flush_offset = offset;
194 : : }
195 : :
3284 rhaas@postgresql.org 196 : 93738 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
7499 tgl@sss.pgh.pa.us 197 : 93738 : nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
3284 rhaas@postgresql.org 198 : 93738 : pgstat_report_wait_end();
7530 tgl@sss.pgh.pa.us 199 [ - + ]: 93738 : if (nbytes < 0)
7530 tgl@sss.pgh.pa.us 200 [ # # ]:UBC 0 : ereport(ERROR,
201 : : (errcode_for_file_access(),
202 : : errmsg("could not read file \"%s\": %m", fromfile)));
7530 tgl@sss.pgh.pa.us 203 [ + + ]:CBC 93738 : if (nbytes == 0)
204 : 46572 : break;
205 : 47166 : errno = 0;
3284 rhaas@postgresql.org 206 : 47166 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
7530 tgl@sss.pgh.pa.us 207 [ - + ]: 47166 : if ((int) write(dstfd, buffer, nbytes) != nbytes)
208 : : {
209 : : /* if write didn't set errno, assume problem is no disk space */
7530 tgl@sss.pgh.pa.us 210 [ # # ]:UBC 0 : if (errno == 0)
211 : 0 : errno = ENOSPC;
212 [ # # ]: 0 : ereport(ERROR,
213 : : (errcode_for_file_access(),
214 : : errmsg("could not write to file \"%s\": %m", tofile)));
215 : : }
3284 rhaas@postgresql.org 216 :CBC 47166 : pgstat_report_wait_end();
217 : : }
218 : :
3080 tgl@sss.pgh.pa.us 219 [ + + ]: 46572 : if (offset > flush_offset)
220 : 38504 : pg_flush_data(dstfd, flush_offset, offset - flush_offset);
221 : :
2444 peter@eisentraut.org 222 [ - + ]: 46572 : if (CloseTransientFile(dstfd) != 0)
7530 tgl@sss.pgh.pa.us 223 [ # # ]:UBC 0 : ereport(ERROR,
224 : : (errcode_for_file_access(),
225 : : errmsg("could not close file \"%s\": %m", tofile)));
226 : :
2444 peter@eisentraut.org 227 [ - + ]:CBC 46572 : if (CloseTransientFile(srcfd) != 0)
2563 michael@paquier.xyz 228 [ # # ]:UBC 0 : ereport(ERROR,
229 : : (errcode_for_file_access(),
230 : : errmsg("could not close file \"%s\": %m", fromfile)));
231 : :
7499 tgl@sss.pgh.pa.us 232 :CBC 46572 : pfree(buffer);
8340 bruce@momjian.us 233 : 46572 : }
234 : :
235 : : /*
236 : : * clone one file
237 : : */
238 : : static void
341 tmunro@postgresql.or 239 :UBC 0 : clone_file(const char *fromfile, const char *tofile)
240 : : {
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 : : int dstfd;
250 : : ssize_t nbytes;
251 : :
252 : 0 : srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
253 [ # # ]: 0 : if (srcfd < 0)
254 [ # # ]: 0 : ereport(ERROR,
255 : : (errcode_for_file_access(),
256 : : 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 : : (errcode_for_file_access(),
262 : : errmsg("could not create file \"%s\": %m", tofile)));
263 : :
264 : : do
265 : : {
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 : : (errcode_for_file_access(),
276 : : errmsg("could not clone file \"%s\" to \"%s\": %m",
277 : : fromfile, tofile)));
278 : 0 : pgstat_report_wait_end();
279 : : }
280 [ # # ]: 0 : while (nbytes != 0);
281 : :
282 [ # # ]: 0 : if (CloseTransientFile(dstfd) != 0)
283 [ # # ]: 0 : ereport(ERROR,
284 : : (errcode_for_file_access(),
285 : : errmsg("could not close file \"%s\": %m", tofile)));
286 : :
287 [ # # ]: 0 : if (CloseTransientFile(srcfd) != 0)
288 [ # # ]: 0 : ereport(ERROR,
289 : : (errcode_for_file_access(),
290 : : 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 : }
|