Merge pull request #1635 from arrbee/simplify-mkdir Simplify git_futils_mkdir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
diff --git a/src/fileops.c b/src/fileops.c
index 088ae5e..02f48e1 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -277,10 +277,11 @@ int git_futils_mkdir(
mode_t mode,
uint32_t flags)
{
- int error = -1, tmp_errno;
+ int error = -1;
git_buf make_path = GIT_BUF_INIT;
ssize_t root = 0, min_root_len;
- char lastch, *tail;
+ char lastch = '/', *tail;
+ struct stat st;
/* build path and find "root" where we should start calling mkdir */
if (git_path_join_unrooted(&make_path, path, base, &root) < 0)
@@ -288,7 +289,7 @@ int git_futils_mkdir(
if (make_path.size == 0) {
giterr_set(GITERR_OS, "Attempt to create empty path");
- goto fail;
+ goto done;
}
/* remove trailing slashes on path */
@@ -305,24 +306,32 @@ int git_futils_mkdir(
if ((flags & GIT_MKDIR_SKIP_LAST) != 0)
git_buf_rtruncate_at_char(&make_path, '/');
+ /* if nothing left after truncation, then we're done! */
+ if (!make_path.size) {
+ error = 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, '/');
- /* clip root to make_path length */
- if (root >= (ssize_t)make_path.size)
- root = (ssize_t)make_path.size - 1;
- if (root < 0)
- root = 0;
-
- /* make sure mkdir root is at least after filesystem root */
+ /* advance root past drive name or network mount prefix */
min_root_len = git_path_root(make_path.ptr);
if (root < min_root_len)
root = min_root_len;
+ while (make_path.ptr[root] == '/')
+ ++root;
+
+ /* clip root to make_path length */
+ if (root > (ssize_t)make_path.size)
+ root = (ssize_t)make_path.size; /* i.e. NUL byte of string */
+ if (root < 0)
+ root = 0;
- tail = & make_path.ptr[root];
+ /* walk down tail of path making each directory */
+ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) {
- while (*tail) {
/* advance tail to include next path component */
while (*tail == '/')
tail++;
@@ -332,72 +341,48 @@ int git_futils_mkdir(
/* truncate path at next component */
lastch = *tail;
*tail = '\0';
+ st.st_mode = 0;
/* make directory */
if (p_mkdir(make_path.ptr, mode) < 0) {
- int already_exists = 0;
-
- switch (errno) {
- case EEXIST:
- if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
- !git_path_isdir(make_path.ptr)) {
- giterr_set(
- GITERR_OS, "Existing path is not a directory '%s'",
- make_path.ptr);
- error = GIT_ENOTFOUND;
- goto fail;
- }
-
- already_exists = 1;
- break;
- case ENOSYS:
- case EACCES:
- /* Possible recoverable errors. These errors could occur
- * on some OS if we try to mkdir at a network mount point
- * or at the root of a volume. If the path is a dir, just
- * treat as EEXIST.
- */
- tmp_errno = errno;
-
- if (git_path_isdir(make_path.ptr)) {
- already_exists = 1;
- break;
- }
-
- /* Fall through */
+ int tmp_errno = errno;
+
+ /* ignore error if directory already exists */
+ if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
errno = tmp_errno;
- default:
- giterr_set(GITERR_OS, "Failed to make directory '%s'",
- make_path.ptr);
- goto fail;
+ giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
+ goto done;
}
- if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) {
- giterr_set(GITERR_OS, "Directory already exists '%s'",
- make_path.ptr);
+ /* with exclusive create, existing dir is an error */
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'", make_path.ptr);
error = GIT_EEXISTS;
- goto fail;
+ goto done;
}
}
- /* 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;
- }
+ /* chmod if requested and necessary */
+ if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||
+ (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) &&
+ st.st_mode != mode &&
+ (error = p_chmod(make_path.ptr, mode)) < 0) {
+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
+ goto done;
}
-
- *tail = lastch;
}
- git_buf_free(&make_path);
- return 0;
+ error = 0;
+
+ /* check that full path really is a directory if requested & needed */
+ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 &&
+ lastch != '\0' &&
+ (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
+ giterr_set(GITERR_OS, "Path is not a directory '%s'", make_path.ptr);
+ error = GIT_ENOTFOUND;
+ }
-fail:
+done:
git_buf_free(&make_path);
return error;
}