File Coverage

deps/libgit2/src/util/futils.c
Criterion Covered Total %
statement 262 613 42.7
branch 159 454 35.0
condition n/a
subroutine n/a
pod n/a
total 421 1067 39.4


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
4             * This file is part of libgit2, distributed under the GNU GPL v2 with
5             * a Linking Exception. For full terms see the included COPYING file.
6             */
7              
8             #include "futils.h"
9              
10             #include "runtime.h"
11             #include "strmap.h"
12             #include "hash.h"
13             #include "rand.h"
14              
15             #include
16             #if GIT_WIN32
17             #include "win32/findfile.h"
18             #endif
19              
20             #define GIT_FILEMODE_DEFAULT 0100666
21              
22 262           int git_futils_mkpath2file(const char *file_path, const mode_t mode)
23             {
24 262           return git_futils_mkdir(
25             file_path, mode,
26             GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
27             }
28              
29 187           int git_futils_mktmp(git_str *path_out, const char *filename, mode_t mode)
30             {
31 187           const int open_flags = O_RDWR | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC;
32 187           unsigned int tries = 32;
33             int fd;
34              
35 187 50         while (tries--) {
36 187           uint64_t rand = git_rand_next();
37              
38 187           git_str_sets(path_out, filename);
39 187           git_str_puts(path_out, "_git2_");
40 187           git_str_encode_hexstr(path_out, (void *)&rand, sizeof(uint64_t));
41              
42 187 50         if (git_str_oom(path_out))
43 187           return -1;
44              
45             /* Note that we open with O_CREAT | O_EXCL */
46 187 50         if ((fd = p_open(path_out->ptr, open_flags, mode)) >= 0)
47 187           return fd;
48             }
49              
50 0           git_error_set(GIT_ERROR_OS,
51             "failed to create temporary file '%s'", path_out->ptr);
52 0           git_str_dispose(path_out);
53 0           return -1;
54             }
55              
56 0           int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode)
57             {
58             int fd;
59              
60 0 0         if (git_futils_mkpath2file(path, dirmode) < 0)
61 0           return -1;
62              
63 0           fd = p_creat(path, mode);
64 0 0         if (fd < 0) {
65 0           git_error_set(GIT_ERROR_OS, "failed to create file '%s'", path);
66 0           return -1;
67             }
68              
69 0           return fd;
70             }
71              
72 317           int git_futils_creat_locked(const char *path, const mode_t mode)
73             {
74 317           int fd = p_open(path, O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC,
75             mode);
76              
77 317 50         if (fd < 0) {
78 0           int error = errno;
79 0           git_error_set(GIT_ERROR_OS, "failed to create locked file '%s'", path);
80 0           switch (error) {
81             case EEXIST:
82 0           return GIT_ELOCKED;
83             case ENOENT:
84 0           return GIT_ENOTFOUND;
85             default:
86 0           return -1;
87             }
88             }
89              
90 317           return fd;
91             }
92              
93 144           int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode)
94             {
95 144 50         if (git_futils_mkpath2file(path, dirmode) < 0)
96 0           return -1;
97              
98 144           return git_futils_creat_locked(path, mode);
99             }
100              
101 3332           int git_futils_open_ro(const char *path)
102             {
103 3332           int fd = p_open(path, O_RDONLY);
104 3332 100         if (fd < 0)
105 392           return git_fs_path_set_error(errno, path, "open");
106 2940           return fd;
107             }
108              
109 0           int git_futils_truncate(const char *path, int mode)
110             {
111 0           int fd = p_open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
112 0 0         if (fd < 0)
113 0           return git_fs_path_set_error(errno, path, "open");
114              
115 0           close(fd);
116 0           return 0;
117             }
118              
119 4           int git_futils_filesize(uint64_t *out, git_file fd)
120             {
121             struct stat sb;
122              
123 4 50         if (p_fstat(fd, &sb)) {
124 0           git_error_set(GIT_ERROR_OS, "failed to stat file descriptor");
125 0           return -1;
126             }
127              
128 4 50         if (sb.st_size < 0) {
129 0           git_error_set(GIT_ERROR_INVALID, "invalid file size");
130 0           return -1;
131             }
132              
133 4           *out = sb.st_size;
134 4           return 0;
135             }
136              
137 877           mode_t git_futils_canonical_mode(mode_t raw_mode)
138             {
139 877 100         if (S_ISREG(raw_mode))
140 671 50         return S_IFREG | GIT_PERMS_CANONICAL(raw_mode);
141 206 50         else if (S_ISLNK(raw_mode))
142 0           return S_IFLNK;
143 206 50         else if (S_ISGITLINK(raw_mode))
144 0           return S_IFGITLINK;
145 206 50         else if (S_ISDIR(raw_mode))
146 206           return S_IFDIR;
147             else
148 0           return 0;
149             }
150              
151 2371           int git_futils_readbuffer_fd(git_str *buf, git_file fd, size_t len)
152             {
153 2371           ssize_t read_size = 0;
154             size_t alloc_len;
155              
156 2371           git_str_clear(buf);
157              
158 2371 50         if (!git__is_ssizet(len)) {
159 0           git_error_set(GIT_ERROR_INVALID, "read too large");
160 0           return -1;
161             }
162              
163 2371 50         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, len, 1);
    50          
164 2371 50         if (git_str_grow(buf, alloc_len) < 0)
165 0           return -1;
166              
167             /* p_read loops internally to read len bytes */
168 2371           read_size = p_read(fd, buf->ptr, len);
169              
170 2371 50         if (read_size < 0) {
171 0           git_error_set(GIT_ERROR_OS, "failed to read descriptor");
172 0           git_str_dispose(buf);
173 0           return -1;
174             }
175              
176 2371 50         if ((size_t)read_size != len) {
177 0           git_error_set(GIT_ERROR_FILESYSTEM, "could not read (expected %" PRIuZ " bytes, read %" PRIuZ ")", len, (size_t)read_size);
178 0           git_str_dispose(buf);
179 0           return -1;
180             }
181              
182 2371           buf->ptr[read_size] = '\0';
183 2371           buf->size = read_size;
184              
185 2371           return 0;
186             }
187              
188 0           int git_futils_readbuffer_fd_full(git_str *buf, git_file fd)
189             {
190             static size_t blocksize = 10240;
191 0           size_t alloc_len = 0, total_size = 0;
192 0           ssize_t read_size = 0;
193              
194 0           git_str_clear(buf);
195              
196             while (true) {
197 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, blocksize);
    0          
198              
199 0 0         if (git_str_grow(buf, alloc_len) < 0)
200 0           return -1;
201              
202             /* p_read loops internally to read blocksize bytes */
203 0           read_size = p_read(fd, buf->ptr, blocksize);
204              
205 0 0         if (read_size < 0) {
206 0           git_error_set(GIT_ERROR_OS, "failed to read descriptor");
207 0           git_str_dispose(buf);
208 0           return -1;
209             }
210              
211 0           total_size += read_size;
212              
213 0 0         if ((size_t)read_size < blocksize) {
214 0           break;
215             }
216 0           }
217              
218 0           buf->ptr[total_size] = '\0';
219 0           buf->size = total_size;
220              
221 0           return 0;
222             }
223              
224 2545           int git_futils_readbuffer_updated(
225             git_str *out,
226             const char *path,
227             unsigned char checksum[GIT_HASH_SHA1_SIZE],
228             int *updated)
229             {
230             int error;
231             git_file fd;
232             struct stat st;
233 2545           git_str buf = GIT_STR_INIT;
234             unsigned char checksum_new[GIT_HASH_SHA1_SIZE];
235              
236 2545 50         GIT_ASSERT_ARG(out);
237 2545 50         GIT_ASSERT_ARG(path && *path);
    50          
238              
239 2545 50         if (updated != NULL)
240 0           *updated = 0;
241              
242 2545 100         if (p_stat(path, &st) < 0)
243 228           return git_fs_path_set_error(errno, path, "stat");
244              
245              
246 2317 50         if (S_ISDIR(st.st_mode)) {
247 0           git_error_set(GIT_ERROR_INVALID, "requested file is a directory");
248 0           return GIT_ENOTFOUND;
249             }
250              
251 2317 50         if (!git__is_sizet(st.st_size+1)) {
252 0           git_error_set(GIT_ERROR_OS, "invalid regular file stat for '%s'", path);
253 0           return -1;
254             }
255              
256 2317 50         if ((fd = git_futils_open_ro(path)) < 0)
257 0           return fd;
258              
259 2317 50         if (git_futils_readbuffer_fd(&buf, fd, (size_t)st.st_size) < 0) {
260 0           p_close(fd);
261 0           return -1;
262             }
263              
264 2317           p_close(fd);
265              
266 2317 50         if (checksum) {
267 0 0         if ((error = git_hash_buf(checksum_new, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0) {
268 0           git_str_dispose(&buf);
269 0           return error;
270             }
271              
272             /*
273             * If we were given a checksum, we only want to use it if it's different
274             */
275 0 0         if (!memcmp(checksum, checksum_new, GIT_HASH_SHA1_SIZE)) {
276 0           git_str_dispose(&buf);
277 0 0         if (updated)
278 0           *updated = 0;
279              
280 0           return 0;
281             }
282              
283 0           memcpy(checksum, checksum_new, GIT_HASH_SHA1_SIZE);
284             }
285              
286             /*
287             * If we're here, the file did change, or the user didn't have an old version
288             */
289 2317 50         if (updated != NULL)
290 0           *updated = 1;
291              
292 2317           git_str_swap(out, &buf);
293 2317           git_str_dispose(&buf);
294              
295 2545           return 0;
296             }
297              
298 2545           int git_futils_readbuffer(git_str *buf, const char *path)
299             {
300 2545           return git_futils_readbuffer_updated(buf, path, NULL, NULL);
301             }
302              
303 143           int git_futils_writebuffer(
304             const git_str *buf, const char *path, int flags, mode_t mode)
305             {
306 143           int fd, do_fsync = 0, error = 0;
307              
308 143 100         if (!flags)
309 21           flags = O_CREAT | O_TRUNC | O_WRONLY;
310              
311 143 50         if ((flags & O_FSYNC) != 0)
312 0           do_fsync = 1;
313              
314 143           flags &= ~O_FSYNC;
315              
316 143 50         if (!mode)
317 0           mode = GIT_FILEMODE_DEFAULT;
318              
319 143 50         if ((fd = p_open(path, flags, mode)) < 0) {
320 0           git_error_set(GIT_ERROR_OS, "could not open '%s' for writing", path);
321 0           return fd;
322             }
323              
324 143 50         if ((error = p_write(fd, git_str_cstr(buf), git_str_len(buf))) < 0) {
325 0           git_error_set(GIT_ERROR_OS, "could not write to '%s'", path);
326 0           (void)p_close(fd);
327 0           return error;
328             }
329              
330 143 50         if (do_fsync && (error = p_fsync(fd)) < 0) {
    0          
331 0           git_error_set(GIT_ERROR_OS, "could not fsync '%s'", path);
332 0           p_close(fd);
333 0           return error;
334             }
335              
336 143 50         if ((error = p_close(fd)) < 0) {
337 0           git_error_set(GIT_ERROR_OS, "error while closing '%s'", path);
338 0           return error;
339             }
340              
341 143 50         if (do_fsync && (flags & O_CREAT))
    0          
342 0           error = git_futils_fsync_parent(path);
343              
344 143           return error;
345             }
346              
347 0           int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
348             {
349 0 0         if (git_futils_mkpath2file(to, dirmode) < 0)
350 0           return -1;
351              
352 0 0         if (p_rename(from, to) < 0) {
353 0           git_error_set(GIT_ERROR_OS, "failed to rename '%s' to '%s'", from, to);
354 0           return -1;
355             }
356              
357 0           return 0;
358             }
359              
360 55           int git_futils_mmap_ro(git_map *out, git_file fd, off64_t begin, size_t len)
361             {
362 55           return p_mmap(out, len, GIT_PROT_READ, GIT_MAP_SHARED, fd, begin);
363             }
364              
365 0           int git_futils_mmap_ro_file(git_map *out, const char *path)
366             {
367 0           git_file fd = git_futils_open_ro(path);
368             uint64_t len;
369             int result;
370              
371 0 0         if (fd < 0)
372 0           return fd;
373              
374 0 0         if ((result = git_futils_filesize(&len, fd)) < 0)
375 0           goto out;
376              
377 0 0         if (!git__is_sizet(len)) {
378 0           git_error_set(GIT_ERROR_OS, "file `%s` too large to mmap", path);
379 0           result = -1;
380 0           goto out;
381             }
382              
383 0           result = git_futils_mmap_ro(out, fd, 0, (size_t)len);
384             out:
385 0           p_close(fd);
386 0           return result;
387             }
388              
389 55           void git_futils_mmap_free(git_map *out)
390             {
391 55           p_munmap(out);
392 55           }
393              
394 304           GIT_INLINE(int) mkdir_validate_dir(
395             const char *path,
396             struct stat *st,
397             mode_t mode,
398             uint32_t flags,
399             struct git_futils_mkdir_options *opts)
400             {
401             /* with exclusive create, existing dir is an error */
402 304 50         if ((flags & GIT_MKDIR_EXCL) != 0) {
403 0           git_error_set(GIT_ERROR_FILESYSTEM,
404             "failed to make directory '%s': directory exists", path);
405 0           return GIT_EEXISTS;
406             }
407              
408 304 50         if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) ||
    0          
    50          
409 0 0         (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) {
410 0 0         if (p_unlink(path) < 0) {
411 0 0         git_error_set(GIT_ERROR_OS, "failed to remove %s '%s'",
412 0           S_ISLNK(st->st_mode) ? "symlink" : "file", path);
413 0           return GIT_EEXISTS;
414             }
415              
416 0           opts->perfdata.mkdir_calls++;
417              
418 0 0         if (p_mkdir(path, mode) < 0) {
419 0           git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
420 0           return GIT_EEXISTS;
421             }
422             }
423              
424 304 50         else if (S_ISLNK(st->st_mode)) {
425             /* Re-stat the target, make sure it's a directory */
426 0           opts->perfdata.stat_calls++;
427              
428 0 0         if (p_stat(path, st) < 0) {
429 0           git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", path);
430 0           return GIT_EEXISTS;
431             }
432             }
433              
434 304 50         else if (!S_ISDIR(st->st_mode)) {
435 0           git_error_set(GIT_ERROR_FILESYSTEM,
436             "failed to make directory '%s': directory exists", path);
437 0           return GIT_EEXISTS;
438             }
439              
440 304           return 0;
441             }
442              
443 495           GIT_INLINE(int) mkdir_validate_mode(
444             const char *path,
445             struct stat *st,
446             bool terminal_path,
447             mode_t mode,
448             uint32_t flags,
449             struct git_futils_mkdir_options *opts)
450             {
451 495 100         if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) ||
    50          
    50          
452 0 0         (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) {
453              
454 0           opts->perfdata.chmod_calls++;
455              
456 0 0         if (p_chmod(path, mode) < 0) {
457 0           git_error_set(GIT_ERROR_OS, "failed to set permissions on '%s'", path);
458 0           return -1;
459             }
460             }
461              
462 495           return 0;
463             }
464              
465 517           GIT_INLINE(int) mkdir_canonicalize(
466             git_str *path,
467             uint32_t flags)
468             {
469             ssize_t root_len;
470              
471 517 50         if (path->size == 0) {
472 0           git_error_set(GIT_ERROR_OS, "attempt to create empty path");
473 0           return -1;
474             }
475              
476             /* Trim trailing slashes (except the root) */
477 517 50         if ((root_len = git_fs_path_root(path->ptr)) < 0)
478 0           root_len = 0;
479             else
480 517           root_len++;
481              
482 562 50         while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/')
    100          
483 45           path->ptr[--path->size] = '\0';
484              
485             /* if we are not supposed to made the last element, truncate it */
486 517 50         if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {
487 0           git_fs_path_dirname_r(path, path->ptr);
488 0           flags |= GIT_MKDIR_SKIP_LAST;
489             }
490 517 100         if ((flags & GIT_MKDIR_SKIP_LAST) != 0) {
491 460           git_fs_path_dirname_r(path, path->ptr);
492             }
493              
494             /* We were either given the root path (or trimmed it to
495             * the root), we don't have anything to do.
496             */
497 517 50         if (path->size <= (size_t)root_len)
498 0           git_str_clear(path);
499              
500 517           return 0;
501             }
502              
503 282           int git_futils_mkdir(
504             const char *path,
505             mode_t mode,
506             uint32_t flags)
507             {
508 282           git_str make_path = GIT_STR_INIT, parent_path = GIT_STR_INIT;
509             const char *relative;
510 282           struct git_futils_mkdir_options opts = { 0 };
511             struct stat st;
512 282           size_t depth = 0;
513 282           int len = 0, root_len, error;
514              
515 282 50         if ((error = git_str_puts(&make_path, path)) < 0 ||
    50          
516 282 50         (error = mkdir_canonicalize(&make_path, flags)) < 0 ||
517 282 50         (error = git_str_puts(&parent_path, make_path.ptr)) < 0 ||
518 282           make_path.size == 0)
519             goto done;
520              
521 282           root_len = git_fs_path_root(make_path.ptr);
522              
523             /* find the first parent directory that exists. this will be used
524             * as the base to dirname_relative.
525             */
526 293 50         for (relative = make_path.ptr; parent_path.size; ) {
527 293           error = p_lstat(parent_path.ptr, &st);
528              
529 293 100         if (error == 0) {
530 273           break;
531 20 50         } else if (errno != ENOENT) {
532 0           git_error_set(GIT_ERROR_OS, "failed to stat '%s'", parent_path.ptr);
533 0           error = -1;
534 0           goto done;
535             }
536              
537 20           depth++;
538              
539             /* examine the parent of the current path */
540 20 50         if ((len = git_fs_path_dirname_r(&parent_path, parent_path.ptr)) < 0) {
541 0           error = len;
542 0           goto done;
543             }
544              
545 20 50         GIT_ASSERT(len);
546              
547             /*
548             * We've walked all the given path's parents and it's either relative
549             * (the parent is simply '.') or rooted (the length is less than or
550             * equal to length of the root path). The path may be less than the
551             * root path length on Windows, where `C:` == `C:/`.
552             */
553 20 50         if ((len == 1 && parent_path.ptr[0] == '.') ||
    0          
    50          
554 20 0         (len == 1 && parent_path.ptr[0] == '/') ||
    50          
555             len <= root_len) {
556 0           relative = make_path.ptr;
557 0           break;
558             }
559              
560 20           relative = make_path.ptr + len + 1;
561              
562             /* not recursive? just make this directory relative to its parent. */
563 20 100         if ((flags & GIT_MKDIR_PATH) == 0)
564 9           break;
565             }
566              
567             /* we found an item at the location we're trying to create,
568             * validate it.
569             */
570 282 100         if (depth == 0) {
571 268           error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts);
572              
573 268 50         if (!error)
574 268           error = mkdir_validate_mode(
575 268           make_path.ptr, &st, true, mode, flags, &opts);
576              
577 268           goto done;
578             }
579              
580             /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
581             * canonicalizing `make_path`.
582             */
583 14           flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST);
584              
585 14 50         error = git_futils_mkdir_relative(relative,
586 14           parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts);
587              
588             done:
589 282           git_str_dispose(&make_path);
590 282           git_str_dispose(&parent_path);
591 282           return error;
592             }
593              
594 0           int git_futils_mkdir_r(const char *path, const mode_t mode)
595             {
596 0           return git_futils_mkdir(path, mode, GIT_MKDIR_PATH);
597             }
598              
599 235           int git_futils_mkdir_relative(
600             const char *relative_path,
601             const char *base,
602             mode_t mode,
603             uint32_t flags,
604             struct git_futils_mkdir_options *opts)
605             {
606 235           git_str make_path = GIT_STR_INIT;
607 235           ssize_t root = 0, min_root_len;
608 235           char lastch = '/', *tail;
609             struct stat st;
610 235           struct git_futils_mkdir_options empty_opts = {0};
611             int error;
612              
613 235 100         if (!opts)
614 187           opts = &empty_opts;
615              
616             /* build path and find "root" where we should start calling mkdir */
617 235 50         if (git_fs_path_join_unrooted(&make_path, relative_path, base, &root) < 0)
618 0           return -1;
619              
620 235 50         if ((error = mkdir_canonicalize(&make_path, flags)) < 0 ||
    50          
621 235           make_path.size == 0)
622             goto done;
623              
624             /* if we are not supposed to make the whole path, reset root */
625 235 100         if ((flags & GIT_MKDIR_PATH) == 0)
626 9           root = git_str_rfind(&make_path, '/');
627              
628             /* advance root past drive name or network mount prefix */
629 235           min_root_len = git_fs_path_root(make_path.ptr);
630 235 50         if (root < min_root_len)
631 0           root = min_root_len;
632 249 50         while (root >= 0 && make_path.ptr[root] == '/')
    100          
633 14           ++root;
634              
635             /* clip root to make_path length */
636 235 50         if (root > (ssize_t)make_path.size)
637 0           root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
638 235 50         if (root < 0)
639 0           root = 0;
640              
641             /* walk down tail of path making each directory */
642 462 100         for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
643 227           bool mkdir_attempted = false;
644              
645             /* advance tail to include next path component */
646 253 100         while (*tail == '/')
647 26           tail++;
648 930 100         while (*tail && *tail != '/')
    100          
649 703           tail++;
650              
651             /* truncate path at next component */
652 227           lastch = *tail;
653 227           *tail = '\0';
654 227           st.st_mode = 0;
655              
656 227 50         if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr))
    0          
657 0           continue;
658              
659             /* See what's going on with this path component */
660 227           opts->perfdata.stat_calls++;
661              
662             retry_lstat:
663 227 100         if (p_lstat(make_path.ptr, &st) < 0) {
664 191 50         if (mkdir_attempted || errno != ENOENT) {
    50          
665 0           git_error_set(GIT_ERROR_OS, "cannot access component in path '%s'", make_path.ptr);
666 0           error = -1;
667 0           goto done;
668             }
669              
670 191           git_error_clear();
671 191           opts->perfdata.mkdir_calls++;
672 191           mkdir_attempted = true;
673 191 50         if (p_mkdir(make_path.ptr, mode) < 0) {
674 0 0         if (errno == EEXIST)
675 0           goto retry_lstat;
676 0           git_error_set(GIT_ERROR_OS, "failed to make directory '%s'", make_path.ptr);
677 0           error = -1;
678 0           goto done;
679             }
680             } else {
681 36 50         if ((error = mkdir_validate_dir(
682 36           make_path.ptr, &st, mode, flags, opts)) < 0)
683 0           goto done;
684             }
685              
686             /* chmod if requested and necessary */
687 227 50         if ((error = mkdir_validate_mode(
688 227           make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0)
689 0           goto done;
690              
691 227 50         if (opts->dir_map && opts->pool) {
    0          
692             char *cache_path;
693             size_t alloc_size;
694              
695 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1);
    0          
696 0           cache_path = git_pool_malloc(opts->pool, alloc_size);
697 0 0         GIT_ERROR_CHECK_ALLOC(cache_path);
698              
699 0           memcpy(cache_path, make_path.ptr, make_path.size + 1);
700              
701 0 0         if ((error = git_strmap_set(opts->dir_map, cache_path, cache_path)) < 0)
702 0           goto done;
703             }
704             }
705              
706 235           error = 0;
707              
708             /* check that full path really is a directory if requested & needed */
709 235 100         if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
    100          
710             lastch != '\0') {
711 34           opts->perfdata.stat_calls++;
712              
713 34 50         if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
    50          
714 0           git_error_set(GIT_ERROR_OS, "path is not a directory '%s'",
715             make_path.ptr);
716 0           error = GIT_ENOTFOUND;
717             }
718             }
719              
720             done:
721 235           git_str_dispose(&make_path);
722 235           return error;
723             }
724              
725             typedef struct {
726             const char *base;
727             size_t baselen;
728             uint32_t flags;
729             int depth;
730             } futils__rmdir_data;
731              
732             #define FUTILS_MAX_DEPTH 100
733              
734 0           static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
735             {
736 0 0         if (filemsg)
737 0           git_error_set(GIT_ERROR_OS, "could not remove directory '%s': %s",
738             path, filemsg);
739             else
740 0           git_error_set(GIT_ERROR_OS, "could not remove directory '%s'", path);
741              
742 0           return -1;
743             }
744              
745 0           static int futils__rm_first_parent(git_str *path, const char *ceiling)
746             {
747 0           int error = GIT_ENOTFOUND;
748             struct stat st;
749              
750 0 0         while (error == GIT_ENOTFOUND) {
751 0           git_str_rtruncate_at_char(path, '/');
752              
753 0 0         if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
    0          
754 0           error = 0;
755 0 0         else if (p_lstat_posixly(path->ptr, &st) == 0) {
756 0 0         if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
    0          
757 0           error = p_unlink(path->ptr);
758 0 0         else if (!S_ISDIR(st.st_mode))
759 0           error = -1; /* fail to remove non-regular file */
760 0 0         } else if (errno != ENOTDIR)
761 0           error = -1;
762             }
763              
764 0 0         if (error)
765 0           futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
766              
767 0           return error;
768             }
769              
770 182           static int futils__rmdir_recurs_foreach(void *opaque, git_str *path)
771             {
772 182           int error = 0;
773 182           futils__rmdir_data *data = opaque;
774             struct stat st;
775              
776 182 50         if (data->depth > FUTILS_MAX_DEPTH)
777 0           error = futils__error_cannot_rmdir(
778 0           path->ptr, "directory nesting too deep");
779              
780 182 100         else if ((error = p_lstat_posixly(path->ptr, &st)) < 0) {
781 39 50         if (errno == ENOENT)
782 39           error = 0;
783 0 0         else if (errno == ENOTDIR) {
784             /* asked to remove a/b/c/d/e and a/b is a normal file */
785 0 0         if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
786 0           error = futils__rm_first_parent(path, data->base);
787             else
788 0           futils__error_cannot_rmdir(
789 0           path->ptr, "parent is not directory");
790             }
791             else
792 39           error = git_fs_path_set_error(errno, path->ptr, "rmdir");
793             }
794              
795 143 100         else if (S_ISDIR(st.st_mode)) {
796 12           data->depth++;
797              
798 12           error = git_fs_path_direach(path, 0, futils__rmdir_recurs_foreach, data);
799              
800 12           data->depth--;
801              
802 12 50         if (error < 0)
803 0           return error;
804              
805 12 50         if (data->depth == 0 && (data->flags & GIT_RMDIR_SKIP_ROOT) != 0)
    50          
806 0           return error;
807              
808 12 50         if ((error = p_rmdir(path->ptr)) < 0) {
809 0           if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
810 0 0         (errno == ENOTEMPTY || errno == EEXIST || errno == EBUSY))
    0          
811 0           error = 0;
812             else
813 12           error = git_fs_path_set_error(errno, path->ptr, "rmdir");
814             }
815             }
816              
817 131 100         else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
818 55 50         if (p_unlink(path->ptr) < 0)
819 55           error = git_fs_path_set_error(errno, path->ptr, "remove");
820             }
821              
822 76 50         else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
823 0           error = futils__error_cannot_rmdir(path->ptr, "still present");
824              
825 182           return error;
826             }
827              
828 56           static int futils__rmdir_empty_parent(void *opaque, const char *path)
829             {
830 56           futils__rmdir_data *data = opaque;
831 56           int error = 0;
832              
833 56 100         if (strlen(path) <= data->baselen)
834 26           error = GIT_ITEROVER;
835              
836 30 100         else if (p_rmdir(path) < 0) {
837 26           int en = errno;
838              
839 26 50         if (en == ENOENT || en == ENOTDIR) {
    0          
840             /* do nothing */
841 0 0         } else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0 &&
    0          
842             en == EBUSY) {
843 0           error = git_fs_path_set_error(errno, path, "rmdir");
844 0 0         } else if (en == ENOTEMPTY || en == EEXIST || en == EBUSY) {
    0          
    0          
845 0           error = GIT_ITEROVER;
846             } else {
847 0           error = git_fs_path_set_error(errno, path, "rmdir");
848             }
849             }
850              
851 56           return error;
852             }
853              
854 145           int git_futils_rmdir_r(
855             const char *path, const char *base, uint32_t flags)
856             {
857             int error;
858 145           git_str fullpath = GIT_STR_INIT;
859             futils__rmdir_data data;
860              
861             /* build path and find "root" where we should start calling mkdir */
862 145 50         if (git_fs_path_join_unrooted(&fullpath, path, base, NULL) < 0)
863 0           return -1;
864              
865 145           memset(&data, 0, sizeof(data));
866 145 100         data.base = base ? base : "";
867 145 100         data.baselen = base ? strlen(base) : 0;
868 145           data.flags = flags;
869              
870 145           error = futils__rmdir_recurs_foreach(&data, &fullpath);
871              
872             /* remove now-empty parents if requested */
873 145 50         if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0)
    100          
874 26           error = git_fs_path_walk_up(
875             &fullpath, base, futils__rmdir_empty_parent, &data);
876              
877 145 100         if (error == GIT_ITEROVER) {
878 26           git_error_clear();
879 26           error = 0;
880             }
881              
882 145           git_str_dispose(&fullpath);
883              
884 145           return error;
885             }
886              
887 0           int git_futils_fake_symlink(const char *target, const char *path)
888             {
889 0           int retcode = GIT_ERROR;
890 0           int fd = git_futils_creat_withpath(path, 0755, 0644);
891 0 0         if (fd >= 0) {
892 0           retcode = p_write(fd, target, strlen(target));
893 0           p_close(fd);
894             }
895 0           return retcode;
896             }
897              
898 0           static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)
899             {
900 0           int error = 0;
901             char buffer[GIT_BUFSIZE_FILEIO];
902 0           ssize_t len = 0;
903              
904 0 0         while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
    0          
905             /* p_write() does not have the same semantics as write(). It loops
906             * internally and will return 0 when it has completed writing.
907             */
908 0           error = p_write(ofd, buffer, len);
909              
910 0 0         if (len < 0) {
911 0           git_error_set(GIT_ERROR_OS, "read error while copying file");
912 0           error = (int)len;
913             }
914              
915 0 0         if (error < 0)
916 0           git_error_set(GIT_ERROR_OS, "write error while copying file");
917              
918 0 0         if (close_fd_when_done) {
919 0           p_close(ifd);
920 0           p_close(ofd);
921             }
922              
923 0           return error;
924             }
925              
926 0           int git_futils_cp(const char *from, const char *to, mode_t filemode)
927             {
928             int ifd, ofd;
929              
930 0 0         if ((ifd = git_futils_open_ro(from)) < 0)
931 0           return ifd;
932              
933 0 0         if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
934 0           p_close(ifd);
935 0           return git_fs_path_set_error(errno, to, "open for writing");
936             }
937              
938 0           return cp_by_fd(ifd, ofd, true);
939             }
940              
941 289           int git_futils_touch(const char *path, time_t *when)
942             {
943             struct p_timeval times[2];
944             int ret;
945              
946 289 50         times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL);
947 289           times[0].tv_usec = times[1].tv_usec = 0;
948              
949 289           ret = p_utimes(path, times);
950              
951 289 100         return (ret < 0) ? git_fs_path_set_error(errno, path, "touch") : 0;
952             }
953              
954 0           static int cp_link(const char *from, const char *to, size_t link_size)
955             {
956 0           int error = 0;
957             ssize_t read_len;
958             char *link_data;
959             size_t alloc_size;
960              
961 0 0         GIT_ERROR_CHECK_ALLOC_ADD(&alloc_size, link_size, 1);
    0          
962 0           link_data = git__malloc(alloc_size);
963 0 0         GIT_ERROR_CHECK_ALLOC(link_data);
964              
965 0           read_len = p_readlink(from, link_data, link_size);
966 0 0         if (read_len != (ssize_t)link_size) {
967 0           git_error_set(GIT_ERROR_OS, "failed to read symlink data for '%s'", from);
968 0           error = -1;
969             }
970             else {
971 0           link_data[read_len] = '\0';
972              
973 0 0         if (p_symlink(link_data, to) < 0) {
974 0           git_error_set(GIT_ERROR_OS, "could not symlink '%s' as '%s'",
975             link_data, to);
976 0           error = -1;
977             }
978             }
979              
980 0           git__free(link_data);
981 0           return error;
982             }
983              
984             typedef struct {
985             const char *to_root;
986             git_str to;
987             ssize_t from_prefix;
988             uint32_t flags;
989             uint32_t mkdir_flags;
990             mode_t dirmode;
991             } cp_r_info;
992              
993             #define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
994              
995 0           static int _cp_r_mkdir(cp_r_info *info, git_str *from)
996             {
997 0           int error = 0;
998              
999             /* create root directory the first time we need to create a directory */
1000 0 0         if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {
1001 0 0         error = git_futils_mkdir(
1002             info->to_root, info->dirmode,
1003 0           (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);
1004              
1005 0           info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;
1006             }
1007              
1008             /* create directory with root as base to prevent excess chmods */
1009 0 0         if (!error)
1010 0           error = git_futils_mkdir_relative(
1011 0           from->ptr + info->from_prefix, info->to_root,
1012             info->dirmode, info->mkdir_flags, NULL);
1013              
1014 0           return error;
1015             }
1016              
1017 0           static int _cp_r_callback(void *ref, git_str *from)
1018             {
1019 0           int error = 0;
1020 0           cp_r_info *info = ref;
1021             struct stat from_st, to_st;
1022 0           bool exists = false;
1023              
1024 0           if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
1025 0           from->ptr[git_fs_path_basename_offset(from)] == '.')
1026 0           return 0;
1027              
1028 0 0         if ((error = git_str_joinpath(
1029 0           &info->to, info->to_root, from->ptr + info->from_prefix)) < 0)
1030 0           return error;
1031              
1032 0 0         if (!(error = git_fs_path_lstat(info->to.ptr, &to_st)))
1033 0           exists = true;
1034 0 0         else if (error != GIT_ENOTFOUND)
1035 0           return error;
1036             else {
1037 0           git_error_clear();
1038 0           error = 0;
1039             }
1040              
1041 0 0         if ((error = git_fs_path_lstat(from->ptr, &from_st)) < 0)
1042 0           return error;
1043              
1044 0 0         if (S_ISDIR(from_st.st_mode)) {
1045 0           mode_t oldmode = info->dirmode;
1046              
1047             /* if we are not chmod'ing, then overwrite dirmode */
1048 0 0         if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)
1049 0           info->dirmode = from_st.st_mode;
1050              
1051             /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
1052 0 0         if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
    0          
1053 0           error = _cp_r_mkdir(info, from);
1054              
1055             /* recurse onto target directory */
1056 0 0         if (!error && (!exists || S_ISDIR(to_st.st_mode)))
    0          
    0          
1057 0           error = git_fs_path_direach(from, 0, _cp_r_callback, info);
1058              
1059 0 0         if (oldmode != 0)
1060 0           info->dirmode = oldmode;
1061              
1062 0           return error;
1063             }
1064              
1065 0 0         if (exists) {
1066 0 0         if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
1067 0           return 0;
1068              
1069 0 0         if (p_unlink(info->to.ptr) < 0) {
1070 0           git_error_set(GIT_ERROR_OS, "cannot overwrite existing file '%s'",
1071             info->to.ptr);
1072 0           return GIT_EEXISTS;
1073             }
1074             }
1075              
1076             /* Done if this isn't a regular file or a symlink */
1077 0 0         if (!S_ISREG(from_st.st_mode) &&
    0          
1078 0 0         (!S_ISLNK(from_st.st_mode) ||
1079 0           (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
1080 0           return 0;
1081              
1082             /* Make container directory on demand if needed */
1083 0 0         if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
    0          
1084             (error = _cp_r_mkdir(info, from)) < 0)
1085 0           return error;
1086              
1087             /* make symlink or regular file */
1088 0 0         if (info->flags & GIT_CPDIR_LINK_FILES) {
1089 0 0         if ((error = p_link(from->ptr, info->to.ptr)) < 0)
1090 0           git_error_set(GIT_ERROR_OS, "failed to link '%s'", from->ptr);
1091 0 0         } else if (S_ISLNK(from_st.st_mode)) {
1092 0           error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);
1093             } else {
1094 0           mode_t usemode = from_st.st_mode;
1095              
1096 0 0         if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)
1097 0 0         usemode = GIT_PERMS_FOR_WRITE(usemode);
1098              
1099 0           error = git_futils_cp(from->ptr, info->to.ptr, usemode);
1100             }
1101              
1102 0           return error;
1103             }
1104              
1105 0           int git_futils_cp_r(
1106             const char *from,
1107             const char *to,
1108             uint32_t flags,
1109             mode_t dirmode)
1110             {
1111             int error;
1112 0           git_str path = GIT_STR_INIT;
1113             cp_r_info info;
1114              
1115 0 0         if (git_str_joinpath(&path, from, "") < 0) /* ensure trailing slash */
1116 0           return -1;
1117              
1118 0           memset(&info, 0, sizeof(info));
1119 0           info.to_root = to;
1120 0           info.flags = flags;
1121 0           info.dirmode = dirmode;
1122 0           info.from_prefix = path.size;
1123 0           git_str_init(&info.to, 0);
1124              
1125             /* precalculate mkdir flags */
1126 0 0         if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
1127             /* if not creating empty dirs, then use mkdir to create the path on
1128             * demand right before files are copied.
1129             */
1130 0           info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
1131 0 0         if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)
1132 0           info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
1133             } else {
1134             /* otherwise, we will do simple mkdir as directories are encountered */
1135 0 0         info.mkdir_flags =
1136 0           ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;
1137             }
1138              
1139 0           error = _cp_r_callback(&info, &path);
1140              
1141 0           git_str_dispose(&path);
1142 0           git_str_dispose(&info.to);
1143              
1144 0           return error;
1145             }
1146              
1147 2676           int git_futils_filestamp_check(
1148             git_futils_filestamp *stamp, const char *path)
1149             {
1150             struct stat st;
1151              
1152             /* if the stamp is NULL, then always reload */
1153 2676 50         if (stamp == NULL)
1154 0           return 1;
1155              
1156 2676 100         if (p_stat(path, &st) < 0)
1157 334           return GIT_ENOTFOUND;
1158              
1159 2342 100         if (stamp->mtime.tv_sec == st.st_mtime &&
    100          
1160             #if defined(GIT_USE_NSEC)
1161             stamp->mtime.tv_nsec == st.st_mtime_nsec &&
1162             #endif
1163 2256 100         stamp->size == (uint64_t)st.st_size &&
1164 2256           stamp->ino == (unsigned int)st.st_ino)
1165 2216           return 0;
1166              
1167 126           stamp->mtime.tv_sec = st.st_mtime;
1168             #if defined(GIT_USE_NSEC)
1169             stamp->mtime.tv_nsec = st.st_mtime_nsec;
1170             #endif
1171 126           stamp->size = (uint64_t)st.st_size;
1172 126           stamp->ino = (unsigned int)st.st_ino;
1173              
1174 2676           return 1;
1175             }
1176              
1177 82           void git_futils_filestamp_set(
1178             git_futils_filestamp *target, const git_futils_filestamp *source)
1179             {
1180 82 100         if (source)
1181 11           memcpy(target, source, sizeof(*target));
1182             else
1183 71           memset(target, 0, sizeof(*target));
1184 82           }
1185              
1186              
1187 171           void git_futils_filestamp_set_from_stat(
1188             git_futils_filestamp *stamp, struct stat *st)
1189             {
1190 171 50         if (st) {
1191 171           stamp->mtime.tv_sec = st->st_mtime;
1192             #if defined(GIT_USE_NSEC)
1193             stamp->mtime.tv_nsec = st->st_mtime_nsec;
1194             #else
1195 171           stamp->mtime.tv_nsec = 0;
1196             #endif
1197 171           stamp->size = (uint64_t)st->st_size;
1198 171           stamp->ino = (unsigned int)st->st_ino;
1199             } else {
1200 0           memset(stamp, 0, sizeof(*stamp));
1201             }
1202 171           }
1203              
1204 0           int git_futils_fsync_dir(const char *path)
1205             {
1206             #ifdef GIT_WIN32
1207             GIT_UNUSED(path);
1208             return 0;
1209             #else
1210 0           int fd, error = -1;
1211              
1212 0 0         if ((fd = p_open(path, O_RDONLY)) < 0) {
1213 0           git_error_set(GIT_ERROR_OS, "failed to open directory '%s' for fsync", path);
1214 0           return -1;
1215             }
1216              
1217 0 0         if ((error = p_fsync(fd)) < 0)
1218 0           git_error_set(GIT_ERROR_OS, "failed to fsync directory '%s'", path);
1219              
1220 0           p_close(fd);
1221 0           return error;
1222             #endif
1223             }
1224              
1225 0           int git_futils_fsync_parent(const char *path)
1226             {
1227             char *parent;
1228             int error;
1229              
1230 0 0         if ((parent = git_fs_path_dirname(path)) == NULL)
1231 0           return -1;
1232              
1233 0           error = git_futils_fsync_dir(parent);
1234 0           git__free(parent);
1235 0           return error;
1236             }