From: Junio C Hamano <hidden> Date: 2016-06-15 22:54:36
Michael Haggerty [off-list ref] writes:
By the way, is the use of realpath(3) permissible in git code?
GIT_CEILING_DIRECTORIES handling could be fixed relatively easily by
using this function to canonicalize pathnames before comparison.
As long as we can add a compat/realpath.c (perhaps lift one from
glibc before they went GPLv3) for platforms that matter, I do not
see it as a huge problem. How close is abspath.c::real_path() to
what you need?
[1] This would be analogous to the inclusion of a space in "trash
directory.*", which I presume was done to detect space-handling problems
quickly.
Yeah, understood where you are coming from, and I think I can agree
with where you are trying to go.
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:36
On 08/30/2012 07:26 AM, Junio C Hamano wrote:
Michael Haggerty [off-list ref] writes:
quoted
By the way, is the use of realpath(3) permissible in git code?
GIT_CEILING_DIRECTORIES handling could be fixed relatively easily by
using this function to canonicalize pathnames before comparison.
As long as we can add a compat/realpath.c (perhaps lift one from
glibc before they went GPLv3) for platforms that matter, I do not
see it as a huge problem. How close is abspath.c::real_path() to
what you need?
Cool, I didn't know about that function. It's approximately what is
needed, except that it dies if fed an invalid path (unacceptable when
processing GIT_CEILING_DIRECTORIES) and it's buggy (try "test-path-utils
real_path ''" or "test-path-utils real_path '/foo'"). However, I'm
working on fixing it and splitting off a variant that returns NULL on
errors.
Michael
--
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
This series fixes longest_ancestor_length() so that it works even if
prefix_list contains entries that involve symlinks. The basic goal of
the series is to call real_path() on each of the entries so that a
textual comparison of the potential prefix to the front of path
correctly decides whether the path is located inside of the entry.
But along the way some other things had to be changed:
* real_path() die()s if the path passed to it is invalid, whereas it
is allowed for GIT_CEILING_DIRECTORIES to contain invalid paths. So
create a new function real_path_if_valid() that returns NULL for
invalid paths.
* Changing longest_ancestor_length() to call real_path_if_valid()
would make the former very difficult to test (because the tests
would depend on the contents of the whole filesystem). Therefore,
rewrite longest_ancestor_length() in terms of functions
string_list_split(), string_list_longest_prefix(), and
real_path_if_valid() which are tested individually.
The net results of these changes are that:
1. t1504 used to have to canonicalize TRASH_DIRECTORY to make itself
work even if the --root directory contains symlinks. This
canonicalization is no longer necessary (and has been removed).
2. t4035, which used to fail if the --root directory contained
symlinks, now works correctly in that situation.
After this change, all tests pass if the --root directory does *not*
contain symlinks, but t9903 still fails if the --root directory
contains symlinks. I haven't analyzed the cause of t9903's failure,
but it does not appear to be related to the GIT_CEILING_DIRECTORIES
feature.
On the mailing list I suggested *purposely* inserting symlinks into
the "trash directory.*" paths to test symlink handling more
systematically. This patch series does *NOT* make that change.
Michael Haggerty (8):
Introduce new static function real_path_internal()
Introduce new function real_path_if_valid()
longest_ancestor_length(): use string_list_split()
longest_ancestor_length(): explicitly filter list before loop
longest_ancestor_length(): always add a slash to the end of prefixes
longest_ancestor_length(): use string_list_longest_prefix()
longest_ancestor_length(): resolve symlinks before comparing paths
t1504: stop resolving symlinks in GIT_CEILING_DIRECTORIES
abspath.c | 98 ++++++++++++++++++++++++++++++++++++++-----------
cache.h | 1 +
path.c | 54 ++++++++++++++++-----------
t/t0060-path-utils.sh | 64 --------------------------------
t/t1504-ceiling-dirs.sh | 67 +++++++++++++++++----------------
5 files changed, 144 insertions(+), 140 deletions(-)
--
1.7.11.3
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
It accepts a new parameter, die_on_error. If die_on_error is false,
it simply cleans up after itself and returns NULL rather than dying.
Signed-off-by: Michael Haggerty <redacted>
---
abspath.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++---------------
1 file changed, 72 insertions(+), 21 deletions(-)
@@ -15,15 +15,26 @@ int is_directory(const char *path)#define MAXDEPTH 5/*-*Usethistogettherealpath,i.e.resolvelinks.Ifyouwantan-*absolutepathbutdon'tmindlinks,useabsolute_path.+*Returntherealpath(i.e.,absolutepath,withsymlinksresolved+*andextraslashesremoved)equivalenttothespecifiedpath.(If+*youwantanabsolutepathbutdon'tmindlinks,use+*absolute_path().)Thereturnvalueisapointertoastatic+*buffer.+*+*TheinputandallintermediatepathsmustbeshorterthanMAX_PATH.+*Thedirectorypartofpath(i.e.,everythinguptothelast+*dir_sep)mustdenoteavalid,existingdirectory,butthelast+*componentneednotexist.Ifdie_on_errorisset,thendiewithan+*informativeerrormessageifthereisaproblem.Otherwise,return+*NULLonerrors(withoutgeneratinganyoutput).**Ifpathisourbuffer,thenreturnpath,asit'salreadywhatthe*userwants.*/-constchar*real_path(constchar*path)+staticconstchar*real_path_internal(constchar*path,intdie_on_error){staticcharbufs[2][PATH_MAX+1],*buf=bufs[0],*next_buf=bufs[1];+char*retval=NULL;charcwd[1024]="";intbuf_index=1;
@@ -35,11 +46,19 @@ const char *real_path(const char *path)if(path==buf||path==next_buf)returnpath;-if(!*path)-die("The empty string is not a valid path");+if(!*path){+if(die_on_error)+die("The empty string is not a valid path");+else+gotoerror_out;+}-if(strlcpy(buf,path,PATH_MAX)>=PATH_MAX)-die("Too long path: %.*s",60,path);+if(strlcpy(buf,path,PATH_MAX)>=PATH_MAX){+if(die_on_error)+die("Too long path: %.*s",60,path);+else+gotoerror_out;+}while(depth--){if(!is_directory(buf)){
@@ -54,20 +73,36 @@ const char *real_path(const char *path)}if(*buf){-if(!*cwd&&!getcwd(cwd,sizeof(cwd)))-die_errno("Could not get current working directory");+if(!*cwd&&!getcwd(cwd,sizeof(cwd))){+if(die_on_error)+die_errno("Could not get current working directory");+else+gotoerror_out;+}-if(chdir(buf))-die_errno("Could not switch to '%s'",buf);+if(chdir(buf)){+if(die_on_error)+die_errno("Could not switch to '%s'",buf);+else+gotoerror_out;+}+}+if(!getcwd(buf,PATH_MAX)){+if(die_on_error)+die_errno("Could not get current working directory");+else+gotoerror_out;}-if(!getcwd(buf,PATH_MAX))-die_errno("Could not get current working directory");if(last_elem){size_tlen=strlen(buf);-if(len+strlen(last_elem)+2>PATH_MAX)-die("Too long path name: '%s/%s'",-buf,last_elem);+if(len+strlen(last_elem)+2>PATH_MAX){+if(die_on_error)+die("Too long path name: '%s/%s'",+buf,last_elem);+else+gotoerror_out;+}if(len&&!is_dir_sep(buf[len-1]))buf[len++]='/';strcpy(buf+len,last_elem);
@@ -77,10 +112,18 @@ const char *real_path(const char *path)if(!lstat(buf,&st)&&S_ISLNK(st.st_mode)){ssize_tlen=readlink(buf,next_buf,PATH_MAX);-if(len<0)-die_errno("Invalid symlink '%s'",buf);-if(PATH_MAX<=len)-die("symbolic link too long: %s",buf);+if(len<0){+if(die_on_error)+die_errno("Invalid symlink '%s'",buf);+else+gotoerror_out;+}+if(PATH_MAX<=len){+if(die_on_error)+die("symbolic link too long: %s",buf);+else+gotoerror_out;+}next_buf[len]='\0';buf=next_buf;buf_index=1-buf_index;
@@ -89,10 +132,18 @@ const char *real_path(const char *path)break;}+retval=buf;+error_out:+free(last_elem);if(*cwd&&chdir(cwd))die_errno("Could not change back to '%s'",cwd);-returnbuf;+returnretval;+}++constchar*real_path(constchar*path)+{+returnreal_path_internal(path,1);}staticconstchar*get_pwd_cwd(void)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
The function is like real_path(), except that it returns NULL on error
instead of dying.
Signed-off-by: Michael Haggerty <redacted>
---
abspath.c | 5 +++++
cache.h | 1 +
2 files changed, 6 insertions(+)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
Separate the step of filtering and normalizing elements of the
prefixes list from the iteration that looks for the longest prefix.
This will help keep the function testable after we not only normalize
the paths, but also convert them into real paths.
Signed-off-by: Michael Haggerty <redacted>
---
path.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
Previously we stripped off any slashes that were present. Instead,
add a slash if it is missing. This removes the need for an extra
check that path has a slash following the prefix and makes the
handling of the root directory more natural, making the way clear to
use string_list_longest_prefix().
Signed-off-by: Michael Haggerty <redacted>
---
path.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
longest_ancestor_length() relies on a textual comparison of directory
parts to find the part of path that overlaps with one of the paths in
prefix_list. But this doesn't work if any of the prefixes involves a
symbolic link, because the directories will look different even though
they might logically refer to the same directory. So canonicalize the
paths listed in prefix_list using real_path_if_valid() before trying
to find matches.
path is already in canonical form, so doesn't need to be canonicalized
again.
This fixes some problems with using GIT_CEILING_DIRECTORIES that
contains paths involving symlinks, including t4035 if run with --root
set to a path involving symlinks.
Remove a number of tests of longest_ancestor_length(). It is awkward
to test longest_ancestor_length() now, because its new path
normalization behavior depends on the contents of the whole
filesystem. But we can live without the tests, because
longest_ancestor_length() is now built of reusable components that are
themselves tested separately: string_list_split(),
string_list_longest_prefix(), and real_path_if_valid().
Signed-off-by: Michael Haggerty <redacted>
---
path.c | 17 ++++++++------
t/t0060-path-utils.sh | 64 ---------------------------------------------------
2 files changed, 10 insertions(+), 71 deletions(-)
@@ -570,22 +570,25 @@ int normalize_path_copy(char *dst, const char *src)staticintnormalize_path_callback(structstring_list_item*item,void*cb_data){-charbuf[PATH_MAX+2];+char*buf;constchar*ceil=item->string;-intlen=strlen(ceil);+constchar*realpath;+intlen;-if(len==0||len>PATH_MAX||!is_absolute_path(ceil))+if(!*ceil||!is_absolute_path(ceil))return0;-memcpy(buf,ceil,len+1);-if(normalize_path_copy(buf,buf)<0)+realpath=real_path_if_valid(ceil);+if(!realpath)return0;-len=strlen(buf);+len=strlen(realpath);+buf=xmalloc(len+2);/* Leave space for possible trailing slash */+strcpy(buf,realpath);if(len==0||buf[len-1]!='/'){buf[len++]='/';buf[len++]='\0';}free(item->string);-item->string=xstrdup(buf);+item->string=buf;return1;}
@@ -12,28 +12,6 @@ norm_path() {"test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"}-# On Windows, we are using MSYS's bash, which mangles the paths.-# Absolute paths are anchored at the MSYS installation directory,-# which means that the path / accounts for this many characters:-rootoff=$(test-path-utilsnormalize_path_copy/|wc-c)-# Account for the trailing LF:-iftest$rootoff=2;then-rootoff=# we are on Unix-else-rootoff=$(($rootoff-1))-fi--ancestor(){-# We do some math with the expected ancestor length.-expected=$3-iftest-n"$rootoff"&&test"x$expected"!=x-1;then-expected=$(($expected+$rootoff))-fi-test_expect_success"longest ancestor: $1$2 => $expected"\-"actual=\$(test-path-utils longest_ancestor_length '$1' '$2') &&-test\"\$actual\"='$expected'"-}-# Absolute path tests must be skipped on Windows because due to path mangling# the test program never sees a POSIX-style absolute pathcase$(uname-s)in
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
Use string_list_longest_prefix() in the implementation of
longest_ancestor_length(), instead of an equivalent loop.
Signed-off-by: Michael Haggerty <redacted>
---
path.c | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
From: Michael Haggerty <hidden> Date: 2016-06-15 22:54:52
This test used to explicitly resolve symlinks in the paths derived
from TRASH_DIRECTORY that were written to GIT_CEILING_DIRECTORIES,
because the code handling GIT_CEILING_DIRECTORIES was confused by
symlinks. This is no longer necessary.
Signed-off-by: Michael Haggerty <redacted>
---
t/t1504-ceiling-dirs.sh | 67 ++++++++++++++++++++++++-------------------------
1 file changed, 33 insertions(+), 34 deletions(-)