From 5e264bf248128735aaba4e368f3875680d2ca438 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Sat, 4 Feb 2012 11:50:53 -0700 Subject: [PATCH] realpath: fix problems with root handling When --relative-base is /, all other paths should be treated as relative (except for // where it matters). Also, on platforms like Cygwin where / and // are distinct, realpath was incorrectly collapsing // into /. http://debbugs.gnu.org/10472. * src/realpath.c (path_prefix, path_common_prefix): Treat / and // as having no common match. (relpath): Allow for no match even without --relative-base. * NEWS: Document this. --- NEWS | 5 +++++ src/realpath.c | 27 +++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index f783afb..e9b3d20 100644 --- a/NEWS +++ b/NEWS @@ -57,6 +57,11 @@ GNU coreutils NEWS -*- outline -*- surprising rename no-op behavior). Now, mv handles this case by skipping the usually-useless rename and simply unlinking A. + realpath no longer mishandles a root directory. This was most + noticeable on platforms where // is a different directory than /, + but could also be observed with --relative-base=/ or + --relative-to=/. [bug since the beginning, in 8.15] + ** Improvements ls can be much more efficient, especially with large directories on file diff --git a/src/realpath.c b/src/realpath.c index 2dc5e11..1972764 100644 --- a/src/realpath.c +++ b/src/realpath.c @@ -108,10 +108,24 @@ realpath_canon (const char *fname, int can_mode) return can_fname; } -/* Test whether prefix is parent or match of path. */ +/* Test whether canonical prefix is parent or match of path. */ static bool _GL_ATTRIBUTE_PURE path_prefix (const char *prefix, const char *path) { + /* We already know prefix[0] and path[0] are '/'. */ + prefix++; + path++; + + /* '/' is the prefix of everything except '//' (since we know '//' + is only present after canonicalization if it is distinct). */ + if (!*prefix) + return *path != '/'; + + /* Likewise, '//' is a prefix of any double-slash path. */ + if (*prefix == '/' && !prefix[1]) + return *path == '/'; + + /* Any other prefix has a non-slash portion. */ while (*prefix && *path) { if (*prefix != *path) @@ -123,7 +137,7 @@ path_prefix (const char *prefix, const char *path) } /* Return the length of the longest common prefix - of PATH1 and PATH2, ensuring only full path components + of canonical PATH1 and PATH2, ensuring only full path components are matched. Return 0 on no match. */ static int _GL_ATTRIBUTE_PURE path_common_prefix (const char *path1, const char *path2) @@ -131,6 +145,12 @@ path_common_prefix (const char *path1, const char *path2) int i = 0; int ret = 0; + /* We already know path1[0] and path2[0] are '/'. Special case + '//', which is only present in a canonical name on platforms + where it is distinct. */ + if ((path1[1] == '/') != (path2[1] == '/')) + return 0; + while (*path1 && *path2) { if (*path1 != *path2) @@ -168,6 +188,9 @@ relpath (const char *can_fname) /* Skip the prefix common to --relative-to and path. */ int common_index = path_common_prefix (can_relative_to, can_fname); + if (!common_index) + return false; + const char *relto_suffix = can_relative_to + common_index; const char *fname_suffix = can_fname + common_index; -- 2.7.4