Branch data 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 : 4 : copydir(const char *fromdir, const char *todir, bool recurse)
49 : : {
50 : 4 : DIR *xldir;
51 : 4 : struct dirent *xlde;
52 : 4 : char fromfile[MAXPGPATH * 2];
53 : 4 : char tofile[MAXPGPATH * 2];
54 : :
55 [ + - ]: 4 : if (MakePGDirectory(todir) != 0)
56 [ # # # # ]: 0 : ereport(ERROR,
57 : : (errcode_for_file_access(),
58 : : errmsg("could not create directory \"%s\": %m", todir)));
59 : :
60 : 4 : xldir = AllocateDir(fromdir);
61 : :
62 [ + + ]: 1212 : while ((xlde = ReadDir(xldir, fromdir)) != NULL)
63 : : {
64 : 1208 : PGFileType xlde_type;
65 : :
66 : : /* If we got a cancel signal during the copy of the directory, quit */
67 [ + - ]: 1208 : CHECK_FOR_INTERRUPTS();
68 : :
69 [ + + + + ]: 1208 : if (strcmp(xlde->d_name, ".") == 0 ||
70 : 1204 : strcmp(xlde->d_name, "..") == 0)
71 : 8 : continue;
72 : :
73 : 1200 : snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
74 : 1200 : snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
75 : :
76 : 1200 : xlde_type = get_dirent_type(fromfile, xlde, false, ERROR);
77 : :
78 [ - + ]: 1200 : if (xlde_type == PGFILETYPE_DIR)
79 : : {
80 : : /* recurse to handle subdirectories */
81 [ # # ]: 0 : if (recurse)
82 : 0 : copydir(fromfile, tofile, true);
83 : 0 : }
84 [ - + ]: 1200 : else if (xlde_type == PGFILETYPE_REG)
85 : : {
86 [ - + ]: 1200 : if (file_copy_method == FILE_COPY_METHOD_CLONE)
87 : 0 : clone_file(fromfile, tofile);
88 : : else
89 : 1200 : copy_file(fromfile, tofile);
90 : 1200 : }
91 [ + + ]: 1208 : }
92 : 4 : 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 [ - + ]: 4 : if (!enableFsync)
99 : 4 : 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 : 4 : }
128 : :
129 : : /*
130 : : * copy one file
131 : : */
132 : : void
133 : 1200 : copy_file(const char *fromfile, const char *tofile)
134 : : {
135 : 1200 : char *buffer;
136 : 1200 : int srcfd;
137 : 1200 : int dstfd;
138 : 1200 : int nbytes;
139 : 1200 : off_t offset;
140 : 1200 : 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 : 1200 : buffer = palloc(COPY_BUF_SIZE);
159 : :
160 : : /*
161 : : * Open the files
162 : : */
163 : 1200 : srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
164 [ + - ]: 1200 : if (srcfd < 0)
165 [ # # # # ]: 0 : ereport(ERROR,
166 : : (errcode_for_file_access(),
167 : : errmsg("could not open file \"%s\": %m", fromfile)));
168 : :
169 : 1200 : dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
170 [ + - ]: 1200 : 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 : 1200 : flush_offset = 0;
179 : 2396 : for (offset = 0;; offset += nbytes)
180 : : {
181 : : /* If we got a cancel signal during the copy of the file, quit */
182 [ + - ]: 2396 : 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 [ + - ]: 2396 : if (offset - flush_offset >= FLUSH_DISTANCE)
190 : : {
191 : 0 : pg_flush_data(dstfd, flush_offset, offset - flush_offset);
192 : 0 : flush_offset = offset;
193 : 0 : }
194 : :
195 : 2396 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
196 : 2396 : nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
197 : 2396 : pgstat_report_wait_end();
198 [ + - ]: 2396 : if (nbytes < 0)
199 [ # # # # ]: 0 : ereport(ERROR,
200 : : (errcode_for_file_access(),
201 : : errmsg("could not read file \"%s\": %m", fromfile)));
202 [ + + ]: 2396 : if (nbytes == 0)
203 : 1200 : break;
204 : 1196 : errno = 0;
205 : 1196 : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
206 [ + - ]: 1196 : 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 : 0 : }
215 : 1196 : pgstat_report_wait_end();
216 : 1196 : }
217 : :
218 [ + + ]: 1200 : if (offset > flush_offset)
219 : 992 : pg_flush_data(dstfd, flush_offset, offset - flush_offset);
220 : :
221 [ + - ]: 1200 : if (CloseTransientFile(dstfd) != 0)
222 [ # # # # ]: 0 : ereport(ERROR,
223 : : (errcode_for_file_access(),
224 : : errmsg("could not close file \"%s\": %m", tofile)));
225 : :
226 [ + - ]: 1200 : if (CloseTransientFile(srcfd) != 0)
227 [ # # # # ]: 0 : ereport(ERROR,
228 : : (errcode_for_file_access(),
229 : : errmsg("could not close file \"%s\": %m", fromfile)));
230 : :
231 : 1200 : pfree(buffer);
232 : 1200 : }
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 [ # # ]: 0 : if (copyfile(fromfile, tofile, NULL, COPYFILE_CLONE_FORCE) < 0)
242 [ # # # # ]: 0 : 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 : : srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
252 : : if (srcfd < 0)
253 : : ereport(ERROR,
254 : : (errcode_for_file_access(),
255 : : errmsg("could not open file \"%s\": %m", fromfile)));
256 : :
257 : : dstfd = OpenTransientFile(tofile, O_WRONLY | O_CREAT | O_EXCL | PG_BINARY);
258 : : if (dstfd < 0)
259 : : 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 : : CHECK_FOR_INTERRUPTS();
270 : : pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_COPY);
271 : : nbytes = copy_file_range(srcfd, NULL, dstfd, NULL, 1024 * 1024, 0);
272 : : if (nbytes < 0 && errno != EINTR)
273 : : ereport(ERROR,
274 : : (errcode_for_file_access(),
275 : : errmsg("could not clone file \"%s\" to \"%s\": %m",
276 : : fromfile, tofile)));
277 : : pgstat_report_wait_end();
278 : : }
279 : : while (nbytes != 0);
280 : :
281 : : if (CloseTransientFile(dstfd) != 0)
282 : : ereport(ERROR,
283 : : (errcode_for_file_access(),
284 : : errmsg("could not close file \"%s\": %m", tofile)));
285 : :
286 : : if (CloseTransientFile(srcfd) != 0)
287 : : 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 : }
|