File Coverage

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