diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/checkout.c | 2 | ||||
| -rw-r--r-- | src/config_file.c | 39 | ||||
| -rw-r--r-- | src/fileops.c | 251 | ||||
| -rw-r--r-- | src/fileops.h | 17 | ||||
| -rw-r--r-- | src/odb_loose.c | 4 | ||||
| -rw-r--r-- | src/path.c | 11 | ||||
| -rw-r--r-- | src/path.h | 8 | ||||
| -rw-r--r-- | src/refdb_fs.c | 3 | ||||
| -rw-r--r-- | src/repository.c | 12 | ||||
| -rw-r--r-- | src/win32/posix_w32.c | 11 |
10 files changed, 256 insertions, 102 deletions
diff --git a/src/checkout.c b/src/checkout.c index 8c06b3335..2a8bfd558 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1366,7 +1366,7 @@ static int checkout_mkdir( mkdir_opts.dir_map = data->mkdir_map; mkdir_opts.pool = &data->pool; - error = git_futils_mkdir_ext( + error = git_futils_mkdir_relative( path, base, mode, flags, &mkdir_opts); data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; diff --git a/src/config_file.c b/src/config_file.c index a3fec1b34..46f21c0f1 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1711,6 +1711,7 @@ static const char *quotes_for_value(const char *value) struct write_data { git_buf *buf; + git_buf buffered_comment; unsigned int in_section : 1, preg_replaced : 1; const char *section; @@ -1719,16 +1720,21 @@ struct write_data { const char *value; }; -static int write_line(struct write_data *write_data, const char *line, size_t line_len) +static int write_line_to(git_buf *buf, const char *line, size_t line_len) { - int result = git_buf_put(write_data->buf, line, line_len); + int result = git_buf_put(buf, line, line_len); if (!result && line_len && line[line_len-1] != '\n') - result = git_buf_printf(write_data->buf, "\n"); + result = git_buf_printf(buf, "\n"); return result; } +static int write_line(struct write_data *write_data, const char *line, size_t line_len) +{ + return write_line_to(write_data->buf, line, line_len); +} + static int write_value(struct write_data *write_data) { const char *q; @@ -1770,6 +1776,14 @@ static int write_on_section( write_data->in_section = strcmp(current_section, write_data->section) == 0; + /* + * If there were comments just before this section, dump them as well. + */ + if (!result) { + result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); + git_buf_clear(&write_data->buffered_comment); + } + if (!result) result = write_line(write_data, line, line_len); @@ -1787,10 +1801,19 @@ static int write_on_variable( { struct write_data *write_data = (struct write_data *)data; bool has_matched = false; + int error; GIT_UNUSED(reader); GIT_UNUSED(current_section); + /* + * If there were comments just before this variable, let's dump them as well. + */ + if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return error; + + git_buf_clear(&write_data->buffered_comment); + /* See if we are to update this name/value pair; first examine name */ if (write_data->in_section && strcasecmp(write_data->name, var_name) == 0) @@ -1825,7 +1848,7 @@ static int write_on_comment(struct reader **reader, const char *line, size_t lin GIT_UNUSED(reader); write_data = (struct write_data *)data; - return write_line(write_data, line, line_len); + return write_line_to(&write_data->buffered_comment, line, line_len); } static int write_on_eof(struct reader **reader, void *data) @@ -1835,6 +1858,12 @@ static int write_on_eof(struct reader **reader, void *data) GIT_UNUSED(reader); + /* + * If we've buffered comments when reaching EOF, make sure to dump them. + */ + if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return result; + /* If we are at the EOF and have not written our value (again, for a * simple name/value set, not a multivar) then we have never seen the * section in question and should create a new section and write the @@ -1892,6 +1921,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p section = git__strndup(key, ldot - key); write_data.buf = &buf; + git_buf_init(&write_data.buffered_comment, 0); write_data.section = section; write_data.in_section = 0; write_data.preg_replaced = 0; @@ -1901,6 +1931,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data); git__free(section); + git_buf_free(&write_data.buffered_comment); if (result < 0) { git_filebuf_cleanup(&file); diff --git a/src/fileops.c b/src/fileops.c index b7b55159f..57d2ce9c3 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -18,7 +18,7 @@ GIT__USE_STRMAP int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( - file_path, NULL, mode, + file_path, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } @@ -289,97 +289,230 @@ void git_futils_mmap_free(git_map *out) p_munmap(out); } -GIT_INLINE(int) validate_existing( - const char *make_path, +GIT_INLINE(int) mkdir_validate_dir( + const char *path, struct stat *st, mode_t mode, uint32_t flags, - struct git_futils_mkdir_perfdata *perfdata) + struct git_futils_mkdir_options *opts) { + /* 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", path); + return GIT_EEXISTS; + } + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(make_path) < 0) { + if (p_unlink(path) < 0) { giterr_set(GITERR_OS, "Failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", make_path); + S_ISLNK(st->st_mode) ? "symlink" : "file", path); return GIT_EEXISTS; } - perfdata->mkdir_calls++; + opts->perfdata.mkdir_calls++; - if (p_mkdir(make_path, mode) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_mkdir(path, mode) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (S_ISLNK(st->st_mode)) { /* Re-stat the target, make sure it's a directory */ - perfdata->stat_calls++; + opts->perfdata.stat_calls++; - if (p_stat(make_path, st) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_stat(path, st) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (!S_ISDIR(st->st_mode)) { giterr_set(GITERR_FILESYSTEM, - "Failed to make directory '%s': directory exists", make_path); + "Failed to make directory '%s': directory exists", path); return GIT_EEXISTS; } return 0; } -int git_futils_mkdir_ext( +GIT_INLINE(int) mkdir_validate_mode( const char *path, - const char *base, + struct stat *st, + bool terminal_path, 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; + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { - /* build path and find "root" where we should start calling mkdir */ - if (git_path_join_unrooted(&make_path, path, base, &root) < 0) - return -1; + opts->perfdata.chmod_calls++; - if (make_path.size == 0) { - giterr_set(GITERR_OS, "Attempt to create empty path"); - goto done; + if (p_chmod(path, mode) < 0) { + giterr_set(GITERR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} + +GIT_INLINE(int) mkdir_canonicalize( + git_buf *path, + uint32_t flags) +{ + ssize_t root_len; + + if (path->size == 0) { + giterr_set(GITERR_OS, "attempt to create empty path"); + return -1; } /* 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, root_len, 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; + + root_len = git_path_root(make_path.ptr); + + /* 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] == '.') || len == root_len+1) { + 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) { + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); + + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); + 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, '/'); @@ -437,32 +570,15 @@ retry_lstat: goto done; } } 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; + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) 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 ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; if (opts->dir_map && opts->pool) { char *cache_path; @@ -501,21 +617,6 @@ done: return error; } -int git_futils_mkdir( - const char *path, - const char *base, - mode_t mode, - uint32_t flags) -{ - struct git_futils_mkdir_options options = {0}; - return git_futils_mkdir_ext(path, base, mode, flags, &options); -} - -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); -} - typedef struct { const char *base; size_t baselen; @@ -777,7 +878,7 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create root directory the first time we need to create a directory */ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { error = git_futils_mkdir( - info->to_root, NULL, info->dirmode, + info->to_root, info->dirmode, (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; @@ -785,9 +886,9 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create directory with root as base to prevent excess chmods */ if (!error) - error = git_futils_mkdir( + error = git_futils_mkdir_relative( from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags); + info->dirmode, info->mkdir_flags, NULL); return error; } diff --git a/src/fileops.h b/src/fileops.h index 0f6466c59..572ff01a5 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -55,12 +55,9 @@ extern int git_futils_creat_locked(const char *path, const mode_t mode); extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); /** - * Create a path recursively - * - * If a base parameter is being passed, it's expected to be valued with a - * path pointing to an already existing directory. + * Create a path recursively. */ -extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); +extern int git_futils_mkdir_r(const char *path, const mode_t mode); /** * Flags to pass to `git_futils_mkdir`. @@ -111,20 +108,20 @@ struct git_futils_mkdir_options * and optionally chmods the directory immediately after (or each part of the * path if requested). * - * @param path The path to create. + * @param path The path to create, relative to base. * @param base Root for relative path. These directories will never be made. * @param mode The mode to use for created directories. * @param flags Combination of the mkdir flags above. - * @param opts Extended options, use `git_futils_mkdir` if you are not interested. + * @param opts Extended options, or null. * @return 0 on success, else error code */ -extern int git_futils_mkdir_ext(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); /** - * Create a directory or entire path. Similar to `git_futils_mkdir_withperf` + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` * without performance data. */ -extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags); +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); /** * Create all the folders required to contain diff --git a/src/odb_loose.c b/src/odb_loose.c index 99b8f7c91..730c4b1e1 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -84,9 +84,9 @@ static int object_file_name( static int object_mkdir(const git_buf *name, const loose_backend *be) { - return git_futils_mkdir( + return git_futils_mkdir_relative( name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); } static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) diff --git a/src/path.c b/src/path.c index cb11acee3..72cb289e0 100644 --- a/src/path.c +++ b/src/path.c @@ -526,6 +526,17 @@ bool git_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +bool git_path_islink(const char *path) +{ + struct stat st; + + assert(path); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + #ifdef GIT_WIN32 bool git_path_is_empty_dir(const char *path) diff --git a/src/path.h b/src/path.h index 971603ea7..7e156fce8 100644 --- a/src/path.h +++ b/src/path.h @@ -72,7 +72,7 @@ extern const char *git_path_topdir(const char *path); * This will return a number >= 0 which is the offset to the start of the * path, if the path is rooted (i.e. "/rooted/path" returns 0 and * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns < 0. + * returns -1. */ extern int git_path_root(const char *path); @@ -169,6 +169,12 @@ extern bool git_path_isdir(const char *path); extern bool git_path_isfile(const char *path); /** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_path_islink(const char *path); + +/** * Check if the given path is a directory, and is empty. */ extern bool git_path_is_empty_dir(const char *path); diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 1ddce4649..921f7862b 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -1413,7 +1413,8 @@ static int setup_namespace(git_buf *path, git_repository *repo) git__free(parts); /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) + if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository, + 0777, GIT_MKDIR_PATH, NULL) < 0) return -1; /* Return root of the namespaced path, i.e. without the trailing '/refs' */ diff --git a/src/repository.c b/src/repository.c index cbdfd5826..77145cfc8 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1457,8 +1457,8 @@ static int repo_init_structure( if (chmod) mkdir_flags |= GIT_MKDIR_CHMOD; - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, mkdir_flags); + error = git_futils_mkdir_relative( + tpl->path, repo_dir, dmode, mkdir_flags, NULL); } else if (!external_tpl) { const char *content = tpl->content; @@ -1480,7 +1480,7 @@ static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2) * don't try to set gid or grant world write access */ return git_futils_mkdir( - buf->ptr, NULL, mode & ~(S_ISGID | 0002), + buf->ptr, mode & ~(S_ISGID | 0002), GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); } @@ -1584,14 +1584,14 @@ static int repo_init_directories( /* create path #4 */ if (wd_path->size > 0 && (error = git_futils_mkdir( - wd_path->ptr, NULL, dirmode & ~S_ISGID, + wd_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR)) < 0) return error; /* create path #2 (if not the same as #4) */ if (!natural_wd && (error = git_futils_mkdir( - repo_path->ptr, NULL, dirmode & ~S_ISGID, + repo_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) return error; } @@ -1601,7 +1601,7 @@ static int repo_init_directories( has_dotgit) { /* create path #1 */ - error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, + error = git_futils_mkdir(repo_path->ptr, dirmode, GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index c909af6cc..414cb4701 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -148,12 +148,19 @@ static int lstat_w( return git_win32__file_attribute_to_stat(buf, &fdata, path); } - errno = ENOENT; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } /* To match POSIX behavior, set ENOTDIR when any of the folders in the * file path is a regular file, otherwise set ENOENT. */ - if (posix_enotdir) { + if (errno == ENOENT && posix_enotdir) { size_t path_len = wcslen(path); /* scan up path until we find an existing item */ |
