summaryrefslogtreecommitdiff
path: root/src/fileops.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fileops.c')
-rw-r--r--src/fileops.c338
1 files changed, 270 insertions, 68 deletions
diff --git a/src/fileops.c b/src/fileops.c
index 70c5c387..5aa6632e 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -10,19 +10,8 @@
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
- int result = 0;
- git_buf target_folder = GIT_BUF_INIT;
-
- if (git_path_dirname_r(&target_folder, file_path) < 0)
- return -1;
-
- /* Does the containing folder exist? */
- if (git_path_isdir(target_folder.ptr) == false)
- /* Let's create the tree structure */
- result = git_futils_mkdir_r(target_folder.ptr, NULL, mode);
-
- git_buf_free(&target_folder);
- return result;
+ return git_futils_mkdir(
+ file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
}
int git_futils_mktmp(git_buf *path_out, const char *filename)
@@ -239,76 +228,90 @@ void git_futils_mmap_free(git_map *out)
p_munmap(out);
}
-int git_futils_mkdir_q(const char *path, const mode_t mode)
-{
- if (p_mkdir(path, mode) < 0 && errno != EEXIST) {
- giterr_set(GITERR_OS, "Failed to create directory at '%s'", path);
- return -1;
- }
-
- return 0;
-}
-
-int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
+int git_futils_mkdir(
+ const char *path,
+ const char *base,
+ mode_t mode,
+ uint32_t flags)
{
git_buf make_path = GIT_BUF_INIT;
- size_t start = 0;
- char *pp, *sp;
- bool failed = false;
-
- if (base != NULL) {
- /*
- * when a base is being provided, it is supposed to already exist.
- * Therefore, no attempt is being made to recursively create this leading path
- * segment. It's just skipped. */
- start = strlen(base);
- if (git_buf_joinpath(&make_path, base, path) < 0)
- return -1;
- } else {
- int root_path_offset;
+ ssize_t root = 0;
+ char lastch, *tail;
- if (git_buf_puts(&make_path, path) < 0)
- return -1;
+ /* build path and find "root" where we should start calling mkdir */
+ if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
+ return -1;
- root_path_offset = git_path_root(make_path.ptr);
- if (root_path_offset > 0) {
- /*
- * On Windows, will skip the drive name (eg. C: or D:)
- * or the leading part of a network path (eg. //computer_name ) */
- start = root_path_offset;
- }
+ if (make_path.size == 0) {
+ giterr_set(GITERR_OS, "Attempt to create empty path");
+ goto fail;
}
- pp = make_path.ptr + start;
-
- while (!failed && (sp = strchr(pp, '/')) != NULL) {
- if (sp != pp && git_path_isdir(make_path.ptr) == false) {
- *sp = 0;
-
- /* Do not choke while trying to recreate an existing directory */
- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
- failed = true;
+ /* remove trailing slashes on path */
+ while (make_path.ptr[make_path.size - 1] == '/') {
+ make_path.size--;
+ make_path.ptr[make_path.size] = '\0';
+ }
- *sp = '/';
+ /* if we are not supposed to made the last element, truncate it */
+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
+ git_buf_rtruncate_at_char(&make_path, '/');
+
+ /* if we are not supposed to make the whole path, reset root */
+ if ((flags & GIT_MKDIR_PATH) == 0)
+ root = git_buf_rfind(&make_path, '/');
+
+ /* clip root to make_path length */
+ if (root >= (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size - 1;
+
+ tail = & make_path.ptr[root];
+
+ while (*tail) {
+ /* advance tail to include next path component */
+ while (*tail == '/')
+ tail++;
+ while (*tail && *tail != '/')
+ tail++;
+
+ /* truncate path at next component */
+ lastch = *tail;
+ *tail = '\0';
+
+ /* make directory */
+ if (p_mkdir(make_path.ptr, mode) < 0 &&
+ (errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
+ {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'",
+ make_path.ptr);
+ goto fail;
}
- pp = sp + 1;
- }
+ /* chmod if requested */
+ if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
+ ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))
+ {
+ if (p_chmod(make_path.ptr, mode) < 0) {
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'",
+ make_path.ptr);
+ goto fail;
+ }
+ }
- if (*pp != '\0' && !failed) {
- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)
- failed = true;
+ *tail = lastch;
}
git_buf_free(&make_path);
+ return 0;
- if (failed) {
- giterr_set(GITERR_OS,
- "Failed to create directory structure at '%s'", path);
- return -1;
- }
+fail:
+ git_buf_free(&make_path);
+ return -1;
+}
- return 0;
+int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
+{
+ return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
}
static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
@@ -505,3 +508,202 @@ int git_futils_fake_symlink(const char *old, const char *new)
}
return retcode;
}
+
+static int git_futils_cp_fd(int ifd, int ofd, bool close_fd)
+{
+ int error = 0;
+ char buffer[4096];
+ ssize_t len = 0;
+
+ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)
+ /* p_write() does not have the same semantics as write(). It loops
+ * internally and will return 0 when it has completed writing.
+ */
+ error = p_write(ofd, buffer, len);
+
+ if (len < 0) {
+ giterr_set(GITERR_OS, "Read error while copying file");
+ error = (int)len;
+ }
+
+ if (close_fd) {
+ p_close(ifd);
+ p_close(ofd);
+ }
+
+ return error;
+}
+
+int git_futils_cp_withpath(
+ const char *from, const char *to, mode_t filemode, mode_t dirmode)
+{
+ int ifd, ofd;
+
+ if (git_futils_mkpath2file(to, dirmode) < 0)
+ return -1;
+
+ if ((ifd = git_futils_open_ro(from)) < 0)
+ return ifd;
+
+ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ ofd = GIT_ENOTFOUND;
+ giterr_set(GITERR_OS, "Failed to open '%s' for writing", to);
+ p_close(ifd);
+ return ofd;
+ }
+
+ return git_futils_cp_fd(ifd, ofd, true);
+}
+
+static int git_futils_cplink(
+ const char *from, size_t from_filesize, const char *to)
+{
+ int error = 0;
+ ssize_t read_len;
+ char *link_data = git__malloc(from_filesize + 1);
+ GITERR_CHECK_ALLOC(link_data);
+
+ read_len = p_readlink(from, link_data, from_filesize);
+ if (read_len != (ssize_t)from_filesize) {
+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from);
+ error = -1;
+ }
+ else {
+ link_data[read_len] = '\0';
+
+ if (p_symlink(link_data, to) < 0) {
+ giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",
+ link_data, to);
+ error = -1;
+ }
+ }
+
+ git__free(link_data);
+ return error;
+}
+
+typedef struct {
+ const char *to_root;
+ git_buf to;
+ ssize_t from_prefix;
+ uint32_t flags;
+ uint32_t mkdir_flags;
+ mode_t dirmode;
+} cp_r_info;
+
+static int _cp_r_callback(void *ref, git_buf *from)
+{
+ cp_r_info *info = ref;
+ struct stat from_st, to_st;
+ bool exists = false;
+
+ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&
+ from->ptr[git_path_basename_offset(from)] == '.')
+ return 0;
+
+ if (git_buf_joinpath(
+ &info->to, info->to_root, from->ptr + info->from_prefix) < 0)
+ return -1;
+
+ if (p_lstat(info->to.ptr, &to_st) < 0) {
+ if (errno != ENOENT) {
+ giterr_set(GITERR_OS,
+ "Could not access %s while copying files", info->to.ptr);
+ return -1;
+ }
+ } else
+ exists = true;
+
+ if (git_path_lstat(from->ptr, &from_st) < 0)
+ return -1;
+
+ if (S_ISDIR(from_st.st_mode)) {
+ int error = 0;
+ mode_t oldmode = info->dirmode;
+
+ /* if we are not chmod'ing, then overwrite dirmode */
+ if ((info->flags & GIT_CPDIR_CHMOD) == 0)
+ info->dirmode = from_st.st_mode;
+
+ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */
+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)
+ error = git_futils_mkdir(
+ info->to.ptr, NULL, info->dirmode, info->mkdir_flags);
+
+ /* recurse onto target directory */
+ if (!exists || S_ISDIR(to_st.st_mode))
+ error = git_path_direach(from, _cp_r_callback, info);
+
+ if (oldmode != 0)
+ info->dirmode = oldmode;
+
+ return error;
+ }
+
+ if (exists) {
+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)
+ return 0;
+
+ if (p_unlink(info->to.ptr) < 0) {
+ giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",
+ info->to.ptr);
+ return -1;
+ }
+ }
+
+ /* Done if this isn't a regular file or a symlink */
+ if (!S_ISREG(from_st.st_mode) &&
+ (!S_ISLNK(from_st.st_mode) ||
+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0))
+ return 0;
+
+ /* Make container directory on demand if needed */
+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&
+ git_futils_mkdir(
+ info->to.ptr, NULL, info->dirmode, info->mkdir_flags) < 0)
+ return -1;
+
+ /* make symlink or regular file */
+ if (S_ISLNK(from_st.st_mode))
+ return git_futils_cplink(from->ptr, from_st.st_size, info->to.ptr);
+ else
+ return git_futils_cp_withpath(
+ from->ptr, info->to.ptr, from_st.st_mode, info->dirmode);
+}
+
+int git_futils_cp_r(
+ const char *from,
+ const char *to,
+ uint32_t flags,
+ mode_t dirmode)
+{
+ int error;
+ git_buf path = GIT_BUF_INIT;
+ cp_r_info info;
+
+ if (git_buf_sets(&path, from) < 0)
+ return -1;
+
+ info.to_root = to;
+ info.flags = flags;
+ info.dirmode = dirmode;
+ info.from_prefix = path.size;
+ git_buf_init(&info.to, 0);
+
+ /* precalculate mkdir flags */
+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {
+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;
+ if ((flags & GIT_CPDIR_CHMOD) != 0)
+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;
+ } else {
+ info.mkdir_flags =
+ ((flags & GIT_CPDIR_CHMOD) != 0) ? GIT_MKDIR_CHMOD : 0;
+ }
+
+ error = _cp_r_callback(&info, &path);
+
+ git_buf_free(&path);
+
+ return error;
+}