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