void test_status_worktree__unreadable(void) { #ifndef GIT_WIN32 const char *expected_paths[] = { "no_permission/foo" }; const unsigned int expected_statuses[] = {GIT_STATUS_WT_UNREADABLE}; git_repository *repo = cl_git_sandbox_init("empty_standard_repo"); git_status_options opts = GIT_STATUS_OPTIONS_INIT; status_entry_counts counts = {0}; /* Create directory with no read permission */ cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); counts.expected_entry_count = 1; counts.expected_paths = expected_paths; counts.expected_statuses = expected_statuses; opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; opts.flags = GIT_STATUS_OPT_DEFAULTS | GIT_STATUS_OPT_INCLUDE_UNREADABLE; cl_git_pass( git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) ); /* Restore permissions so we can cleanup :) */ p_chmod("empty_standard_repo/no_permission", 0777); cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); cl_assert_equal_i(0, counts.wrong_status_flags_count); cl_assert_equal_i(0, counts.wrong_sorted_path); #endif }
void test_repo_iterator__unreadable_dir(void) { git_iterator *i; const git_index_entry *e; if (!cl_is_chmod_supported()) return; g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); cl_git_mkfile("empty_standard_repo/r/a", "hello"); cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); cl_git_mkfile("empty_standard_repo/r/d", "final"); cl_git_pass(git_iterator_for_filesystem( &i, "empty_standard_repo/r", NULL)); cl_git_pass(git_iterator_advance(&e, i)); /* a */ cl_git_fail(git_iterator_advance(&e, i)); /* b */ cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); git_iterator_free(i); }
int git_futils_mktmp(git_buf *path_out, const char *filename, mode_t mode) { int fd; mode_t mask; p_umask(mask = p_umask(0)); git_buf_sets(path_out, filename); git_buf_puts(path_out, "_git2_XXXXXX"); if (git_buf_oom(path_out)) return -1; if ((fd = p_mkstemp(path_out->ptr)) < 0) { giterr_set(GITERR_OS, "Failed to create temporary file '%s'", path_out->ptr); return -1; } if (p_chmod(path_out->ptr, (mode & ~mask))) { giterr_set(GITERR_OS, "Failed to set permissions on file '%s'", path_out->ptr); return -1; } return fd; }
static int buffer_to_file( git_buf *buffer, const char *path, mode_t dir_mode, int file_open_flags, mode_t file_mode) { int fd, error, error_close; if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) return error; if ((fd = p_open(path, file_open_flags, file_mode)) < 0) return fd; error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); error_close = p_close(fd); if (!error) error = error_close; if (!error && (file_mode & 0100) != 0 && (error = p_chmod(path, file_mode)) < 0) giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); return error; }
bool cl_toggle_filemode(const char *filename) { struct stat st1, st2; cl_must_pass(p_stat(filename, &st1)); cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100)); cl_must_pass(p_stat(filename, &st2)); return (st1.st_mode != st2.st_mode); }
void test_iterator_workdir__skips_unreadable_dirs(void) { git_iterator *i; const git_index_entry *e; if (!cl_is_chmod_supported()) return; #ifndef GIT_WIN32 if (geteuid() == 0) cl_skip(); #endif g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); cl_git_mkfile("empty_standard_repo/r/a", "hello"); cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); cl_git_mkfile("empty_standard_repo/r/c/foo", "aloha"); cl_git_mkfile("empty_standard_repo/r/d", "final"); cl_git_pass(git_iterator_for_filesystem( &i, "empty_standard_repo/r", NULL)); cl_git_pass(git_iterator_advance(&e, i)); /* a */ cl_assert_equal_s("a", e->path); cl_git_pass(git_iterator_advance(&e, i)); /* c/foo */ cl_assert_equal_s("c/foo", e->path); cl_git_pass(git_iterator_advance(&e, i)); /* d */ cl_assert_equal_s("d", e->path); cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); git_iterator_free(i); }
static bool is_chmod_supported(const char *file_path) { struct stat st1, st2; if (p_stat(file_path, &st1) < 0) return false; if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) return false; if (p_stat(file_path, &st2) < 0) return false; return (st1.st_mode != st2.st_mode); }
void test_apply_both__index_mode_must_match_workdir(void) { git_diff *diff; if (!cl_is_chmod_supported()) clar__skip(); /* Set a file in the working directory executable. */ cl_must_pass(p_chmod("merge-recursive/asparagus.txt", 0755)); cl_git_pass(git_diff_from_buffer(&diff, DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES))); cl_git_fail_with(GIT_EAPPLYFAIL, git_apply(repo, diff, GIT_APPLY_LOCATION_BOTH, NULL)); git_diff_free(diff); }
GIT_INLINE(int) mkdir_validate_mode( const char *path, struct stat *st, bool terminal_path, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts) { if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { opts->perfdata.chmod_calls++; if (p_chmod(path, mode) < 0) { giterr_set(GITERR_OS, "failed to set permissions on '%s'", path); return -1; } } return 0; }
int git_filebuf_commit(git_filebuf *file, mode_t mode) { /* temporary files cannot be committed */ assert(file && file->path_original); file->flush_mode = Z_FINISH; flush_buffer(file); if (verify_last_error(file) < 0) goto on_error; file->fd_is_open = false; if (p_close(file->fd) < 0) { giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock); goto on_error; } file->fd = -1; if (p_chmod(file->path_lock, mode)) { giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); goto on_error; } p_unlink(file->path_original); if (p_rename(file->path_lock, file->path_original) < 0) { giterr_set(GITERR_OS, "Failed to rename lockfile to '%s'", file->path_original); goto on_error; } git_filebuf_cleanup(file); return 0; on_error: git_filebuf_cleanup(file); return -1; }
int git_futils_mkdir( const char *path, const char *base, mode_t mode, uint32_t flags) { int error = -1; git_buf make_path = GIT_BUF_INIT; ssize_t root = 0, min_root_len; 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) return -1; if (make_path.size == 0) { giterr_set(GITERR_OS, "Attempt to create empty path"); goto done; } /* remove trailing slashes on path */ while (make_path.ptr[make_path.size - 1] == '/') { make_path.size--; make_path.ptr[make_path.size] = '\0'; } /* if we are not supposed to made the last element, truncate it */ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { git_buf_rtruncate_at_char(&make_path, '/'); flags |= GIT_MKDIR_SKIP_LAST; } 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, '/'); /* 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 (root >= 0 && 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; /* walk down tail of path making each directory */ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { /* advance tail to include next path component */ while (*tail == '/') tail++; while (*tail && *tail != '/') tail++; /* truncate path at next component */ lastch = *tail; *tail = '\0'; st.st_mode = 0; /* make directory */ if (p_mkdir(make_path.ptr, mode) < 0) { int tmp_errno = giterr_system_last(); /* 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; } /* 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 done; } } /* 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 && lastch == '\0') { giterr_set(GITERR_OS, "Failed to set permissions on '%s'", make_path.ptr); goto done; } } 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; } done: git_buf_free(&make_path); return error; }
int git_futils_mkdir_ext( const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts) { 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; /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&make_path, path, base, &root) < 0) 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) 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'; /* 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); flags |= GIT_MKDIR_SKIP_LAST; } if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { git_path_dirname_r(&make_path, make_path.ptr); } /* We were either given the root path (or trimmed it to * the root), we don't have anything to do. */ if (make_path.size <= (size_t)root_len) { 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, '/'); /* 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 (root >= 0 && 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; /* walk down tail of path making each directory */ for (tail = &make_path.ptr[root]; *tail; *tail = lastch) { /* advance tail to include next path component */ while (*tail == '/') tail++; while (*tail && *tail != '/') tail++; /* truncate path at next component */ lastch = *tail; *tail = '\0'; st.st_mode = 0; if (opts->dir_map && git_strmap_exists(opts->dir_map, make_path.ptr)) continue; /* See what's going on with this path component */ opts->perfdata.stat_calls++; if (p_lstat(make_path.ptr, &st) < 0) { opts->perfdata.mkdir_calls++; if (errno != ENOENT || p_mkdir(make_path.ptr, mode) < 0) { giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path.ptr); error = GIT_EEXISTS; goto done; } giterr_clear(); } else { /* with exclusive create, existing dir is an error */ 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; } if ((error = validate_existing( make_path.ptr, &st, mode, flags, &opts->perfdata)) < 0) goto done; } /* chmod if requested and necessary */ if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && st.st_mode != mode) { opts->perfdata.chmod_calls++; if ((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; } } if (opts->dir_map && opts->pool) { char *cache_path; size_t alloc_size; GITERR_CHECK_ALLOC_ADD(&alloc_size, make_path.size, 1); if (!git__is_uint32(alloc_size)) return -1; cache_path = git_pool_malloc(opts->pool, (uint32_t)alloc_size); GITERR_CHECK_ALLOC(cache_path); memcpy(cache_path, make_path.ptr, make_path.size + 1); git_strmap_insert(opts->dir_map, cache_path, cache_path, error); if (error < 0) goto done; } } error = 0; /* check that full path really is a directory if requested & needed */ if ((flags & GIT_MKDIR_VERIFY_DIR) != 0 && lastch != '\0') { opts->perfdata.stat_calls++; if (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; } } done: git_buf_free(&make_path); return error; }
int git_futils_mkdir( const char *path, const char *base, mode_t mode, uint32_t flags) { git_buf make_path = GIT_BUF_INIT; ssize_t root = 0; char lastch, *tail; /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&make_path, path, base, &root) < 0) return -1; if (make_path.size == 0) { giterr_set(GITERR_OS, "Attempt to create empty path"); goto fail; } /* remove trailing slashes on path */ while (make_path.ptr[make_path.size - 1] == '/') { make_path.size--; make_path.ptr[make_path.size] = '\0'; } /* 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; if (root < 0) root = 0; 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; } /* 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; } } *tail = lastch; } git_buf_free(&make_path); return 0; fail: git_buf_free(&make_path); return -1; }
void test_index_addall__repo_lifecycle(void) { int error; git_index *index; git_strarray paths = { NULL, 0 }; char *strs[1]; addall_create_test_repo(true); cl_git_pass(git_repository_index(&index, g_repo)); strs[0] = "file.*"; paths.strings = strs; paths.count = 1; cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0); cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file"); check_stat_data(index, TEST_DIR "/file.bar", false); check_status(g_repo, 1, 0, 0, 1, 0, 1, 1, 0); cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.bar", true); check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0); cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.zzz", true); check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit"); check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); if (cl_repo_get_bool(g_repo, "core.filemode")) { cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0777)); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0); /* go back to what we had before */ cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0666)); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); } /* attempt to add an ignored file - does nothing */ strs[0] = "file.foo"; cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); /* add with check - should generate error */ error = git_index_add_all( index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL); cl_assert_equal_i(GIT_EINVALIDSPEC, error); check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); /* add with force - should allow */ cl_git_pass(git_index_add_all( index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.foo", true); check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); /* now it's in the index, so regular add should work */ cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file"); check_stat_data(index, TEST_DIR "/file.foo", false); check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0); cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_stat_data(index, TEST_DIR "/file.foo", true); check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0); cl_git_pass(git_index_add_bypath(index, "more.zzz")); check_stat_data(index, TEST_DIR "/more.zzz", true); check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0); cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file"); check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0); cl_git_pass(git_index_add_bypath(index, "file.zzz")); check_stat_data(index, TEST_DIR "/file.zzz", true); check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0); strs[0] = "*.zzz"; cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL)); check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0); cl_git_pass(git_index_add_bypath(index, "file.zzz")); check_status(g_repo, 1, 0, 1, 3, 0, 0, 0, 0); cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit"); check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0); cl_must_pass(p_unlink(TEST_DIR "/file.zzz")); check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0); /* update_all should be able to remove entries */ cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_status(g_repo, 0, 1, 0, 3, 0, 0, 0, 0); strs[0] = "*"; cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL)); check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0); /* must be able to remove at any position while still updating other files */ cl_must_pass(p_unlink(TEST_DIR "/.gitignore")); cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file"); cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality"); check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0); cl_git_pass(git_index_update_all(index, NULL, NULL, NULL)); check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0); /* this behavior actually matches 'git add -u' where "file.zzz" has * been removed from the index, so when you go to update, even though * it exists in the HEAD, it is not re-added to the index, leaving it * as a DELETE when comparing HEAD to index and as an ADD comparing * index to worktree */ git_index_free(index); }