/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 | } |