diff options
author | Edward Thomson <ethomson@microsoft.com> | 2015-09-17 09:42:05 -0400 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2015-09-17 10:11:56 -0400 |
commit | e24c60dba4cd7c6b768fd6c39d4a0c003a48fb1f (patch) | |
tree | de04ed1901a455f49ef00d373f1915cf300425db | |
parent | 0862ec2eb9529573ab46f3975defc0b7632bede4 (diff) | |
download | libgit2-e24c60dba4cd7c6b768fd6c39d4a0c003a48fb1f.tar.gz |
mkdir: find component paths for mkdir_relative
`git_futils_mkdir` does not blindly call `git_futils_mkdir_relative`.
`git_futils_mkdir_relative` is used when you have some base directory
and want to create some path inside of it, potentially removing blocking
symlinks and files in the process. This is not suitable for a general
recursive mkdir within the filesystem.
Instead, when `mkdir` is being recursive, locate the first existent
parent directory and use that as the base for `mkdir_relative`.
-rw-r--r-- | src/fileops.c | 178 | ||||
-rw-r--r-- | tests/core/mkdir.c | 15 |
2 files changed, 142 insertions, 51 deletions
diff --git a/src/fileops.c b/src/fileops.c index b986a6546..f00c3e821 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -331,59 +331,163 @@ GIT_INLINE(int) validate_existing( return 0; } -int git_futils_mkdir_relative( - const char *relative_path, - const char *base, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) + +GIT_INLINE(int) mkdir_canonicalize( + git_buf *path, + uint32_t flags) { - int error = -1; - git_buf make_path = GIT_BUF_INIT; - ssize_t root = 0, min_root_len, root_len; - char lastch = '/', *tail; - struct stat st; - struct git_futils_mkdir_options empty_opts = {0}; - - if (!opts) - opts = &empty_opts; + ssize_t root_len; - /* build path and find "root" where we should start calling mkdir */ - if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + if (path->size == 0) { + giterr_set(GITERR_OS, "attempt to create empty path"); return -1; - - if (make_path.size == 0) { - giterr_set(GITERR_OS, "Attempt to create empty path"); - goto done; } /* Trim trailing slashes (except the root) */ - if ((root_len = git_path_root(make_path.ptr)) < 0) + if ((root_len = git_path_root(path->ptr)) < 0) root_len = 0; else root_len++; - while (make_path.size > (size_t)root_len && - make_path.ptr[make_path.size - 1] == '/') - make_path.ptr[--make_path.size] = '\0'; + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; /* if we are not supposed to made the last element, truncate it */ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); + git_path_dirname_r(path, path->ptr); flags |= GIT_MKDIR_SKIP_LAST; } if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); + git_path_dirname_r(path, path->ptr); } /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_buf_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, error; + + if ((error = git_buf_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_buf_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. */ - if (make_path.size <= (size_t)root_len) { - error = 0; + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + giterr_set(GITERR_OS, "failed to stat '%s'", parent_path.ptr); + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + assert(len); + + /* we've walked all the given path's parents and it's either relative + * or rooted. either way, give up and make the entire path. + */ + if (len == 1 && + (parent_path.ptr[0] == '.' || parent_path.ptr[0] == '/')) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + if ((error = validate_existing(make_path.ptr, &st, mode, flags, &opts.perfdata)) < 0) + goto done; + + if ((flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_FILESYSTEM, "failed to make directory '%s': " + "directory exists", make_path.ptr); + error = GIT_EEXISTS; + goto done; + } + goto done; } + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_buf_free(&make_path); + git_buf_free(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, + const char *base, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + git_buf make_path = GIT_BUF_INIT; + ssize_t root = 0, min_root_len; + char lastch = '/', *tail; + struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; + + /* build path and find "root" where we should start calling mkdir */ + if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + return -1; + + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) + goto done; + /* if we are not supposed to make the whole path, reset root */ if ((flags & GIT_MKDIR_PATH) == 0) root = git_buf_rfind(&make_path, '/'); @@ -505,20 +609,6 @@ done: return error; } -int git_futils_mkdir( - const char *path, - mode_t mode, - uint32_t flags) -{ - struct git_futils_mkdir_options options = {0}; - return git_futils_mkdir_relative(path, NULL, mode, flags, &options); -} - -int git_futils_mkdir_r(const char *path, const mode_t mode) -{ - return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); -} - typedef struct { const char *base; size_t baselen; diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index 8d487e594..5e6a06002 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -27,25 +27,27 @@ void test_core_mkdir__absolute(void) cl_assert(git_path_isdir(path.ptr)); git_buf_joinpath(&path, path.ptr, "subdir"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); cl_assert(git_path_isdir(path.ptr)); + /* ensure mkdir_r works for a single subdir */ git_buf_joinpath(&path, path.ptr, "another"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); cl_assert(git_path_isdir(path.ptr)); + /* ensure mkdir_r works */ git_buf_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); cl_assert(git_path_isdir(path.ptr)); + + /* ensure we don't imply recursive */ + git_buf_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(!git_path_isdir(path.ptr)); } void test_core_mkdir__basic(void) @@ -285,4 +287,3 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) cl_must_pass(p_chmod("r/mode", 0777)); } - |