From: Rémy Lefevre Date: Tue, 2 Apr 2013 01:48:28 +0000 (+0100) Subject: ln: --relative: fix updating of existing symlinks X-Git-Tag: v8.22~170 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e52293aa7fcf283758f97bc9bcc945707ccbce0a;p=thirdparty%2Fcoreutils.git ln: --relative: fix updating of existing symlinks Don't dereference an existing symlink being replaced. I.E. generate the symlink relative to the symlink's containing dir, rather than to some arbitrary place it points to. * src/ln.c (convert_abs_rel): Don't consider the final component of the symlink name when canonicalizing, as we want to avoid dereferencing the final component. * tests/ln/relative.sh: Add a test case. * NEWS: Mention the fix. Resolves http://bugs.gnu.org/14116 --- diff --git a/NEWS b/NEWS index 0c2daad3ec..b9fc6d2d99 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,10 @@ GNU coreutils NEWS -*- outline -*- permissions. [This bug was present in "the beginning".] + ln --relative now updates existing symlinks correctly. Previously it based + the relative link on the dereferenced path of an existing link. + [This bug was introduced when --relative was added in coreutils-8.16.] + ** New features join accepts a new option: --zero-terminated (-z). As with the sort,uniq diff --git a/src/ln.c b/src/ln.c index 1aa1473244..2489b9ac7f 100644 --- a/src/ln.c +++ b/src/ln.c @@ -132,22 +132,24 @@ target_directory_operand (char const *file) static char * convert_abs_rel (const char *from, const char *target) { - char *realtarget = canonicalize_filename_mode (target, CAN_MISSING); + /* Get dirname to generate paths relative to. We don't resolve + the full TARGET as the last component could be an existing symlink. */ + char *targetdir = dir_name (target); + + char *realdest = canonicalize_filename_mode (targetdir, CAN_MISSING); char *realfrom = canonicalize_filename_mode (from, CAN_MISSING); /* Write to a PATH_MAX buffer. */ char *relative_from = xmalloc (PATH_MAX); - /* Get dirname to generate paths relative to. */ - realtarget[dir_len (realtarget)] = '\0'; - - if (!relpath (realfrom, realtarget, relative_from, PATH_MAX)) + if (!relpath (realfrom, realdest, relative_from, PATH_MAX)) { free (relative_from); relative_from = NULL; } - free (realtarget); + free (targetdir); + free (realdest); free (realfrom); return relative_from ? relative_from : xstrdup (from); diff --git a/tests/ln/relative.sh b/tests/ln/relative.sh index 0418b8a046..818da8392d 100755 --- a/tests/ln/relative.sh +++ b/tests/ln/relative.sh @@ -29,4 +29,9 @@ test $(readlink usr/bin/foo) = '../lib/foo/foo' || fail=1 ln -sr usr/bin/foo usr/lib/foo/link-to-foo test $(readlink usr/lib/foo/link-to-foo) = 'foo' || fail=1 +# Correctly update an existing link, which was broken in <= 8.21 +ln -s dir1/dir2/f existing_link +ln -srf here existing_link +test $(readlink existing_link) = 'here' || fail=1 + Exit $fail