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