]> git.proxmox.com Git - libgit2.git/blame - tests/clar/fs.h
New upstream version 1.1.0+dfsg.1
[libgit2.git] / tests / clar / fs.h
CommitLineData
22a2d3d5
UG
1/*
2 * By default, use a read/write loop to copy files on POSIX systems.
3 * On Linux, use sendfile by default as it's slightly faster. On
4 * macOS, we avoid fcopyfile by default because it's slightly slower.
5 */
6#undef USE_FCOPYFILE
7#define USE_SENDFILE 1
8
2e6f06a8
VM
9#ifdef _WIN32
10
11#define RM_RETRY_COUNT 5
12#define RM_RETRY_DELAY 10
13
14#ifdef __MINGW32__
15
16/* These security-enhanced functions are not available
17 * in MinGW, so just use the vanilla ones */
18#define wcscpy_s(a, b, c) wcscpy((a), (c))
19#define wcscat_s(a, b, c) wcscat((a), (c))
20
21#endif /* __MINGW32__ */
22
7be88b4c 23static int
2e6f06a8
VM
24fs__dotordotdot(WCHAR *_tocheck)
25{
26 return _tocheck[0] == '.' &&
27 (_tocheck[1] == '\0' ||
28 (_tocheck[1] == '.' && _tocheck[2] == '\0'));
29}
30
31static int
32fs_rmdir_rmdir(WCHAR *_wpath)
33{
34 unsigned retries = 1;
35
36 while (!RemoveDirectoryW(_wpath)) {
37 /* Only retry when we have retries remaining, and the
38 * error was ERROR_DIR_NOT_EMPTY. */
39 if (retries++ > RM_RETRY_COUNT ||
40 ERROR_DIR_NOT_EMPTY != GetLastError())
41 return -1;
42
43 /* Give whatever has a handle to a child item some time
44 * to release it before trying again */
45 Sleep(RM_RETRY_DELAY * retries * retries);
46 }
47
48 return 0;
49}
50
51static void
52fs_rmdir_helper(WCHAR *_wsource)
53{
54 WCHAR buffer[MAX_PATH];
55 HANDLE find_handle;
56 WIN32_FIND_DATAW find_data;
f3738eba 57 size_t buffer_prefix_len;
2e6f06a8
VM
58
59 /* Set up the buffer and capture the length */
60 wcscpy_s(buffer, MAX_PATH, _wsource);
61 wcscat_s(buffer, MAX_PATH, L"\\");
62 buffer_prefix_len = wcslen(buffer);
63
64 /* FindFirstFile needs a wildcard to match multiple items */
65 wcscat_s(buffer, MAX_PATH, L"*");
66 find_handle = FindFirstFileW(buffer, &find_data);
67 cl_assert(INVALID_HANDLE_VALUE != find_handle);
68
69 do {
70 /* FindFirstFile/FindNextFile gives back . and ..
71 * entries at the beginning */
72 if (fs__dotordotdot(find_data.cFileName))
73 continue;
74
75 wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName);
76
77 if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
78 fs_rmdir_helper(buffer);
79 else {
80 /* If set, the +R bit must be cleared before deleting */
81 if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
82 cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
83
84 cl_assert(DeleteFileW(buffer));
85 }
86 }
87 while (FindNextFileW(find_handle, &find_data));
88
89 /* Ensure that we successfully completed the enumeration */
90 cl_assert(ERROR_NO_MORE_FILES == GetLastError());
91
92 /* Close the find handle */
93 FindClose(find_handle);
94
95 /* Now that the directory is empty, remove it */
96 cl_assert(0 == fs_rmdir_rmdir(_wsource));
97}
98
99static int
100fs_rm_wait(WCHAR *_wpath)
101{
102 unsigned retries = 1;
103 DWORD last_error;
104
105 do {
106 if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
107 last_error = GetLastError();
108 else
109 last_error = ERROR_SUCCESS;
110
111 /* Is the item gone? */
112 if (ERROR_FILE_NOT_FOUND == last_error ||
113 ERROR_PATH_NOT_FOUND == last_error)
114 return 0;
115
116 Sleep(RM_RETRY_DELAY * retries * retries);
117 }
118 while (retries++ <= RM_RETRY_COUNT);
119
120 return -1;
121}
122
123static void
124fs_rm(const char *_source)
125{
126 WCHAR wsource[MAX_PATH];
127 DWORD attrs;
128
129 /* The input path is UTF-8. Convert it to wide characters
130 * for use with the Windows API */
131 cl_assert(MultiByteToWideChar(CP_UTF8,
132 MB_ERR_INVALID_CHARS,
133 _source,
134 -1, /* Indicates NULL termination */
135 wsource,
136 MAX_PATH));
137
138 /* Does the item exist? If not, we have no work to do */
139 attrs = GetFileAttributesW(wsource);
140
141 if (INVALID_FILE_ATTRIBUTES == attrs)
142 return;
143
144 if (FILE_ATTRIBUTE_DIRECTORY & attrs)
145 fs_rmdir_helper(wsource);
146 else {
147 /* The item is a file. Strip the +R bit */
148 if (FILE_ATTRIBUTE_READONLY & attrs)
149 cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
150
151 cl_assert(DeleteFileW(wsource));
152 }
153
154 /* Wait for the DeleteFile or RemoveDirectory call to complete */
155 cl_assert(0 == fs_rm_wait(wsource));
156}
157
158static void
159fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
160{
161 WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH];
162 HANDLE find_handle;
163 WIN32_FIND_DATAW find_data;
f3738eba 164 size_t buf_source_prefix_len, buf_dest_prefix_len;
2e6f06a8
VM
165
166 wcscpy_s(buf_source, MAX_PATH, _wsource);
167 wcscat_s(buf_source, MAX_PATH, L"\\");
168 buf_source_prefix_len = wcslen(buf_source);
169
170 wcscpy_s(buf_dest, MAX_PATH, _wdest);
171 wcscat_s(buf_dest, MAX_PATH, L"\\");
172 buf_dest_prefix_len = wcslen(buf_dest);
173
174 /* Get an enumerator for the items in the source. */
175 wcscat_s(buf_source, MAX_PATH, L"*");
176 find_handle = FindFirstFileW(buf_source, &find_data);
177 cl_assert(INVALID_HANDLE_VALUE != find_handle);
178
179 /* Create the target directory. */
180 cl_assert(CreateDirectoryW(_wdest, NULL));
181
182 do {
183 /* FindFirstFile/FindNextFile gives back . and ..
184 * entries at the beginning */
185 if (fs__dotordotdot(find_data.cFileName))
186 continue;
187
188 wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName);
189 wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
190
191 if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
192 fs_copydir_helper(buf_source, buf_dest);
193 else
194 cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
195 }
196 while (FindNextFileW(find_handle, &find_data));
197
198 /* Ensure that we successfully completed the enumeration */
199 cl_assert(ERROR_NO_MORE_FILES == GetLastError());
200
201 /* Close the find handle */
202 FindClose(find_handle);
203}
204
205static void
206fs_copy(const char *_source, const char *_dest)
207{
208 WCHAR wsource[MAX_PATH], wdest[MAX_PATH];
209 DWORD source_attrs, dest_attrs;
210 HANDLE find_handle;
211 WIN32_FIND_DATAW find_data;
7be88b4c 212
2e6f06a8
VM
213 /* The input paths are UTF-8. Convert them to wide characters
214 * for use with the Windows API. */
215 cl_assert(MultiByteToWideChar(CP_UTF8,
216 MB_ERR_INVALID_CHARS,
217 _source,
218 -1,
219 wsource,
220 MAX_PATH));
221
222 cl_assert(MultiByteToWideChar(CP_UTF8,
223 MB_ERR_INVALID_CHARS,
224 _dest,
225 -1,
226 wdest,
227 MAX_PATH));
228
229 /* Check the source for existence */
230 source_attrs = GetFileAttributesW(wsource);
231 cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
232
233 /* Check the target for existence */
234 dest_attrs = GetFileAttributesW(wdest);
235
236 if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
237 /* Target exists; append last path part of source to target.
238 * Use FindFirstFile to parse the path */
239 find_handle = FindFirstFileW(wsource, &find_data);
240 cl_assert(INVALID_HANDLE_VALUE != find_handle);
241 wcscat_s(wdest, MAX_PATH, L"\\");
242 wcscat_s(wdest, MAX_PATH, find_data.cFileName);
243 FindClose(find_handle);
244
245 /* Check the new target for existence */
246 cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
247 }
248
249 if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
250 fs_copydir_helper(wsource, wdest);
251 else
252 cl_assert(CopyFileW(wsource, wdest, TRUE));
253}
254
255void
256cl_fs_cleanup(void)
257{
258 fs_rm(fixture_path(_clar_path, "*"));
259}
260
261#else
7be88b4c
RB
262
263#include <errno.h>
264#include <string.h>
22a2d3d5
UG
265#include <limits.h>
266#include <dirent.h>
267#include <fcntl.h>
268#include <unistd.h>
269#include <sys/types.h>
270#include <sys/stat.h>
271
272#if defined(__linux__)
273# include <sys/sendfile.h>
274#endif
7be88b4c 275
22a2d3d5
UG
276#if defined(__APPLE__)
277# include <copyfile.h>
278#endif
279
280static void basename_r(const char **out, int *out_len, const char *in)
2e6f06a8 281{
22a2d3d5
UG
282 size_t in_len = strlen(in), start_pos;
283
284 for (in_len = strlen(in); in_len; in_len--) {
285 if (in[in_len - 1] != '/')
286 break;
287 }
288
289 for (start_pos = in_len; start_pos; start_pos--) {
290 if (in[start_pos - 1] == '/')
291 break;
292 }
2e6f06a8 293
22a2d3d5 294 cl_assert(in_len - start_pos < INT_MAX);
2e6f06a8 295
22a2d3d5
UG
296 if (in_len - start_pos > 0) {
297 *out = &in[start_pos];
298 *out_len = (in_len - start_pos);
299 } else {
300 *out = "/";
301 *out_len = 1;
2e6f06a8 302 }
22a2d3d5
UG
303}
304
305static char *joinpath(const char *dir, const char *base, int base_len)
306{
307 char *out;
308 int len;
2e6f06a8 309
22a2d3d5
UG
310 if (base_len == -1) {
311 size_t bl = strlen(base);
312
313 cl_assert(bl < INT_MAX);
314 base_len = (int)bl;
2e6f06a8
VM
315 }
316
22a2d3d5
UG
317 len = strlen(dir) + base_len + 2;
318 cl_assert(len > 0);
319
320 cl_assert(out = malloc(len));
321 cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
322
323 return out;
324}
325
326static void
327fs_copydir_helper(const char *source, const char *dest, int dest_mode)
328{
329 DIR *source_dir;
330 struct dirent *d;
331
332 mkdir(dest, dest_mode);
333
334 cl_assert_(source_dir = opendir(source), "Could not open source dir");
335 while ((d = (errno = 0, readdir(source_dir))) != NULL) {
336 char *child;
337
338 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
339 continue;
340
341 child = joinpath(source, d->d_name, -1);
342 fs_copy(child, dest);
343 free(child);
344 }
345
346 cl_assert_(errno == 0, "Failed to iterate source dir");
347
348 closedir(source_dir);
349}
350
351static void
352fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
353{
354 int in, out;
355
356 cl_must_pass((in = open(source, O_RDONLY)));
357 cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
358
359#if USE_FCOPYFILE && defined(__APPLE__)
360 ((void)(source_len)); /* unused */
361 cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
362#elif USE_SENDFILE && defined(__linux__)
363 {
364 ssize_t ret = 0;
365
366 while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
367 source_len -= (size_t)ret;
368 }
369 cl_assert(ret >= 0);
370 }
371#else
372 {
373 char buf[131072];
374 ssize_t ret;
375
376 ((void)(source_len)); /* unused */
7be88b4c 377
22a2d3d5
UG
378 while ((ret = read(in, buf, sizeof(buf))) > 0) {
379 size_t len = (size_t)ret;
380
381 while (len && (ret = write(out, buf, len)) > 0) {
382 cl_assert(ret <= (ssize_t)len);
383 len -= ret;
384 }
385 cl_assert(ret >= 0);
386 }
387 cl_assert(ret == 0);
388 }
389#endif
390
391 close(in);
392 close(out);
2e6f06a8
VM
393}
394
395static void
22a2d3d5 396fs_copy(const char *source, const char *_dest)
2e6f06a8 397{
22a2d3d5
UG
398 char *dbuf = NULL;
399 const char *dest;
400 struct stat source_st, dest_st;
401
402 cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
2e6f06a8 403
22a2d3d5
UG
404 if (lstat(_dest, &dest_st) == 0) {
405 const char *base;
406 int base_len;
2e6f06a8 407
22a2d3d5
UG
408 /* Target exists and is directory; append basename */
409 cl_assert(S_ISDIR(dest_st.st_mode));
2e6f06a8 410
22a2d3d5
UG
411 basename_r(&base, &base_len, source);
412 cl_assert(base_len < INT_MAX);
2e6f06a8 413
22a2d3d5
UG
414 dbuf = joinpath(_dest, base, base_len);
415 dest = dbuf;
416 } else if (errno != ENOENT) {
417 cl_fail("Cannot copy; cannot stat destination");
418 } else {
419 dest = _dest;
420 }
2e6f06a8 421
22a2d3d5
UG
422 if (S_ISDIR(source_st.st_mode)) {
423 fs_copydir_helper(source, dest, source_st.st_mode);
424 } else {
425 fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
426 }
427
428 free(dbuf);
2e6f06a8
VM
429}
430
431static void
22a2d3d5 432fs_rmdir_helper(const char *path)
2e6f06a8 433{
22a2d3d5
UG
434 DIR *dir;
435 struct dirent *d;
436
437 cl_assert_(dir = opendir(path), "Could not open dir");
438 while ((d = (errno = 0, readdir(dir))) != NULL) {
439 char *child;
440
441 if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
442 continue;
2e6f06a8 443
22a2d3d5
UG
444 child = joinpath(path, d->d_name, -1);
445 fs_rm(child);
446 free(child);
447 }
448
449 cl_assert_(errno == 0, "Failed to iterate source dir");
450 closedir(dir);
451
452 cl_must_pass_(rmdir(path), "Could not remove directory");
453}
2e6f06a8 454
22a2d3d5
UG
455static void
456fs_rm(const char *path)
457{
458 struct stat st;
459
460 if (lstat(path, &st)) {
461 if (errno == ENOENT)
462 return;
463
464 cl_fail("Cannot copy; cannot stat destination");
465 }
466
467 if (S_ISDIR(st.st_mode)) {
468 fs_rmdir_helper(path);
469 } else {
470 cl_must_pass(unlink(path));
471 }
2e6f06a8
VM
472}
473
474void
475cl_fs_cleanup(void)
476{
477 clar_unsandbox();
478 clar_sandbox();
479}
480#endif