Commit 668ae2ddf854e509dca6e76772b64c9876c1d717

Russell Belfer 2014-08-22T10:05:09

Allow mkdir helper to skip parent errors Our mkdir helper was failing is a parent directory was not accessible even if the child directory could be created. This changes the helper to keep trying child directories even when the parent is unwritable.

diff --git a/src/fileops.c b/src/fileops.c
index bebbae4..34659ad 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -355,8 +355,9 @@ int git_futils_mkdir(
 		if (p_mkdir(make_path.ptr, mode) < 0) {
 			int tmp_errno = giterr_system_last();
 
-			/* ignore error if directory already exists */
-			if (p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode)) {
+			/* ignore error if not at end or if directory already exists */
+			if (lastch == '\0' &&
+				(p_stat(make_path.ptr, &st) < 0 || !S_ISDIR(st.st_mode))) {
 				giterr_system_set(tmp_errno);
 				giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr);
 				goto done;
@@ -374,7 +375,8 @@ int git_futils_mkdir(
 		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) {
+			(error = p_chmod(make_path.ptr, mode)) < 0 &&
+			lastch == '\0') {
 			giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr);
 			goto done;
 		}
diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c
index a8c5b10..a0e8491 100644
--- a/tests/core/mkdir.c
+++ b/tests/core/mkdir.c
@@ -186,3 +186,34 @@ void test_core_mkdir__chmods(void)
 	cl_git_pass(git_path_lstat("r/mode2/is2/important2.1", &st));
 	check_mode(0777, st.st_mode);
 }
+
+void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void)
+{
+	struct stat st;
+	mode_t *old;
+
+	/* FAT filesystems don't support exec bit, nor group/world bits */
+	if (!cl_is_chmod_supported())
+		return;
+
+	cl_assert((old = git__malloc(sizeof(mode_t))) != NULL);
+	*old = p_umask(022);
+	cl_set_cleanup(cleanup_chmod_root, old);
+
+	cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0));
+	cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH));
+	cl_git_pass(git_path_lstat("r/mode", &st));
+	check_mode(0755, st.st_mode);
+
+	cl_must_pass(p_chmod("r/mode", 0111));
+	cl_git_pass(git_path_lstat("r/mode", &st));
+	check_mode(0111, st.st_mode);
+
+	cl_git_pass(
+		git_futils_mkdir("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH));
+	cl_git_pass(git_path_lstat("r/mode/is/okay/inside", &st));
+	check_mode(0755, st.st_mode);
+
+	cl_must_pass(p_chmod("r/mode", 0777));
+}
+