Age Owner Branch data TLA 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
962 michael@paquier.xyz 48 :CBC 146 : 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 : :
2709 sfrost@snowman.net 55 [ - + ]: 146 : if (MakePGDirectory(todir) != 0)
7340 tgl@sss.pgh.pa.us 56 [ # # ]:UBC 0 : ereport(ERROR,
57 : : (errcode_for_file_access(),
58 : : errmsg("could not create directory \"%s\": %m", todir)));
59 : :
7866 tgl@sss.pgh.pa.us 60 :CBC 146 : xldir = AllocateDir(fromdir);
61 : :
7340 62 [ + + ]: 44284 : 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 */
5541 bruce@momjian.us 67 [ - + ]: 44138 : CHECK_FOR_INTERRUPTS();
68 : :
7266 69 [ + + ]: 44138 : if (strcmp(xlde->d_name, ".") == 0 ||
7340 tgl@sss.pgh.pa.us 70 [ + + ]: 43992 : strcmp(xlde->d_name, "..") == 0)
7266 bruce@momjian.us 71 : 292 : continue;
72 : :
3070 peter_e@gmx.net 73 : 43846 : snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
74 : 43846 : snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
75 : :
1100 michael@paquier.xyz 76 : 43846 : xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
77 : :
78 [ - + ]: 43846 : if (xlde_type == PGFILETYPE_DIR)
79 : : {
80 : : /* recurse to handle subdirectories */
7340 tgl@sss.pgh.pa.us 81 [ # # ]:UBC 0 : if (recurse)
82 : 0 : copydir(fromfile, tofile, true);
83 : : }
1100 michael@paquier.xyz 84 [ + - ]:CBC 43846 : else if (xlde_type == PGFILETYPE_REG)
85 : : {
151 tmunro@postgresql.or 86 [ - + ]: 43846 : if (file_copy_method == FILE_COPY_METHOD_CLONE)
151 tmunro@postgresql.or 87 :UBC 0 : clone_file(fromfile, tofile);
88 : : else
151 tmunro@postgresql.or 89 :CBC 43846 : copy_file(fromfile, tofile);
90 : : }
91 : : }
5675 tgl@sss.pgh.pa.us 92 : 146 : 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 : : */
4795 98 [ + - ]: 146 : if (!enableFsync)
99 : 146 : return;
100 : :
5675 tgl@sss.pgh.pa.us 101 :UBC 0 : xldir = AllocateDir(todir);
102 : :
103 [ # # ]: 0 : while ((xlde = ReadDir(xldir, todir)) != NULL)
104 : : {
5682 stark@mit.edu 105 [ # # ]: 0 : if (strcmp(xlde->d_name, ".") == 0 ||
106 [ # # ]: 0 : strcmp(xlde->d_name, "..") == 0)
107 : 0 : continue;
108 : :
3070 peter_e@gmx.net 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 : : */
1100 michael@paquier.xyz 115 [ # # ]: 0 : if (get_dirent_type(tofile, xlde, false, ERROR) == PGFILETYPE_REG)
5669 stark@mit.edu 116 : 0 : fsync_fname(tofile, false);
117 : : }
5682 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 : : */
5669 126 : 0 : fsync_fname(todir, true);
127 : : }
128 : :
129 : : /*
130 : : * copy one file
131 : : */
132 : : void
962 michael@paquier.xyz 133 :CBC 43855 : 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 */
7309 tgl@sss.pgh.pa.us 158 : 43855 : buffer = palloc(COPY_BUF_SIZE);
159 : :
160 : : /*
161 : : * Open the files
162 : : */
2905 peter_e@gmx.net 163 : 43855 : srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
7340 tgl@sss.pgh.pa.us 164 [ - + ]: 43855 : if (srcfd < 0)
7340 tgl@sss.pgh.pa.us 165 [ # # ]:UBC 0 : ereport(ERROR,
166 : : (errcode_for_file_access(),
167 : : errmsg("could not open file \"%s\": %m", fromfile)));
168 : :
2905 peter_e@gmx.net 169 :CBC 43855 : dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
7340 tgl@sss.pgh.pa.us 170 [ - + ]: 43855 : if (dstfd < 0)
7340 tgl@sss.pgh.pa.us 171 [ # # ]:UBC 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 : : */
2890 tgl@sss.pgh.pa.us 178 :CBC 43855 : flush_offset = 0;
5671 bruce@momjian.us 179 : 43855 : for (offset = 0;; offset += nbytes)
180 : : {
181 : : /* If we got a cancel signal during the copy of the file, quit */
5541 182 [ - + ]: 88310 : 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 : : */
2890 tgl@sss.pgh.pa.us 189 [ + + ]: 88310 : 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 : :
3094 rhaas@postgresql.org 195 : 88310 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
7309 tgl@sss.pgh.pa.us 196 : 88310 : nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
3094 rhaas@postgresql.org 197 : 88310 : pgstat_report_wait_end();
7340 tgl@sss.pgh.pa.us 198 [ - + ]: 88310 : if (nbytes < 0)
7340 tgl@sss.pgh.pa.us 199 [ # # ]:UBC 0 : ereport(ERROR,
200 : : (errcode_for_file_access(),
201 : : errmsg("could not read file \"%s\": %m", fromfile)));
7340 tgl@sss.pgh.pa.us 202 [ + + ]:CBC 88310 : if (nbytes == 0)
203 : 43855 : break;
204 : 44455 : errno = 0;
3094 rhaas@postgresql.org 205 : 44455 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
7340 tgl@sss.pgh.pa.us 206 [ - + ]: 44455 : if ((int) write(dstfd, buffer, nbytes) != nbytes)
207 : : {
208 : : /* if write didn't set errno, assume problem is no disk space */
7340 tgl@sss.pgh.pa.us 209 [ # # ]:UBC 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 : : }
3094 rhaas@postgresql.org 215 :CBC 44455 : pgstat_report_wait_end();
216 : : }
217 : :
2890 tgl@sss.pgh.pa.us 218 [ + + ]: 43855 : if (offset > flush_offset)
219 : 36263 : pg_flush_data(dstfd, flush_offset, offset - flush_offset);
220 : :
2254 peter@eisentraut.org 221 [ - + ]: 43855 : if (CloseTransientFile(dstfd) != 0)
7340 tgl@sss.pgh.pa.us 222 [ # # ]:UBC 0 : ereport(ERROR,
223 : : (errcode_for_file_access(),
224 : : errmsg("could not close file \"%s\": %m", tofile)));
225 : :
2254 peter@eisentraut.org 226 [ - + ]:CBC 43855 : if (CloseTransientFile(srcfd) != 0)
2373 michael@paquier.xyz 227 [ # # ]:UBC 0 : ereport(ERROR,
228 : : (errcode_for_file_access(),
229 : : errmsg("could not close file \"%s\": %m", fromfile)));
230 : :
7309 tgl@sss.pgh.pa.us 231 :CBC 43855 : pfree(buffer);
8150 bruce@momjian.us 232 : 43855 : }
233 : :
234 : : /*
235 : : * clone one file
236 : : */
237 : : static void
151 tmunro@postgresql.or 238 :UBC 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 : }
|