From a1be47e4ca65718ec239e4b86a44e45220237aee Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:20:42 -0400 Subject: [PATCH 1/6] hash-object: fix buffer reuse with --path in a subdirectory The hash-object command uses prefix_filename() without duplicating its return value. Since that function returns a static buffer, the value is overwritten by subsequent calls. This can cause incorrect results when we use --path along with hashing a file by its relative path, both of which need to call prefix_filename(). We overwrite the filename computed for --path, effectively ignoring it. We can fix this by calling xstrdup on the return value. Note that we don't bother freeing the "vpath" instance, as it remains valid until the program exit. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/hash-object.c | 7 +++++-- t/t1007-hash-object.sh | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 9028e1fdcc..56df77b0c2 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -115,7 +115,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) prefix_length = prefix ? strlen(prefix) : 0; if (vpath && prefix) - vpath = prefix_filename(prefix, prefix_length, vpath); + vpath = xstrdup(prefix_filename(prefix, prefix_length, vpath)); git_config(git_default_config, NULL); @@ -144,11 +144,14 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) for (i = 0 ; i < argc; i++) { const char *arg = argv[i]; + char *to_free = NULL; if (0 <= prefix_length) - arg = prefix_filename(prefix, prefix_length, arg); + arg = to_free = + xstrdup(prefix_filename(prefix, prefix_length, arg)); hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg, flags, literally); + free(to_free); } if (stdin_paths) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index c5245c5cb4..532682f51c 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -134,6 +134,16 @@ test_expect_success 'gitattributes also work in a subdirectory' ' ) ' +test_expect_success '--path works in a subdirectory' ' + ( + cd subdir && + path1_sha=$(git hash-object --path=../file1 ../file0) && + path0_sha=$(git hash-object --path=../file0 ../file1) && + test "$file0_sha" = "$path0_sha" && + test "$file1_sha" = "$path1_sha" + ) +' + test_expect_success 'check that --no-filters option works' ' nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && From 598019769cbaa38495b0c04297efa13d0f4a572e Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:21:27 -0400 Subject: [PATCH 2/6] prefix_filename: move docstring to header file This is a public function, so we should make its documentation available near the declaration. While we're at it, we can give a few details about how it works. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- abspath.c | 5 ----- cache.h | 12 ++++++++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/abspath.c b/abspath.c index b02e068aa3..fd30aff08f 100644 --- a/abspath.c +++ b/abspath.c @@ -246,11 +246,6 @@ char *absolute_pathdup(const char *path) return strbuf_detach(&sb, NULL); } -/* - * Unlike prefix_path, this should be used if the named file does - * not have to interact with index entry; i.e. name of a random file - * on the filesystem. - */ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) { static struct strbuf path = STRBUF_INIT; diff --git a/cache.h b/cache.h index 9b2157f591..a01668fc42 100644 --- a/cache.h +++ b/cache.h @@ -529,7 +529,19 @@ extern const char *setup_git_directory_gently(int *); extern const char *setup_git_directory(void); extern char *prefix_path(const char *prefix, int len, const char *path); extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path); + +/* + * Concatenate "prefix" (if len is non-zero) and "path", with no + * connecting characters (so "prefix" should end with a "/"). + * Unlike prefix_path, this should be used if the named file does + * not have to interact with index entry; i.e. name of a random file + * on the filesystem. + * + * The return value may point to static storage which will be overwritten by + * further calls. + */ extern const char *prefix_filename(const char *prefix, int len, const char *path); + extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, const char *name, From 116fb64e439d3744d0f244a51d7a6d714b7703ae Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:22:28 -0400 Subject: [PATCH 3/6] prefix_filename: drop length parameter This function takes the prefix as a ptr/len pair, but in every caller the length is exactly strlen(ptr). Let's simplify the interface and just take the string. This saves callers specifying it (and in some cases handling a NULL prefix). In a handful of cases we had the length already without calling strlen, so this is technically slower. But it's not likely to matter (after all, if the prefix is non-empty we'll allocate and copy it into a buffer anyway). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- abspath.c | 4 +++- apply.c | 6 ++---- builtin/config.c | 1 - builtin/hash-object.c | 9 +++------ builtin/log.c | 3 +-- builtin/mailinfo.c | 2 +- builtin/merge-file.c | 8 ++------ builtin/rev-parse.c | 4 +--- builtin/worktree.c | 2 +- cache.h | 2 +- diff-no-index.c | 7 +++---- diff.c | 4 ++-- parse-options.c | 2 +- setup.c | 2 +- worktree.c | 2 +- 15 files changed, 23 insertions(+), 35 deletions(-) diff --git a/abspath.c b/abspath.c index fd30aff08f..c6f480993d 100644 --- a/abspath.c +++ b/abspath.c @@ -246,9 +246,11 @@ char *absolute_pathdup(const char *path) return strbuf_detach(&sb, NULL); } -const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) +const char *prefix_filename(const char *pfx, const char *arg) { static struct strbuf path = STRBUF_INIT; + size_t pfx_len = pfx ? strlen(pfx) : 0; + #ifndef GIT_WINDOWS_NATIVE if (!pfx_len || is_absolute_path(arg)) return arg; diff --git a/apply.c b/apply.c index 0e2caeab9c..b8bd5a4bef 100644 --- a/apply.c +++ b/apply.c @@ -2046,7 +2046,7 @@ static void prefix_one(struct apply_state *state, char **name) char *old_name = *name; if (!old_name) return; - *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name)); + *name = xstrdup(prefix_filename(state->prefix, *name)); free(old_name); } @@ -4815,9 +4815,7 @@ int apply_all_patches(struct apply_state *state, read_stdin = 0; continue; } else if (0 < state->prefix_length) - arg = prefix_filename(state->prefix, - state->prefix_length, - arg); + arg = prefix_filename(state->prefix, arg); fd = open(arg, O_RDONLY); if (fd < 0) { diff --git a/builtin/config.c b/builtin/config.c index 05843a0f96..74f6c34d11 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -528,7 +528,6 @@ int cmd_config(int argc, const char **argv, const char *prefix) if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = xstrdup(prefix_filename(prefix, - strlen(prefix), given_config_source.file)); } diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 56df77b0c2..2ea36909d2 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -102,7 +102,6 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) OPT_END() }; int i; - int prefix_length = -1; const char *errstr = NULL; argc = parse_options(argc, argv, NULL, hash_object_options, @@ -113,9 +112,8 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) else prefix = setup_git_directory_gently(&nongit); - prefix_length = prefix ? strlen(prefix) : 0; if (vpath && prefix) - vpath = xstrdup(prefix_filename(prefix, prefix_length, vpath)); + vpath = xstrdup(prefix_filename(prefix, vpath)); git_config(git_default_config, NULL); @@ -146,9 +144,8 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; char *to_free = NULL; - if (0 <= prefix_length) - arg = to_free = - xstrdup(prefix_filename(prefix, prefix_length, arg)); + if (prefix) + arg = to_free = xstrdup(prefix_filename(prefix, arg)); hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg, flags, literally); free(to_free); diff --git a/builtin/log.c b/builtin/log.c index 281af8c1ec..bfdc7a23d3 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1084,8 +1084,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) if (!output_directory) return prefix; - return xstrdup(prefix_filename(prefix, outdir_offset, - output_directory)); + return xstrdup(prefix_filename(prefix, output_directory)); } static const char * const builtin_format_patch_usage[] = { diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index e3b62f2fc7..681f07f54d 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -15,7 +15,7 @@ static char *prefix_copy(const char *prefix, const char *filename) { if (!prefix || is_absolute_path(filename)) return xstrdup(filename); - return xstrdup(prefix_filename(prefix, strlen(prefix), filename)); + return xstrdup(prefix_filename(prefix, filename)); } int cmd_mailinfo(int argc, const char **argv, const char *prefix) diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 13e22a2f0b..63cd943587 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -28,7 +28,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmparam_t xmp = {{0}}; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; - int prefixlen = 0; struct option options[] = { OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")), OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3), @@ -65,11 +64,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) return error_errno("failed to redirect stderr to /dev/null"); } - if (prefix) - prefixlen = strlen(prefix); - for (i = 0; i < 3; i++) { - const char *fname = prefix_filename(prefix, prefixlen, argv[i]); + const char *fname = prefix_filename(prefix, argv[i]); if (!names[i]) names[i] = argv[i]; if (read_mmfile(mmfs + i, fname)) @@ -90,7 +86,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret >= 0) { const char *filename = argv[0]; - const char *fpath = prefix_filename(prefix, prefixlen, argv[0]); + const char *fpath = prefix_filename(prefix, argv[0]); FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 2549643267..c8035331e2 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -228,9 +228,7 @@ static int show_file(const char *arg, int output_prefix) if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { if (output_prefix) { const char *prefix = startup_info->prefix; - show(prefix_filename(prefix, - prefix ? strlen(prefix) : 0, - arg)); + show(prefix_filename(prefix, arg)); } else show(arg); return 1; diff --git a/builtin/worktree.c b/builtin/worktree.c index 831fe058a5..e38325e44b 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -338,7 +338,7 @@ static int add(int ac, const char **av, const char *prefix) if (ac < 1 || ac > 2) usage_with_options(worktree_usage, options); - path = prefix_filename(prefix, strlen(prefix), av[0]); + path = prefix_filename(prefix, av[0]); branch = ac < 2 ? "HEAD" : av[1]; if (!strcmp(branch, "-")) diff --git a/cache.h b/cache.h index a01668fc42..0b53aef0ed 100644 --- a/cache.h +++ b/cache.h @@ -540,7 +540,7 @@ extern char *prefix_path_gently(const char *prefix, int len, int *remaining, con * The return value may point to static storage which will be overwritten by * further calls. */ -extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern const char *prefix_filename(const char *prefix, const char *path); extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, diff --git a/diff-no-index.c b/diff-no-index.c index df762fd0f7..5f7317ced9 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -236,7 +236,7 @@ static void fixup_paths(const char **path, struct strbuf *replacement) void diff_no_index(struct rev_info *revs, int argc, const char **argv) { - int i, prefixlen; + int i; const char *paths[2]; struct strbuf replacement = STRBUF_INIT; const char *prefix = revs->prefix; @@ -257,7 +257,6 @@ void diff_no_index(struct rev_info *revs, } } - prefixlen = prefix ? strlen(prefix) : 0; for (i = 0; i < 2; i++) { const char *p = argv[argc - 2 + i]; if (!strcmp(p, "-")) @@ -266,8 +265,8 @@ void diff_no_index(struct rev_info *revs, * path that is "-", spell it as "./-". */ p = file_from_standard_input; - else if (prefixlen) - p = xstrdup(prefix_filename(prefix, prefixlen, p)); + else if (prefix) + p = xstrdup(prefix_filename(prefix, p)); paths[i] = p; } diff --git a/diff.c b/diff.c index a628ac3a95..70870b4b69 100644 --- a/diff.c +++ b/diff.c @@ -4023,7 +4023,7 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--pickaxe-regex")) options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { - const char *path = prefix_filename(prefix, strlen(prefix), optarg); + const char *path = prefix_filename(prefix, optarg); options->orderfile = xstrdup(path); return argcount; } @@ -4071,7 +4071,7 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--no-function-context")) DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { - const char *path = prefix_filename(prefix, strlen(prefix), optarg); + const char *path = prefix_filename(prefix, optarg); options->file = fopen(path, "w"); if (!options->file) die_errno("Could not open '%s'", path); diff --git a/parse-options.c b/parse-options.c index 4fbe924a5d..ba6cc30b26 100644 --- a/parse-options.c +++ b/parse-options.c @@ -40,7 +40,7 @@ static void fix_filename(const char *prefix, const char **file) if (!file || !*file || !prefix || is_absolute_path(*file) || !strcmp("-", *file)) return; - *file = xstrdup(prefix_filename(prefix, strlen(prefix), *file)); + *file = xstrdup(prefix_filename(prefix, *file)); } static int opt_command_mode_error(const struct option *opt, diff --git a/setup.c b/setup.c index 64f922a937..a76379e0ce 100644 --- a/setup.c +++ b/setup.c @@ -142,7 +142,7 @@ int check_filename(const char *prefix, const char *arg) return 1; name = arg + 2; } else if (prefix) - name = prefix_filename(prefix, strlen(prefix), arg); + name = prefix_filename(prefix, arg); else name = arg; if (!lstat(name, &st)) diff --git a/worktree.c b/worktree.c index fa7bc67a50..42dd3d52b0 100644 --- a/worktree.c +++ b/worktree.c @@ -254,7 +254,7 @@ struct worktree *find_worktree(struct worktree **list, if ((wt = find_worktree_by_suffix(list, arg))) return wt; - arg = prefix_filename(prefix, strlen(prefix), arg); + arg = prefix_filename(prefix, arg); path = real_pathdup(arg, 1); for (; *list; list++) if (!fspathcmp(path, real_path((*list)->path))) From e4da43b1f063d227b5f7d2922d27458748763a2d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:28:49 -0400 Subject: [PATCH 4/6] prefix_filename: return newly allocated string The prefix_filename() function returns a pointer to static storage, which makes it easy to use dangerously. We already fixed one buggy caller in hash-object recently, and the calls in apply.c are suspicious (I didn't dig in enough to confirm that there is a bug, but we call the function once in apply_all_patches() and then again indirectly from parse_chunk()). Let's make it harder to get wrong by allocating the return value. For simplicity, we'll do this even when the prefix is empty (and we could just return the original file pointer). That will cause us to allocate sometimes when we wouldn't otherwise need to, but this function isn't called in performance critical code-paths (and it already _might_ allocate on any given call, so a caller that cares about performance is questionable anyway). The downside is that the callers need to remember to free() the result to avoid leaking. Most of them already used xstrdup() on the result, so we know they are OK. The remainder have been converted to use free() as appropriate. I considered retaining a prefix_filename_unsafe() for cases where we know the static lifetime is OK (and handling the cleanup is awkward). This is only a handful of cases, though, and it's not worth the mental energy in worrying about whether the "unsafe" variant is OK to use in any situation. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- abspath.c | 10 ++++------ apply.c | 9 ++++++--- builtin/config.c | 3 +-- builtin/hash-object.c | 2 +- builtin/log.c | 2 +- builtin/mailinfo.c | 11 ++--------- builtin/merge-file.c | 14 +++++++++++--- builtin/rev-parse.c | 4 +++- builtin/worktree.c | 3 ++- cache.h | 6 +++--- diff-no-index.c | 2 +- diff.c | 6 +++--- parse-options.c | 2 +- setup.c | 11 ++++++++--- worktree.c | 5 ++++- 15 files changed, 51 insertions(+), 39 deletions(-) diff --git a/abspath.c b/abspath.c index c6f480993d..4addd1fde0 100644 --- a/abspath.c +++ b/abspath.c @@ -246,20 +246,18 @@ char *absolute_pathdup(const char *path) return strbuf_detach(&sb, NULL); } -const char *prefix_filename(const char *pfx, const char *arg) +char *prefix_filename(const char *pfx, const char *arg) { - static struct strbuf path = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; size_t pfx_len = pfx ? strlen(pfx) : 0; #ifndef GIT_WINDOWS_NATIVE if (!pfx_len || is_absolute_path(arg)) - return arg; - strbuf_reset(&path); + return xstrdup(arg); strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); #else /* don't add prefix to absolute paths, but still replace '\' by '/' */ - strbuf_reset(&path); if (is_absolute_path(arg)) pfx_len = 0; else if (pfx_len) @@ -267,5 +265,5 @@ const char *prefix_filename(const char *pfx, const char *arg) strbuf_addstr(&path, arg); convert_slashes(path.buf + pfx_len); #endif - return path.buf; + return strbuf_detach(&path, NULL); } diff --git a/apply.c b/apply.c index b8bd5a4bef..e6dbab26ad 100644 --- a/apply.c +++ b/apply.c @@ -2046,7 +2046,7 @@ static void prefix_one(struct apply_state *state, char **name) char *old_name = *name; if (!old_name) return; - *name = xstrdup(prefix_filename(state->prefix, *name)); + *name = prefix_filename(state->prefix, *name); free(old_name); } @@ -4805,6 +4805,7 @@ int apply_all_patches(struct apply_state *state, for (i = 0; i < argc; i++) { const char *arg = argv[i]; + char *to_free = NULL; int fd; if (!strcmp(arg, "-")) { @@ -4814,19 +4815,21 @@ int apply_all_patches(struct apply_state *state, errs |= res; read_stdin = 0; continue; - } else if (0 < state->prefix_length) - arg = prefix_filename(state->prefix, arg); + } else + arg = to_free = prefix_filename(state->prefix, arg); fd = open(arg, O_RDONLY); if (fd < 0) { error(_("can't open patch '%s': %s"), arg, strerror(errno)); res = -128; + free(to_free); goto end; } read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options); close(fd); + free(to_free); if (res < 0) goto end; errs |= res; diff --git a/builtin/config.c b/builtin/config.c index 74f6c34d11..4f49a0edb9 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -527,8 +527,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = - xstrdup(prefix_filename(prefix, - given_config_source.file)); + prefix_filename(prefix, given_config_source.file); } if (respect_includes == -1) diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 2ea36909d2..bbeaf20bcc 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -145,7 +145,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) char *to_free = NULL; if (prefix) - arg = to_free = xstrdup(prefix_filename(prefix, arg)); + arg = to_free = prefix_filename(prefix, arg); hash_object(arg, type, no_filters ? NULL : vpath ? vpath : arg, flags, literally); free(to_free); diff --git a/builtin/log.c b/builtin/log.c index bfdc7a23d3..670229cbb4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1084,7 +1084,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) if (!output_directory) return prefix; - return xstrdup(prefix_filename(prefix, output_directory)); + return prefix_filename(prefix, output_directory); } static const char * const builtin_format_patch_usage[] = { diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index 681f07f54d..cfb667a594 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -11,13 +11,6 @@ static const char mailinfo_usage[] = "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding= | -n] [--scissors | --no-scissors] < mail >info"; -static char *prefix_copy(const char *prefix, const char *filename) -{ - if (!prefix || is_absolute_path(filename)) - return xstrdup(filename); - return xstrdup(prefix_filename(prefix, filename)); -} - int cmd_mailinfo(int argc, const char **argv, const char *prefix) { const char *def_charset; @@ -60,8 +53,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) mi.input = stdin; mi.output = stdout; - msgfile = prefix_copy(prefix, argv[1]); - patchfile = prefix_copy(prefix, argv[2]); + msgfile = prefix_filename(prefix, argv[1]); + patchfile = prefix_filename(prefix, argv[2]); status = !!mailinfo(&mi, msgfile, patchfile); clear_mailinfo(&mi); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 63cd943587..47dde7c39c 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -65,11 +65,18 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) } for (i = 0; i < 3; i++) { - const char *fname = prefix_filename(prefix, argv[i]); + char *fname; + int ret; + if (!names[i]) names[i] = argv[i]; - if (read_mmfile(mmfs + i, fname)) + + fname = prefix_filename(prefix, argv[i]); + ret = read_mmfile(mmfs + i, fname); + free(fname); + if (ret) return -1; + if (mmfs[i].size > MAX_XDIFF_SIZE || buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) return error("Cannot merge binary files: %s", @@ -86,7 +93,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret >= 0) { const char *filename = argv[0]; - const char *fpath = prefix_filename(prefix, argv[0]); + char *fpath = prefix_filename(prefix, argv[0]); FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) @@ -98,6 +105,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) else if (fclose(f)) ret = error_errno("Could not close %s", filename); free(result.ptr); + free(fpath); } if (ret > 127) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index c8035331e2..7cd01c2819 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -228,7 +228,9 @@ static int show_file(const char *arg, int output_prefix) if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV)) { if (output_prefix) { const char *prefix = startup_info->prefix; - show(prefix_filename(prefix, arg)); + char *fname = prefix_filename(prefix, arg); + show(fname); + free(fname); } else show(arg); return 1; diff --git a/builtin/worktree.c b/builtin/worktree.c index e38325e44b..9993ded41a 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -318,7 +318,8 @@ static int add(int ac, const char **av, const char *prefix) { struct add_opts opts; const char *new_branch_force = NULL; - const char *path, *branch; + char *path; + const char *branch; struct option options[] = { OPT__FORCE(&opts.force, N_("checkout even if already checked out in other worktree")), OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), diff --git a/cache.h b/cache.h index 0b53aef0ed..aa6a0fb91a 100644 --- a/cache.h +++ b/cache.h @@ -537,10 +537,10 @@ extern char *prefix_path_gently(const char *prefix, int len, int *remaining, con * not have to interact with index entry; i.e. name of a random file * on the filesystem. * - * The return value may point to static storage which will be overwritten by - * further calls. + * The return value is always a newly allocated string (even if the + * prefix was empty). */ -extern const char *prefix_filename(const char *prefix, const char *path); +extern char *prefix_filename(const char *prefix, const char *path); extern int check_filename(const char *prefix, const char *name); extern void verify_filename(const char *prefix, diff --git a/diff-no-index.c b/diff-no-index.c index 5f7317ced9..79229382b0 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -266,7 +266,7 @@ void diff_no_index(struct rev_info *revs, */ p = file_from_standard_input; else if (prefix) - p = xstrdup(prefix_filename(prefix, p)); + p = prefix_filename(prefix, p); paths[i] = p; } diff --git a/diff.c b/diff.c index 70870b4b69..58cb72d7e7 100644 --- a/diff.c +++ b/diff.c @@ -4023,8 +4023,7 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--pickaxe-regex")) options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { - const char *path = prefix_filename(prefix, optarg); - options->orderfile = xstrdup(path); + options->orderfile = prefix_filename(prefix, optarg); return argcount; } else if ((argcount = parse_long_opt("diff-filter", av, &optarg))) { @@ -4071,13 +4070,14 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--no-function-context")) DIFF_OPT_CLR(options, FUNCCONTEXT); else if ((argcount = parse_long_opt("output", av, &optarg))) { - const char *path = prefix_filename(prefix, optarg); + char *path = prefix_filename(prefix, optarg); options->file = fopen(path, "w"); if (!options->file) die_errno("Could not open '%s'", path); options->close_file = 1; if (options->use_color != GIT_COLOR_ALWAYS) options->use_color = GIT_COLOR_NEVER; + free(path); return argcount; } else return 0; diff --git a/parse-options.c b/parse-options.c index ba6cc30b26..a23a1e67f0 100644 --- a/parse-options.c +++ b/parse-options.c @@ -40,7 +40,7 @@ static void fix_filename(const char *prefix, const char **file) if (!file || !*file || !prefix || is_absolute_path(*file) || !strcmp("-", *file)) return; - *file = xstrdup(prefix_filename(prefix, *file)); + *file = prefix_filename(prefix, *file); } static int opt_command_mode_error(const struct option *opt, diff --git a/setup.c b/setup.c index a76379e0ce..5c7946d2b4 100644 --- a/setup.c +++ b/setup.c @@ -135,6 +135,7 @@ int path_inside_repo(const char *prefix, const char *path) int check_filename(const char *prefix, const char *arg) { const char *name; + char *to_free = NULL; struct stat st; if (starts_with(arg, ":/")) { @@ -142,13 +143,17 @@ int check_filename(const char *prefix, const char *arg) return 1; name = arg + 2; } else if (prefix) - name = prefix_filename(prefix, arg); + name = to_free = prefix_filename(prefix, arg); else name = arg; - if (!lstat(name, &st)) + if (!lstat(name, &st)) { + free(to_free); return 1; /* file exists */ - if (errno == ENOENT || errno == ENOTDIR) + } + if (errno == ENOENT || errno == ENOTDIR) { + free(to_free); return 0; /* file does not exist */ + } die_errno("failed to stat '%s'", arg); } diff --git a/worktree.c b/worktree.c index 42dd3d52b0..bae787cf8d 100644 --- a/worktree.c +++ b/worktree.c @@ -250,16 +250,19 @@ struct worktree *find_worktree(struct worktree **list, { struct worktree *wt; char *path; + char *to_free = NULL; if ((wt = find_worktree_by_suffix(list, arg))) return wt; - arg = prefix_filename(prefix, arg); + if (prefix) + arg = to_free = prefix_filename(prefix, arg); path = real_pathdup(arg, 1); for (; *list; list++) if (!fspathcmp(path, real_path((*list)->path))) break; free(path); + free(to_free); return *list; } From af10e8b1559790d5c78fcc2a1aae5c42f3930a00 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:30:41 -0400 Subject: [PATCH 5/6] prefix_filename: simplify windows #ifdef The prefix_filename function used to do an early return when there was no prefix on non-Windows platforms, but always allocated on Windows so that it could call convert_slashes(). Now that the function always allocates, we can unify the logic and make convert_slashes() the only conditional part. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- abspath.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/abspath.c b/abspath.c index 4addd1fde0..7f1cfe9792 100644 --- a/abspath.c +++ b/abspath.c @@ -251,18 +251,15 @@ char *prefix_filename(const char *pfx, const char *arg) struct strbuf path = STRBUF_INIT; size_t pfx_len = pfx ? strlen(pfx) : 0; -#ifndef GIT_WINDOWS_NATIVE - if (!pfx_len || is_absolute_path(arg)) - return xstrdup(arg); - strbuf_add(&path, pfx, pfx_len); - strbuf_addstr(&path, arg); -#else - /* don't add prefix to absolute paths, but still replace '\' by '/' */ - if (is_absolute_path(arg)) + if (!pfx_len) + ; /* nothing to prefix */ + else if (is_absolute_path(arg)) pfx_len = 0; - else if (pfx_len) + else strbuf_add(&path, pfx, pfx_len); + strbuf_addstr(&path, arg); +#ifdef GIT_WINDOWS_NATIVE convert_slashes(path.buf + pfx_len); #endif return strbuf_detach(&path, NULL); From 3b754eedd58636926d07b14a34799a3aa7b8ebc2 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 20 Mar 2017 21:31:27 -0400 Subject: [PATCH 6/6] bundle: use prefix_filename with bundle path We may take the path to a bundle file as an argument, and need to adjust the filename based on the prefix we discovered while setting up the git directory. We do so manually into a fixed-size buffer, but using prefix_filename() is the normal way. Besides being more concise, there are two subtle improvements: 1. The original inserted a "/" between the two paths, even though the "prefix" argument always has the "/" appended. That means that: cd subdir && git bundle verify ../foo.bundle was looking at (and reporting) subdir//../foo.bundle. Harmless, but ugly. Using prefix_filename() gets this right. 2. The original checked for an absolute path by looking for a leading '/'. It should have been using is_absolute_path(), which also covers more cases on Windows (backslashes and dos drive prefixes). But it's easier still to just pass the name to prefix_filename(), which handles this case automatically. Note that we'll just leak the resulting buffer in the name of simplicity, since it needs to last through the duration of the program anyway. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin/bundle.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/builtin/bundle.c b/builtin/bundle.c index 4883a435a9..d0de59b94f 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -20,21 +20,15 @@ int cmd_bundle(int argc, const char **argv, const char *prefix) struct bundle_header header; const char *cmd, *bundle_file; int bundle_fd = -1; - char buffer[PATH_MAX]; if (argc < 3) usage(builtin_bundle_usage); cmd = argv[1]; - bundle_file = argv[2]; + bundle_file = prefix_filename(prefix, argv[2]); argc -= 2; argv += 2; - if (prefix && bundle_file[0] != '/') { - snprintf(buffer, sizeof(buffer), "%s/%s", prefix, bundle_file); - bundle_file = buffer; - } - memset(&header, 0, sizeof(header)); if (strcmp(cmd, "create") && (bundle_fd = read_bundle_header(bundle_file, &header)) < 0)