1/*-------------------------------------------------------------------------
2 *
3 * copydir.c
4 * copies a directory
5 *
6 * Portions Copyright (c) 1996-2019, 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#include <fcntl.h>
22#include <unistd.h>
23#include <sys/stat.h>
24
25#include "storage/copydir.h"
26#include "storage/fd.h"
27#include "miscadmin.h"
28#include "pgstat.h"
29
30/*
31 * copydir: copy a directory
32 *
33 * If recurse is false, subdirectories are ignored. Anything that's not
34 * a directory or a regular file is ignored.
35 */
36void
37copydir(char *fromdir, char *todir, bool recurse)
38{
39 DIR *xldir;
40 struct dirent *xlde;
41 char fromfile[MAXPGPATH * 2];
42 char tofile[MAXPGPATH * 2];
43
44 if (MakePGDirectory(todir) != 0)
45 ereport(ERROR,
46 (errcode_for_file_access(),
47 errmsg("could not create directory \"%s\": %m", todir)));
48
49 xldir = AllocateDir(fromdir);
50
51 while ((xlde = ReadDir(xldir, fromdir)) != NULL)
52 {
53 struct stat fst;
54
55 /* If we got a cancel signal during the copy of the directory, quit */
56 CHECK_FOR_INTERRUPTS();
57
58 if (strcmp(xlde->d_name, ".") == 0 ||
59 strcmp(xlde->d_name, "..") == 0)
60 continue;
61
62 snprintf(fromfile, sizeof(fromfile), "%s/%s", fromdir, xlde->d_name);
63 snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
64
65 if (lstat(fromfile, &fst) < 0)
66 ereport(ERROR,
67 (errcode_for_file_access(),
68 errmsg("could not stat file \"%s\": %m", fromfile)));
69
70 if (S_ISDIR(fst.st_mode))
71 {
72 /* recurse to handle subdirectories */
73 if (recurse)
74 copydir(fromfile, tofile, true);
75 }
76 else if (S_ISREG(fst.st_mode))
77 copy_file(fromfile, tofile);
78 }
79 FreeDir(xldir);
80
81 /*
82 * Be paranoid here and fsync all files to ensure the copy is really done.
83 * But if fsync is disabled, we're done.
84 */
85 if (!enableFsync)
86 return;
87
88 xldir = AllocateDir(todir);
89
90 while ((xlde = ReadDir(xldir, todir)) != NULL)
91 {
92 struct stat fst;
93
94 if (strcmp(xlde->d_name, ".") == 0 ||
95 strcmp(xlde->d_name, "..") == 0)
96 continue;
97
98 snprintf(tofile, sizeof(tofile), "%s/%s", todir, xlde->d_name);
99
100 /*
101 * We don't need to sync subdirectories here since the recursive
102 * copydir will do it before it returns
103 */
104 if (lstat(tofile, &fst) < 0)
105 ereport(ERROR,
106 (errcode_for_file_access(),
107 errmsg("could not stat file \"%s\": %m", tofile)));
108
109 if (S_ISREG(fst.st_mode))
110 fsync_fname(tofile, false);
111 }
112 FreeDir(xldir);
113
114 /*
115 * It's important to fsync the destination directory itself as individual
116 * file fsyncs don't guarantee that the directory entry for the file is
117 * synced. Recent versions of ext4 have made the window much wider but
118 * it's been true for ext3 and other filesystems in the past.
119 */
120 fsync_fname(todir, true);
121}
122
123/*
124 * copy one file
125 */
126void
127copy_file(char *fromfile, char *tofile)
128{
129 char *buffer;
130 int srcfd;
131 int dstfd;
132 int nbytes;
133 off_t offset;
134 off_t flush_offset;
135
136 /* Size of copy buffer (read and write requests) */
137#define COPY_BUF_SIZE (8 * BLCKSZ)
138
139 /*
140 * Size of data flush requests. It seems beneficial on most platforms to
141 * do this every 1MB or so. But macOS, at least with early releases of
142 * APFS, is really unfriendly to small mmap/msync requests, so there do it
143 * only every 32MB.
144 */
145#if defined(__darwin__)
146#define FLUSH_DISTANCE (32 * 1024 * 1024)
147#else
148#define FLUSH_DISTANCE (1024 * 1024)
149#endif
150
151 /* Use palloc to ensure we get a maxaligned buffer */
152 buffer = palloc(COPY_BUF_SIZE);
153
154 /*
155 * Open the files
156 */
157 srcfd = OpenTransientFile(fromfile, O_RDONLY | PG_BINARY);
158 if (srcfd < 0)
159 ereport(ERROR,
160 (errcode_for_file_access(),
161 errmsg("could not open file \"%s\": %m", fromfile)));
162
163 dstfd = OpenTransientFile(tofile, O_RDWR | O_CREAT | O_EXCL | PG_BINARY);
164 if (dstfd < 0)
165 ereport(ERROR,
166 (errcode_for_file_access(),
167 errmsg("could not create file \"%s\": %m", tofile)));
168
169 /*
170 * Do the data copying.
171 */
172 flush_offset = 0;
173 for (offset = 0;; offset += nbytes)
174 {
175 /* If we got a cancel signal during the copy of the file, quit */
176 CHECK_FOR_INTERRUPTS();
177
178 /*
179 * We fsync the files later, but during the copy, flush them every so
180 * often to avoid spamming the cache and hopefully get the kernel to
181 * start writing them out before the fsync comes.
182 */
183 if (offset - flush_offset >= FLUSH_DISTANCE)
184 {
185 pg_flush_data(dstfd, flush_offset, offset - flush_offset);
186 flush_offset = offset;
187 }
188
189 pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_READ);
190 nbytes = read(srcfd, buffer, COPY_BUF_SIZE);
191 pgstat_report_wait_end();
192 if (nbytes < 0)
193 ereport(ERROR,
194 (errcode_for_file_access(),
195 errmsg("could not read file \"%s\": %m", fromfile)));
196 if (nbytes == 0)
197 break;
198 errno = 0;
199 pgstat_report_wait_start(WAIT_EVENT_COPY_FILE_WRITE);
200 if ((int) write(dstfd, buffer, nbytes) != nbytes)
201 {
202 /* if write didn't set errno, assume problem is no disk space */
203 if (errno == 0)
204 errno = ENOSPC;
205 ereport(ERROR,
206 (errcode_for_file_access(),
207 errmsg("could not write to file \"%s\": %m", tofile)));
208 }
209 pgstat_report_wait_end();
210 }
211
212 if (offset > flush_offset)
213 pg_flush_data(dstfd, flush_offset, offset - flush_offset);
214
215 if (CloseTransientFile(dstfd))
216 ereport(ERROR,
217 (errcode_for_file_access(),
218 errmsg("could not close file \"%s\": %m", tofile)));
219
220 if (CloseTransientFile(srcfd))
221 ereport(ERROR,
222 (errcode_for_file_access(),
223 errmsg("could not close file \"%s\": %m", fromfile)));
224
225 pfree(buffer);
226}
227