git-mv: Fix error with multiple sources.
PATCH 1/1 git-mv: Fix error with multiple sources.
Commit b8f26269 (fix directory separator treatment on Windows, 30-06-2009) introduced a bug on Mac OS X. The problem is that basename() may return a pointer to internal static storage space that will be overwritten by subsequent calls:
Quoted 1 line
git mv
dir/a.txtdir/b.txtother/
Checking rename of 'dir/a.txt' to 'other/b.txt'
Checking rename of 'dir/b.txt' to 'other/b.txt'
fatal: multiple sources for the same target,
source=dir/b.txt, destination=other/b.txt
This commit also fixed two potentially dangerous uses of prefix_filename() -- which returns static storage space -- in builtin-config.c and hash-object.c.
get_pathspec(): If called with an empty pathspec, allocate a new pathspec with a copy of prefix. This is consistent with the behavior when called with a non-empty pathspec and prevents potential errors.
---
This is my first patch submission to git. Perhaps this patch should be split into two? The one-line change to builtin-mv.c suffices to fix the bug.
builtin-config.c | 4 ++-- builtin-mv.c | 2 +- hash-object.c | 2 +- setup.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-)
builtin-config.c +2 -2
@@ -356,9 +356,9 @@ int cmd_config(int argc, const char **argv, const char *unused_prefix) config_exclusive_filename =git_etc_gitconfig (); else if (given_config_file) {if (!is_absolute_path (given_config_file) && prefix) - config_exclusive_filename =prefix_filename (prefix, + config_exclusive_filename =xstrdup (prefix_filename (prefix,strlen (prefix), - argv[2 ]); + argv[2 ]));else config_exclusive_filename = given_config_file; }
builtin-mv.c +1 -1
@@ -27,7 +27,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,if (length >0 &&is_dir_sep (result[i][length -1 ])) result[i] =xmemdupz (result[i], length -1 );if (base_name) - result[i] =basename ((char *)result[i]); + result[i] =xstrdup (basename ((char *)result[i])); }return get_pathspec (prefix, result); }
hash-object.c +1 -1
@@ -91,7 +91,7 @@ int main(int argc, const char **argv) prefix =setup_git_directory (); 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 );
setup.c +2 -2
@@ -132,8 +132,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)return NULL ;if (!entry) { -static const char *spec[2 ]; - spec[0 ] = prefix; +const char **spec =xmalloc (sizeof (char *) *2 ); + spec[0 ] =xstrdup (prefix); spec[1 ] =NULL ;return spec; }
Signature · 1 line
1.6.6.1
builtin-mv.c static const char **copy_pathspec(const char *prefix, const char **pathspec, · line 33
Quoted 13 lines · David Rydh
builtin-mv.c +1
-1@@ -27,7 +27,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,if (length >0 &&is_dir_sep (result[i][length -1 ])) result[i] =xmemdupz (result[i], length -1 );if (base_name) - result[i] =basename ((char *)result[i]); + result[i] =xstrdup (basename ((char *)result[i])); }return get_pathspec (prefix, result); }
We are already leaking memory of magnitude O(argc*length of file names), and IMO, this new leak of the same magnitude doesn't hurt.
If you want to avoid it, you can set NO_LIBGEN_H in Makefile.
The other changes in this patch should really be a separate patch. They do not fix an immediate problem, right?
-- Hannes
Quoted 9 lines · David Rydh
Commit
b8f26269(fix directory separator treatment on Windows, 30-06-2009) introduced a bug on Mac OS X. The problem is thatbasename()may return a pointer to internal static storage space that will be overwritten by subsequent calls:Quoted 1 line
git mv
dir/a.txtdir/b.txtother/Checking rename of '
dir/a.txt' to 'other/b.txt'
Checking rename of 'dir/b.txt' to 'other/b.txt'
Good spotting. basename(3)/dirname(3) are tricky functions to use correctly. In addition to the "static storage" implementation and its associated problem you encountered, they can also be implemented to modify the given string in place, and can even return a pointer _into_ the given string, which means that if you do:
duped = strdup(string);
duped = basename(duped);
... use duped ...
free(duped);you may be burned quite badly. The other call site of basename(3) is in diff.c and it looks safe.
Quoted 5 lines
setup.c +0
-0
setup.c const char **get_pathspec(const char *prefix, const char **pathspec) · line 139
Quoted 10 lines
setup.c +2
-2@@ -132,8 +132,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)return NULL ;if (!entry) { -static const char *spec[2 ]; - spec[0 ] = prefix; +const char **spec =xmalloc (sizeof (char *) *2 ); + spec[0 ] =xstrdup (prefix); spec[1 ] =NULL ;return spec; }
I don't understand this change. Because elements of returned pathspec from this function are often simply the pathspec argument itself (which in turn is often argv[] of the calling program), and other times allocated by this function, the callers are never going to free() them.
Quoted 7 lines · Junio C Hamano
Quoted 7 lines
setup.c +0
-0@@ -132,8 +132,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)return NULL ;
Quoted 13 lines
setup.c const char **get_pathspec(const char *prefix, const char **pathspec) · line 139
Quoted 8 lines
setup.c +2
-2if (!entry) { -static const char *spec[2 ]; - spec[0 ] = prefix; +const char **spec =xmalloc (sizeof (char *) *2 ); + spec[0 ] =xstrdup (prefix); spec[1 ] =NULL ;return spec; }I don't understand this change. Because elements of returned pathspec from this function are often simply the pathspec argument itself (which in turn is often argv[] of the calling program), and other times allocated by this function, the callers are never going to
free()them.
The xstrdup of prefix is for good measure. The important change is the removal of the static spec-array. Two invocations of get_pathspec with different prefixes could invalidate the contents of a pathspec in use.
When called with a non-empty pathspec, all the entries are allocated so I think it is reasonable to allocate them in the degenerate case as well. My impression of the git-code is that it is leaking quite a bit when it comes to strings anyway.
builtin-mv.c static const char **copy_pathspec(const char *prefix, const char **pathspec, · line 33
Quoted 13 lines · David Rydh
builtin-mv.c +1
-1@@ -27,7 +27,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,if (length >0 &&is_dir_sep (result[i][length -1 ])) result[i] =xmemdupz (result[i], length -1 );if (base_name) - result[i] =basename ((char *)result[i]); + result[i] =xstrdup (basename ((char *)result[i])); }return get_pathspec (prefix, result); }
Given that basename(3) is allowed to modify its parameter, I think the above code is still not portable. casting constness away and feeding result[i], especially when we didn't obtain our own copy by calling xmemdupz(), is especially problematic.
Perhaps something ugly like this?
for (i = 0; i < count; i++) {
int length = strlen(result[i]);
int to_copy = length;
while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1]))
to_copy--;
if (to_copy != length || basename) {
char *it = xmemdupz(result[i], to_copy);
result[i] = base_name ? strdup(basename(it)) : it;
}
}A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 2 lines · Junio C Hamano
Why does '/' matter in
builtin-mv.cthat needed b8f2626, but we can compare with '/' [inmake_relative_path()]?
We should be using is_dir_sep() in make_relative_path() as well (see the fixup that I posted in response to your patch). The thing is that the incorrect result does not matter from a correctness-POV for the only caller of this routine.
-- Hannes
Quoted 9 lines · Junio C Hamano
Quoted 7 lines · Junio C Hamano
Given that basename(3) is allowed to modify its parameter, I think the above code is still not portable. casting constness away and feeding result[i], especially when we didn't obtain our own copy by calling
xmemdupz(), is especially problematic.Perhaps something ugly like this?
Quoted 10 lines
Quoted 10 lines
for (i = 0; i < count; i++) { int length = strlen(result[i]); int to_copy = length; while (to_copy > 0 && is_dir_sep(result[i][to_copy - 1])) to_copy--; if (to_copy != length || basename) { char *it = xmemdupz(result[i], to_copy); result[i] = base_name ? strdup(basename(it)) : it; } }
This looks fine. Currently we have the odd behavior
Quoted 1 line
git mv -n dir/ new-dir
Checking rename of 'dir' to 'new-dir' Checking rename of 'dir/a.txt' to 'new-dir/a.txt' Checking rename of 'dir/b.txt' to 'new-dir/b.txt'
Quoted 1 line
git mv -n dir// new-dir
Checking rename of 'dir/' to 'new-dir'
fatal: source directory is empty, source=dir/, destination=new-dir
Note that at the end of copy_pathspec we call get_pathspec which squashes multiple slashes (even if prefix=NULL) but does not remove a trailing slash so it is necessary to squash them all as above.
Quoted 3 lines · David Rydh
This commit also fixed two potentially dangerous uses of
prefix_filename()-- which returns static storage space -- inbuiltin-config.candhash-object.c.
This should probably be a separate patch. builtin-hash-object.c also uses prefix_filename() first to obtain vpath without strdup() and then uses the function to create arg, which seems to be unsafe to me. I've looked at all the hits from
$ git grep -n -e prefix_filename\( -- '*.c'
and other places seem to be Ok.
Quoted 6 lines · Junio C Hamano
Quoted 3 lines · David Rydh
This commit also fixed two potentially dangerous uses of
prefix_filename()-- which returns static storage space -- inbuiltin-config.candhash-object.c.
Quoted 8 lines
This should probably be a separate patch.
builtin-hash-object.calso usesprefix_filename()first to obtain vpath withoutstrdup()and then uses the function to create arg, which seems to be unsafe to me. I've looked at all the hits from$ git grep -n -e prefix_filename\( -- '*.c'and other places seem to be Ok.
Yes, this was how I found these two places. Do you agree that strdup'ing vpath as I did in the patch fixes this flaw? (I assume that builtin-hash-object.c = hash-object.c) I'll split it up into two patches.
builtin-config.c int cmd_config(int argc, const char **argv, const char *unused_prefix) · line 361
Quoted 6 lines · David Rydh
builtin-config.c +2
-2if (!is_absolute_path (given_config_file) && prefix) - config_exclusive_filename =prefix_filename (prefix, + config_exclusive_filename =xstrdup (prefix_filename (prefix,strlen (prefix), - argv[2 ]); + argv[2 ]));
Broken indentation on the last two lines.
Other than that, you use strdup without free-ing the result. Probably not so serious, but it may be cleaner to actually call free, or to explain why you don't (in commit message).
Signature · 2 lines
Matthieu Moy http://www-verimag.imag.fr/~moy/