2 * Copyright (C) the libgit2 contributors. All rights reserved.
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.
15 #include "win32/findfile.h"
18 int git_futils_mkpath2file(const char *file_path
, const mode_t mode
)
20 return git_futils_mkdir(
22 GIT_MKDIR_PATH
| GIT_MKDIR_SKIP_LAST
| GIT_MKDIR_VERIFY_DIR
);
25 int git_futils_mktmp(git_buf
*path_out
, const char *filename
, mode_t mode
)
30 p_umask(mask
= p_umask(0));
32 git_buf_sets(path_out
, filename
);
33 git_buf_puts(path_out
, "_git2_XXXXXX");
35 if (git_buf_oom(path_out
))
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
);
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
);
53 int git_futils_creat_withpath(const char *path
, const mode_t dirmode
, const mode_t mode
)
57 if (git_futils_mkpath2file(path
, dirmode
) < 0)
60 fd
= p_creat(path
, mode
);
62 git_error_set(GIT_ERROR_OS
, "failed to create file '%s'", path
);
69 int git_futils_creat_locked(const char *path
, const mode_t mode
)
71 int fd
= p_open(path
, O_WRONLY
| O_CREAT
| O_EXCL
| O_BINARY
| O_CLOEXEC
,
76 git_error_set(GIT_ERROR_OS
, "failed to create locked file '%s'", path
);
90 int git_futils_creat_locked_withpath(const char *path
, const mode_t dirmode
, const mode_t mode
)
92 if (git_futils_mkpath2file(path
, dirmode
) < 0)
95 return git_futils_creat_locked(path
, mode
);
98 int git_futils_open_ro(const char *path
)
100 int fd
= p_open(path
, O_RDONLY
);
102 return git_path_set_error(errno
, path
, "open");
106 int git_futils_truncate(const char *path
, int mode
)
108 int fd
= p_open(path
, O_WRONLY
| O_CREAT
| O_TRUNC
| O_CLOEXEC
, mode
);
110 return git_path_set_error(errno
, path
, "open");
116 int git_futils_filesize(uint64_t *out
, git_file fd
)
120 if (p_fstat(fd
, &sb
)) {
121 git_error_set(GIT_ERROR_OS
, "failed to stat file descriptor");
125 if (sb
.st_size
< 0) {
126 git_error_set(GIT_ERROR_INVALID
, "invalid file size");
134 mode_t
git_futils_canonical_mode(mode_t raw_mode
)
136 if (S_ISREG(raw_mode
))
137 return S_IFREG
| GIT_PERMS_CANONICAL(raw_mode
);
138 else if (S_ISLNK(raw_mode
))
140 else if (S_ISGITLINK(raw_mode
))
142 else if (S_ISDIR(raw_mode
))
148 int git_futils_readbuffer_fd(git_buf
*buf
, git_file fd
, size_t len
)
150 ssize_t read_size
= 0;
155 if (!git__is_ssizet(len
)) {
156 git_error_set(GIT_ERROR_INVALID
, "read too large");
160 GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len
, len
, 1);
161 if (git_buf_grow(buf
, alloc_len
) < 0)
164 /* p_read loops internally to read len bytes */
165 read_size
= p_read(fd
, buf
->ptr
, len
);
167 if (read_size
!= (ssize_t
)len
) {
168 git_error_set(GIT_ERROR_OS
, "failed to read descriptor");
169 git_buf_dispose(buf
);
173 buf
->ptr
[read_size
] = '\0';
174 buf
->size
= read_size
;
179 int git_futils_readbuffer_updated(
180 git_buf
*out
, const char *path
, git_oid
*checksum
, int *updated
)
185 git_buf buf
= GIT_BUF_INIT
;
186 git_oid checksum_new
;
189 GIT_ASSERT_ARG(path
&& *path
);
194 if (p_stat(path
, &st
) < 0)
195 return git_path_set_error(errno
, path
, "stat");
198 if (S_ISDIR(st
.st_mode
)) {
199 git_error_set(GIT_ERROR_INVALID
, "requested file is a directory");
200 return GIT_ENOTFOUND
;
203 if (!git__is_sizet(st
.st_size
+1)) {
204 git_error_set(GIT_ERROR_OS
, "invalid regular file stat for '%s'", path
);
208 if ((fd
= git_futils_open_ro(path
)) < 0)
211 if (git_futils_readbuffer_fd(&buf
, fd
, (size_t)st
.st_size
) < 0) {
219 if ((error
= git_hash_buf(&checksum_new
, buf
.ptr
, buf
.size
)) < 0) {
220 git_buf_dispose(&buf
);
225 * If we were given a checksum, we only want to use it if it's different
227 if (!git_oid__cmp(checksum
, &checksum_new
)) {
228 git_buf_dispose(&buf
);
235 git_oid_cpy(checksum
, &checksum_new
);
239 * If we're here, the file did change, or the user didn't have an old version
244 git_buf_swap(out
, &buf
);
245 git_buf_dispose(&buf
);
250 int git_futils_readbuffer(git_buf
*buf
, const char *path
)
252 return git_futils_readbuffer_updated(buf
, path
, NULL
, NULL
);
255 int git_futils_writebuffer(
256 const git_buf
*buf
, const char *path
, int flags
, mode_t mode
)
258 int fd
, do_fsync
= 0, error
= 0;
261 flags
= O_CREAT
| O_TRUNC
| O_WRONLY
;
263 if ((flags
& O_FSYNC
) != 0)
269 mode
= GIT_FILEMODE_BLOB
;
271 if ((fd
= p_open(path
, flags
, mode
)) < 0) {
272 git_error_set(GIT_ERROR_OS
, "could not open '%s' for writing", path
);
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
);
282 if (do_fsync
&& (error
= p_fsync(fd
)) < 0) {
283 git_error_set(GIT_ERROR_OS
, "could not fsync '%s'", path
);
288 if ((error
= p_close(fd
)) < 0) {
289 git_error_set(GIT_ERROR_OS
, "error while closing '%s'", path
);
293 if (do_fsync
&& (flags
& O_CREAT
))
294 error
= git_futils_fsync_parent(path
);
299 int git_futils_mv_withpath(const char *from
, const char *to
, const mode_t dirmode
)
301 if (git_futils_mkpath2file(to
, dirmode
) < 0)
304 if (p_rename(from
, to
) < 0) {
305 git_error_set(GIT_ERROR_OS
, "failed to rename '%s' to '%s'", from
, to
);
312 int git_futils_mmap_ro(git_map
*out
, git_file fd
, off64_t begin
, size_t len
)
314 return p_mmap(out
, len
, GIT_PROT_READ
, GIT_MAP_SHARED
, fd
, begin
);
317 int git_futils_mmap_ro_file(git_map
*out
, const char *path
)
319 git_file fd
= git_futils_open_ro(path
);
326 if ((result
= git_futils_filesize(&len
, fd
)) < 0)
329 if (!git__is_sizet(len
)) {
330 git_error_set(GIT_ERROR_OS
, "file `%s` too large to mmap", path
);
335 result
= git_futils_mmap_ro(out
, fd
, 0, (size_t)len
);
341 void git_futils_mmap_free(git_map
*out
)
346 GIT_INLINE(int) mkdir_validate_dir(
351 struct git_futils_mkdir_options
*opts
)
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
);
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
);
368 opts
->perfdata
.mkdir_calls
++;
370 if (p_mkdir(path
, mode
) < 0) {
371 git_error_set(GIT_ERROR_OS
, "failed to make directory '%s'", path
);
376 else if (S_ISLNK(st
->st_mode
)) {
377 /* Re-stat the target, make sure it's a directory */
378 opts
->perfdata
.stat_calls
++;
380 if (p_stat(path
, st
) < 0) {
381 git_error_set(GIT_ERROR_OS
, "failed to make directory '%s'", path
);
386 else if (!S_ISDIR(st
->st_mode
)) {
387 git_error_set(GIT_ERROR_FILESYSTEM
,
388 "failed to make directory '%s': directory exists", path
);
395 GIT_INLINE(int) mkdir_validate_mode(
401 struct git_futils_mkdir_options
*opts
)
403 if (((terminal_path
&& (flags
& GIT_MKDIR_CHMOD
) != 0) ||
404 (flags
& GIT_MKDIR_CHMOD_PATH
) != 0) && st
->st_mode
!= mode
) {
406 opts
->perfdata
.chmod_calls
++;
408 if (p_chmod(path
, mode
) < 0) {
409 git_error_set(GIT_ERROR_OS
, "failed to set permissions on '%s'", path
);
417 GIT_INLINE(int) mkdir_canonicalize(
423 if (path
->size
== 0) {
424 git_error_set(GIT_ERROR_OS
, "attempt to create empty path");
428 /* Trim trailing slashes (except the root) */
429 if ((root_len
= git_path_root(path
->ptr
)) < 0)
434 while (path
->size
> (size_t)root_len
&& path
->ptr
[path
->size
- 1] == '/')
435 path
->ptr
[--path
->size
] = '\0';
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
;
442 if ((flags
& GIT_MKDIR_SKIP_LAST
) != 0) {
443 git_path_dirname_r(path
, path
->ptr
);
446 /* We were either given the root path (or trimmed it to
447 * the root), we don't have anything to do.
449 if (path
->size
<= (size_t)root_len
)
455 int git_futils_mkdir(
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 };
465 int len
= 0, root_len
, error
;
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 ||
473 root_len
= git_path_root(make_path
.ptr
);
475 /* find the first parent directory that exists. this will be used
476 * as the base to dirname_relative.
478 for (relative
= make_path
.ptr
; parent_path
.size
; ) {
479 error
= p_lstat(parent_path
.ptr
, &st
);
483 } else if (errno
!= ENOENT
) {
484 git_error_set(GIT_ERROR_OS
, "failed to stat '%s'", parent_path
.ptr
);
491 /* examine the parent of the current path */
492 if ((len
= git_path_dirname_r(&parent_path
, parent_path
.ptr
)) < 0) {
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:/`.
505 if ((len
== 1 && parent_path
.ptr
[0] == '.') ||
506 (len
== 1 && parent_path
.ptr
[0] == '/') ||
508 relative
= make_path
.ptr
;
512 relative
= make_path
.ptr
+ len
+ 1;
514 /* not recursive? just make this directory relative to its parent. */
515 if ((flags
& GIT_MKDIR_PATH
) == 0)
519 /* we found an item at the location we're trying to create,
523 error
= mkdir_validate_dir(make_path
.ptr
, &st
, mode
, flags
, &opts
);
526 error
= mkdir_validate_mode(
527 make_path
.ptr
, &st
, true, mode
, flags
, &opts
);
532 /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when
533 * canonicalizing `make_path`.
535 flags
&= ~(GIT_MKDIR_SKIP_LAST2
| GIT_MKDIR_SKIP_LAST
);
537 error
= git_futils_mkdir_relative(relative
,
538 parent_path
.size
? parent_path
.ptr
: NULL
, mode
, flags
, &opts
);
541 git_buf_dispose(&make_path
);
542 git_buf_dispose(&parent_path
);
546 int git_futils_mkdir_r(const char *path
, const mode_t mode
)
548 return git_futils_mkdir(path
, mode
, GIT_MKDIR_PATH
);
551 int git_futils_mkdir_relative(
552 const char *relative_path
,
556 struct git_futils_mkdir_options
*opts
)
558 git_buf make_path
= GIT_BUF_INIT
;
559 ssize_t root
= 0, min_root_len
;
560 char lastch
= '/', *tail
;
562 struct git_futils_mkdir_options empty_opts
= {0};
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)
572 if ((error
= mkdir_canonicalize(&make_path
, flags
)) < 0 ||
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
, '/');
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
)
584 while (root
>= 0 && make_path
.ptr
[root
] == '/')
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 */
593 /* walk down tail of path making each directory */
594 for (tail
= &make_path
.ptr
[root
]; *tail
; *tail
= lastch
) {
595 bool mkdir_attempted
= false;
597 /* advance tail to include next path component */
600 while (*tail
&& *tail
!= '/')
603 /* truncate path at next component */
608 if (opts
->dir_map
&& git_strmap_exists(opts
->dir_map
, make_path
.ptr
))
611 /* See what's going on with this path component */
612 opts
->perfdata
.stat_calls
++;
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
);
623 opts
->perfdata
.mkdir_calls
++;
624 mkdir_attempted
= true;
625 if (p_mkdir(make_path
.ptr
, mode
) < 0) {
628 git_error_set(GIT_ERROR_OS
, "failed to make directory '%s'", make_path
.ptr
);
633 if ((error
= mkdir_validate_dir(
634 make_path
.ptr
, &st
, mode
, flags
, opts
)) < 0)
638 /* chmod if requested and necessary */
639 if ((error
= mkdir_validate_mode(
640 make_path
.ptr
, &st
, (lastch
== '\0'), mode
, flags
, opts
)) < 0)
643 if (opts
->dir_map
&& opts
->pool
) {
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
);
651 memcpy(cache_path
, make_path
.ptr
, make_path
.size
+ 1);
653 if ((error
= git_strmap_set(opts
->dir_map
, cache_path
, cache_path
)) < 0)
660 /* check that full path really is a directory if requested & needed */
661 if ((flags
& GIT_MKDIR_VERIFY_DIR
) != 0 &&
663 opts
->perfdata
.stat_calls
++;
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'",
668 error
= GIT_ENOTFOUND
;
673 git_buf_dispose(&make_path
);
682 } futils__rmdir_data
;
684 #define FUTILS_MAX_DEPTH 100
686 static int futils__error_cannot_rmdir(const char *path
, const char *filemsg
)
689 git_error_set(GIT_ERROR_OS
, "could not remove directory '%s': %s",
692 git_error_set(GIT_ERROR_OS
, "could not remove directory '%s'", path
);
697 static int futils__rm_first_parent(git_buf
*path
, const char *ceiling
)
699 int error
= GIT_ENOTFOUND
;
702 while (error
== GIT_ENOTFOUND
) {
703 git_buf_rtruncate_at_char(path
, '/');
705 if (!path
->size
|| git__prefixcmp(path
->ptr
, ceiling
) != 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
)
717 futils__error_cannot_rmdir(path
->ptr
, "cannot remove parent");
722 static int futils__rmdir_recurs_foreach(void *opaque
, git_buf
*path
)
725 futils__rmdir_data
*data
= opaque
;
728 if (data
->depth
> FUTILS_MAX_DEPTH
)
729 error
= futils__error_cannot_rmdir(
730 path
->ptr
, "directory nesting too deep");
732 else if ((error
= p_lstat_posixly(path
->ptr
, &st
)) < 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
);
740 futils__error_cannot_rmdir(
741 path
->ptr
, "parent is not directory");
744 error
= git_path_set_error(errno
, path
->ptr
, "rmdir");
747 else if (S_ISDIR(st
.st_mode
)) {
750 error
= git_path_direach(path
, 0, futils__rmdir_recurs_foreach
, data
);
757 if (data
->depth
== 0 && (data
->flags
& GIT_RMDIR_SKIP_ROOT
) != 0)
760 if ((error
= p_rmdir(path
->ptr
)) < 0) {
761 if ((data
->flags
& GIT_RMDIR_SKIP_NONEMPTY
) != 0 &&
762 (errno
== ENOTEMPTY
|| errno
== EEXIST
|| errno
== EBUSY
))
765 error
= git_path_set_error(errno
, path
->ptr
, "rmdir");
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");
774 else if ((data
->flags
& GIT_RMDIR_SKIP_NONEMPTY
) == 0)
775 error
= futils__error_cannot_rmdir(path
->ptr
, "still present");
780 static int futils__rmdir_empty_parent(void *opaque
, const char *path
)
782 futils__rmdir_data
*data
= opaque
;
785 if (strlen(path
) <= data
->baselen
)
786 error
= GIT_ITEROVER
;
788 else if (p_rmdir(path
) < 0) {
791 if (en
== ENOENT
|| en
== ENOTDIR
) {
793 } else if ((data
->flags
& GIT_RMDIR_SKIP_NONEMPTY
) == 0 &&
795 error
= git_path_set_error(errno
, path
, "rmdir");
796 } else if (en
== ENOTEMPTY
|| en
== EEXIST
|| en
== EBUSY
) {
797 error
= GIT_ITEROVER
;
799 error
= git_path_set_error(errno
, path
, "rmdir");
806 int git_futils_rmdir_r(
807 const char *path
, const char *base
, uint32_t flags
)
810 git_buf fullpath
= GIT_BUF_INIT
;
811 futils__rmdir_data data
;
813 /* build path and find "root" where we should start calling mkdir */
814 if (git_path_join_unrooted(&fullpath
, path
, base
, NULL
) < 0)
817 memset(&data
, 0, sizeof(data
));
818 data
.base
= base
? base
: "";
819 data
.baselen
= base
? strlen(base
) : 0;
822 error
= futils__rmdir_recurs_foreach(&data
, &fullpath
);
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
);
829 if (error
== GIT_ITEROVER
) {
834 git_buf_dispose(&fullpath
);
839 int git_futils_fake_symlink(const char *target
, const char *path
)
841 int retcode
= GIT_ERROR
;
842 int fd
= git_futils_creat_withpath(path
, 0755, 0644);
844 retcode
= p_write(fd
, target
, strlen(target
));
850 static int cp_by_fd(int ifd
, int ofd
, bool close_fd_when_done
)
853 char buffer
[FILEIO_BUFSIZE
];
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.
860 error
= p_write(ofd
, buffer
, len
);
863 git_error_set(GIT_ERROR_OS
, "read error while copying file");
868 git_error_set(GIT_ERROR_OS
, "write error while copying file");
870 if (close_fd_when_done
) {
878 int git_futils_cp(const char *from
, const char *to
, mode_t filemode
)
882 if ((ifd
= git_futils_open_ro(from
)) < 0)
885 if ((ofd
= p_open(to
, O_WRONLY
| O_CREAT
| O_EXCL
, filemode
)) < 0) {
887 return git_path_set_error(errno
, to
, "open for writing");
890 return cp_by_fd(ifd
, ofd
, true);
893 int git_futils_touch(const char *path
, time_t *when
)
895 struct p_timeval times
[2];
898 times
[0].tv_sec
= times
[1].tv_sec
= when
? *when
: time(NULL
);
899 times
[0].tv_usec
= times
[1].tv_usec
= 0;
901 ret
= p_utimes(path
, times
);
903 return (ret
< 0) ? git_path_set_error(errno
, path
, "touch") : 0;
906 static int cp_link(const char *from
, const char *to
, size_t link_size
)
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
);
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
);
923 link_data
[read_len
] = '\0';
925 if (p_symlink(link_data
, to
) < 0) {
926 git_error_set(GIT_ERROR_OS
, "could not symlink '%s' as '%s'",
932 git__free(link_data
);
941 uint32_t mkdir_flags
;
945 #define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)
947 static int _cp_r_mkdir(cp_r_info
*info
, git_buf
*from
)
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);
957 info
->flags
|= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT
;
960 /* create directory with root as base to prevent excess chmods */
962 error
= git_futils_mkdir_relative(
963 from
->ptr
+ info
->from_prefix
, info
->to_root
,
964 info
->dirmode
, info
->mkdir_flags
, NULL
);
969 static int _cp_r_callback(void *ref
, git_buf
*from
)
972 cp_r_info
*info
= ref
;
973 struct stat from_st
, to_st
;
976 if ((info
->flags
& GIT_CPDIR_COPY_DOTFILES
) == 0 &&
977 from
->ptr
[git_path_basename_offset(from
)] == '.')
980 if ((error
= git_buf_joinpath(
981 &info
->to
, info
->to_root
, from
->ptr
+ info
->from_prefix
)) < 0)
984 if (!(error
= git_path_lstat(info
->to
.ptr
, &to_st
)))
986 else if (error
!= GIT_ENOTFOUND
)
993 if ((error
= git_path_lstat(from
->ptr
, &from_st
)) < 0)
996 if (S_ISDIR(from_st
.st_mode
)) {
997 mode_t oldmode
= info
->dirmode
;
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
;
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
);
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
);
1012 info
->dirmode
= oldmode
;
1018 if ((info
->flags
& GIT_CPDIR_OVERWRITE
) == 0)
1021 if (p_unlink(info
->to
.ptr
) < 0) {
1022 git_error_set(GIT_ERROR_OS
, "cannot overwrite existing file '%s'",
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))
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)
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
);
1046 mode_t usemode
= from_st
.st_mode
;
1048 if ((info
->flags
& GIT_CPDIR_SIMPLE_TO_MODE
) != 0)
1049 usemode
= GIT_PERMS_FOR_WRITE(usemode
);
1051 error
= git_futils_cp(from
->ptr
, info
->to
.ptr
, usemode
);
1057 int git_futils_cp_r(
1064 git_buf path
= GIT_BUF_INIT
;
1067 if (git_buf_joinpath(&path
, from
, "") < 0) /* ensure trailing slash */
1070 memset(&info
, 0, sizeof(info
));
1073 info
.dirmode
= dirmode
;
1074 info
.from_prefix
= path
.size
;
1075 git_buf_init(&info
.to
, 0);
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.
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
;
1086 /* otherwise, we will do simple mkdir as directories are encountered */
1088 ((flags
& GIT_CPDIR_CHMOD_DIRS
) != 0) ? GIT_MKDIR_CHMOD
: 0;
1091 error
= _cp_r_callback(&info
, &path
);
1093 git_buf_dispose(&path
);
1094 git_buf_dispose(&info
.to
);
1099 int git_futils_filestamp_check(
1100 git_futils_filestamp
*stamp
, const char *path
)
1104 /* if the stamp is NULL, then always reload */
1108 if (p_stat(path
, &st
) < 0)
1109 return GIT_ENOTFOUND
;
1111 if (stamp
->mtime
.tv_sec
== st
.st_mtime
&&
1112 #if defined(GIT_USE_NSEC)
1113 stamp
->mtime
.tv_nsec
== st
.st_mtime_nsec
&&
1115 stamp
->size
== (uint64_t)st
.st_size
&&
1116 stamp
->ino
== (unsigned int)st
.st_ino
)
1119 stamp
->mtime
.tv_sec
= st
.st_mtime
;
1120 #if defined(GIT_USE_NSEC)
1121 stamp
->mtime
.tv_nsec
= st
.st_mtime_nsec
;
1123 stamp
->size
= (uint64_t)st
.st_size
;
1124 stamp
->ino
= (unsigned int)st
.st_ino
;
1129 void git_futils_filestamp_set(
1130 git_futils_filestamp
*target
, const git_futils_filestamp
*source
)
1133 memcpy(target
, source
, sizeof(*target
));
1135 memset(target
, 0, sizeof(*target
));
1139 void git_futils_filestamp_set_from_stat(
1140 git_futils_filestamp
*stamp
, struct stat
*st
)
1143 stamp
->mtime
.tv_sec
= st
->st_mtime
;
1144 #if defined(GIT_USE_NSEC)
1145 stamp
->mtime
.tv_nsec
= st
->st_mtime_nsec
;
1147 stamp
->mtime
.tv_nsec
= 0;
1149 stamp
->size
= (uint64_t)st
->st_size
;
1150 stamp
->ino
= (unsigned int)st
->st_ino
;
1152 memset(stamp
, 0, sizeof(*stamp
));
1156 int git_futils_fsync_dir(const char *path
)
1164 if ((fd
= p_open(path
, O_RDONLY
)) < 0) {
1165 git_error_set(GIT_ERROR_OS
, "failed to open directory '%s' for fsync", path
);
1169 if ((error
= p_fsync(fd
)) < 0)
1170 git_error_set(GIT_ERROR_OS
, "failed to fsync directory '%s'", path
);
1177 int git_futils_fsync_parent(const char *path
)
1182 if ((parent
= git_path_dirname(path
)) == NULL
)
1185 error
= git_futils_fsync_dir(parent
);