Age Owner Branch data TLA Line data Source code
1 : : /*
2 : : * Copy entire files.
3 : : *
4 : : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
5 : : * Portions Copyright (c) 1994, Regents of the University of California
6 : : *
7 : : * src/bin/pg_combinebackup/copy_file.h
8 : : *
9 : : *-------------------------------------------------------------------------
10 : : */
11 : : #include "postgres_fe.h"
12 : :
13 : : #ifdef HAVE_COPYFILE_H
14 : : #include <copyfile.h>
15 : : #endif
16 : : #ifdef __linux__
17 : : #include <sys/ioctl.h>
18 : : #include <linux/fs.h>
19 : : #endif
20 : : #include <fcntl.h>
21 : : #include <limits.h>
22 : : #include <sys/stat.h>
23 : : #include <unistd.h>
24 : :
25 : : #include "common/file_perm.h"
26 : : #include "common/logging.h"
27 : : #include "copy_file.h"
28 : :
29 : : static void copy_file_blocks(const char *src, const char *dst,
30 : : pg_checksum_context *checksum_ctx);
31 : :
32 : : static void copy_file_clone(const char *src, const char *dest,
33 : : pg_checksum_context *checksum_ctx);
34 : :
35 : : static void copy_file_by_range(const char *src, const char *dest,
36 : : pg_checksum_context *checksum_ctx);
37 : :
38 : : #ifdef WIN32
39 : : static void copy_file_copyfile(const char *src, const char *dst,
40 : : pg_checksum_context *checksum_ctx);
41 : : #endif
42 : :
43 : : static void copy_file_link(const char *src, const char *dest,
44 : : pg_checksum_context *checksum_ctx);
45 : :
46 : : /*
47 : : * Copy a regular file, optionally computing a checksum, and emitting
48 : : * appropriate debug messages. But if we're in dry-run mode, then just emit
49 : : * the messages and don't copy anything.
50 : : */
51 : : void
626 rhaas@postgresql.org 52 :CBC 12905 : copy_file(const char *src, const char *dst,
53 : : pg_checksum_context *checksum_ctx,
54 : : CopyMethod copy_method, bool dry_run)
55 : : {
519 tomas.vondra@postgre 56 : 12905 : char *strategy_name = NULL;
57 : 12905 : void (*strategy_implementation) (const char *, const char *,
58 : : pg_checksum_context *checksum_ctx) = NULL;
59 : :
60 : : /*
61 : : * In dry-run mode, we don't actually copy anything, nor do we read any
62 : : * data from the source file, but we do verify that we can open it.
63 : : */
626 rhaas@postgresql.org 64 [ - + ]: 12905 : if (dry_run)
65 : : {
66 : : int fd;
67 : :
626 rhaas@postgresql.org 68 [ # # ]:UBC 0 : if ((fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
442 peter@eisentraut.org 69 : 0 : pg_fatal("could not open file \"%s\": %m", src);
626 rhaas@postgresql.org 70 [ # # ]: 0 : if (close(fd) < 0)
442 peter@eisentraut.org 71 : 0 : pg_fatal("could not close file \"%s\": %m", src);
72 : : }
73 : :
74 : : #ifdef WIN32
75 : :
76 : : /*
77 : : * We have no specific switch to enable CopyFile on Windows, because it's
78 : : * supported (as far as we know) on all Windows machines. So,
79 : : * automatically enable it unless some other strategy was selected.
80 : : */
81 : : if (copy_method == COPY_METHOD_COPY)
82 : : copy_method = COPY_METHOD_COPYFILE;
83 : : #endif
84 : :
85 : : /* Determine the name of the copy strategy for use in log messages. */
519 tomas.vondra@postgre 86 [ - + - + :CBC 12905 : switch (copy_method)
- ]
87 : : {
519 tomas.vondra@postgre 88 :UBC 0 : case COPY_METHOD_CLONE:
89 : 0 : strategy_name = "clone";
90 : 0 : strategy_implementation = copy_file_clone;
91 : 0 : break;
519 tomas.vondra@postgre 92 :CBC 11933 : case COPY_METHOD_COPY:
93 : : /* leave NULL for simple block-by-block copy */
94 : 11933 : strategy_implementation = copy_file_blocks;
95 : 11933 : break;
519 tomas.vondra@postgre 96 :UBC 0 : case COPY_METHOD_COPY_FILE_RANGE:
97 : 0 : strategy_name = "copy_file_range";
98 : 0 : strategy_implementation = copy_file_by_range;
99 : 0 : break;
100 : : #ifdef WIN32
101 : : case COPY_METHOD_COPYFILE:
102 : : strategy_name = "CopyFile";
103 : : strategy_implementation = copy_file_copyfile;
104 : : break;
105 : : #endif
173 rhaas@postgresql.org 106 :CBC 972 : case COPY_METHOD_LINK:
107 : 972 : strategy_name = "link";
108 : 972 : strategy_implementation = copy_file_link;
109 : 972 : break;
110 : : }
111 : :
626 112 [ - + ]: 12905 : if (dry_run)
113 : : {
519 tomas.vondra@postgre 114 [ # # ]:UBC 0 : if (strategy_name)
115 [ # # ]: 0 : pg_log_debug("would copy \"%s\" to \"%s\" using strategy %s",
116 : : src, dst, strategy_name);
117 : : else
626 rhaas@postgresql.org 118 [ # # ]: 0 : pg_log_debug("would copy \"%s\" to \"%s\"",
119 : : src, dst);
120 : : }
121 : : else
122 : : {
519 tomas.vondra@postgre 123 [ + + ]:CBC 12905 : if (strategy_name)
124 [ + - ]: 972 : pg_log_debug("copying \"%s\" to \"%s\" using strategy %s",
125 : : src, dst, strategy_name);
126 [ + + ]: 11933 : else if (checksum_ctx->type == CHECKSUM_TYPE_NONE)
626 rhaas@postgresql.org 127 [ + + ]: 10966 : pg_log_debug("copying \"%s\" to \"%s\"",
128 : : src, dst);
129 : : else
130 [ - + ]: 967 : pg_log_debug("copying \"%s\" to \"%s\" and checksumming with %s",
131 : : src, dst, pg_checksum_type_name(checksum_ctx->type));
132 : :
519 tomas.vondra@postgre 133 : 12905 : strategy_implementation(src, dst, checksum_ctx);
134 : : }
626 rhaas@postgresql.org 135 : 12905 : }
136 : :
137 : : /*
138 : : * Calculate checksum for the src file.
139 : : */
140 : : static void
519 tomas.vondra@postgre 141 : 972 : checksum_file(const char *src, pg_checksum_context *checksum_ctx)
142 : : {
143 : : int src_fd;
144 : : uint8 *buffer;
145 : 972 : const int buffer_size = 50 * BLCKSZ;
146 : : ssize_t rb;
147 : :
148 : : /* bail out if no checksum needed */
149 [ + - ]: 972 : if (checksum_ctx->type == CHECKSUM_TYPE_NONE)
150 : 972 : return;
151 : :
519 tomas.vondra@postgre 152 [ # # ]:UBC 0 : if ((src_fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
153 : 0 : pg_fatal("could not open file \"%s\": %m", src);
154 : :
155 : 0 : buffer = pg_malloc(buffer_size);
156 : :
157 [ # # ]: 0 : while ((rb = read(src_fd, buffer, buffer_size)) > 0)
158 : : {
159 [ # # ]: 0 : if (pg_checksum_update(checksum_ctx, buffer, rb) < 0)
160 : 0 : pg_fatal("could not update checksum of file \"%s\"", src);
161 : : }
162 : :
163 [ # # ]: 0 : if (rb < 0)
164 : 0 : pg_fatal("could not read file \"%s\": %m", src);
165 : :
166 : 0 : pg_free(buffer);
167 : 0 : close(src_fd);
168 : : }
169 : :
170 : : /*
171 : : * Copy a file block by block, and optionally compute a checksum as we go.
172 : : */
173 : : static void
626 rhaas@postgresql.org 174 :CBC 11933 : copy_file_blocks(const char *src, const char *dst,
175 : : pg_checksum_context *checksum_ctx)
176 : : {
177 : : int src_fd;
178 : : int dest_fd;
179 : : uint8 *buffer;
180 : 11933 : const int buffer_size = 50 * BLCKSZ;
181 : : ssize_t rb;
182 : 11933 : unsigned offset = 0;
183 : :
184 [ - + ]: 11933 : if ((src_fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
626 rhaas@postgresql.org 185 :UBC 0 : pg_fatal("could not open file \"%s\": %m", src);
186 : :
626 rhaas@postgresql.org 187 [ - + ]:CBC 11933 : if ((dest_fd = open(dst, O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
188 : : pg_file_create_mode)) < 0)
626 rhaas@postgresql.org 189 :UBC 0 : pg_fatal("could not open file \"%s\": %m", dst);
190 : :
626 rhaas@postgresql.org 191 :CBC 11933 : buffer = pg_malloc(buffer_size);
192 : :
193 [ + + ]: 22354 : while ((rb = read(src_fd, buffer, buffer_size)) > 0)
194 : : {
195 : : ssize_t wb;
196 : :
197 [ - + ]: 10421 : if ((wb = write(dest_fd, buffer, rb)) != rb)
198 : : {
626 rhaas@postgresql.org 199 [ # # ]:UBC 0 : if (wb < 0)
442 peter@eisentraut.org 200 : 0 : pg_fatal("could not write to file \"%s\": %m", dst);
201 : : else
202 : 0 : pg_fatal("could not write to file \"%s\", offset %u: wrote %d of %d",
203 : : dst, offset, (int) wb, (int) rb);
204 : : }
205 : :
626 rhaas@postgresql.org 206 [ - + ]:CBC 10421 : if (pg_checksum_update(checksum_ctx, buffer, rb) < 0)
626 rhaas@postgresql.org 207 :UBC 0 : pg_fatal("could not update checksum of file \"%s\"", dst);
208 : :
626 rhaas@postgresql.org 209 :CBC 10421 : offset += rb;
210 : : }
211 : :
212 [ - + ]: 11933 : if (rb < 0)
442 peter@eisentraut.org 213 :UBC 0 : pg_fatal("could not read from file \"%s\": %m", dst);
214 : :
626 rhaas@postgresql.org 215 :CBC 11933 : pg_free(buffer);
216 : 11933 : close(src_fd);
217 : 11933 : close(dest_fd);
218 : 11933 : }
219 : :
220 : : /*
221 : : * copy_file_clone
222 : : * Clones/reflinks a file from src to dest.
223 : : *
224 : : * If needed, also reads the file and calculates the checksum.
225 : : */
226 : : static void
519 tomas.vondra@postgre 227 :UBC 0 : copy_file_clone(const char *src, const char *dest,
228 : : pg_checksum_context *checksum_ctx)
229 : : {
230 : : #if defined(HAVE_COPYFILE) && defined(COPYFILE_CLONE_FORCE)
231 : : if (copyfile(src, dest, NULL, COPYFILE_CLONE_FORCE) < 0)
232 : : pg_fatal("error while cloning file \"%s\" to \"%s\": %m", src, dest);
233 : : #elif defined(__linux__) && defined(FICLONE)
234 : : {
235 : : int src_fd;
236 : : int dest_fd;
237 : :
238 [ # # ]: 0 : if ((src_fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
239 : 0 : pg_fatal("could not open file \"%s\": %m", src);
240 : :
241 [ # # ]: 0 : if ((dest_fd = open(dest, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
242 : : pg_file_create_mode)) < 0)
243 : 0 : pg_fatal("could not create file \"%s\": %m", dest);
244 : :
245 [ # # ]: 0 : if (ioctl(dest_fd, FICLONE, src_fd) < 0)
246 : : {
247 : 0 : int save_errno = errno;
248 : :
249 : 0 : unlink(dest);
250 : :
251 : 0 : pg_fatal("error while cloning file \"%s\" to \"%s\": %s",
252 : : src, dest, strerror(save_errno));
253 : : }
254 : :
433 255 : 0 : close(src_fd);
256 : 0 : close(dest_fd);
257 : : }
258 : : #else
259 : : pg_fatal("file cloning not supported on this platform");
260 : : #endif
261 : :
262 : : /* if needed, calculate checksum of the file */
519 263 : 0 : checksum_file(src, checksum_ctx);
264 : 0 : }
265 : :
266 : : /*
267 : : * copy_file_by_range
268 : : * Copies a file from src to dest using copy_file_range system call.
269 : : *
270 : : * If needed, also reads the file and calculates the checksum.
271 : : */
272 : : static void
273 : 0 : copy_file_by_range(const char *src, const char *dest,
274 : : pg_checksum_context *checksum_ctx)
275 : : {
276 : : #if defined(HAVE_COPY_FILE_RANGE)
277 : : int src_fd;
278 : : int dest_fd;
279 : : ssize_t nbytes;
280 : :
281 [ # # ]: 0 : if ((src_fd = open(src, O_RDONLY | PG_BINARY, 0)) < 0)
282 : 0 : pg_fatal("could not open file \"%s\": %m", src);
283 : :
284 [ # # ]: 0 : if ((dest_fd = open(dest, O_RDWR | O_CREAT | O_EXCL | PG_BINARY,
285 : : pg_file_create_mode)) < 0)
286 : 0 : pg_fatal("could not create file \"%s\": %m", dest);
287 : :
288 : : do
289 : : {
290 : 0 : nbytes = copy_file_range(src_fd, NULL, dest_fd, NULL, SSIZE_MAX, 0);
291 [ # # ]: 0 : if (nbytes < 0)
292 : 0 : pg_fatal("error while copying file range from \"%s\" to \"%s\": %m",
293 : : src, dest);
294 [ # # ]: 0 : } while (nbytes > 0);
295 : :
296 : 0 : close(src_fd);
297 : 0 : close(dest_fd);
298 : : #else
299 : : pg_fatal("copy_file_range not supported on this platform");
300 : : #endif
301 : :
302 : : /* if needed, calculate checksum of the file */
303 : 0 : checksum_file(src, checksum_ctx);
304 : 0 : }
305 : :
306 : : #ifdef WIN32
307 : : static void
308 : : copy_file_copyfile(const char *src, const char *dst,
309 : : pg_checksum_context *checksum_ctx)
310 : : {
311 : : if (CopyFile(src, dst, true) == 0)
312 : : {
313 : : _dosmaperr(GetLastError());
314 : : pg_fatal("could not copy file \"%s\" to \"%s\": %m", src, dst);
315 : : }
316 : :
317 : : /* if needed, calculate checksum of the file */
318 : : checksum_file(src, checksum_ctx);
319 : : }
320 : : #endif /* WIN32 */
321 : :
322 : : /*
323 : : * copy_file_link
324 : : * Hard-links a file from src to dest.
325 : : *
326 : : * If needed, also reads the file and calculates the checksum.
327 : : */
328 : : static void
173 rhaas@postgresql.org 329 :CBC 972 : copy_file_link(const char *src, const char *dest,
330 : : pg_checksum_context *checksum_ctx)
331 : : {
332 [ - + ]: 972 : if (link(src, dest) < 0)
131 peter@eisentraut.org 333 :UBC 0 : pg_fatal("could not create link from \"%s\" to \"%s\": %m",
334 : : src, dest);
335 : :
336 : : /* if needed, calculate checksum of the file */
173 rhaas@postgresql.org 337 :CBC 972 : checksum_file(src, checksum_ctx);
338 : 972 : }
|