bug#10686: mv: moving hardlink of a softlink to the softlink does nothing

2012-02-04 Thread Jim Meyering
Jim Meyering wrote:

> Eric Blake wrote:
>
>> On 02/01/2012 05:47 AM, Jim Meyering wrote:
>>> Bernhard Voelker wrote:
 Playing around with the latest mv checkin,
 I noticed another corner case:

 Create a file 'f', a symlink 'l' to it, and
 then a hardlink 's' to that symlink:

   $ touch f && ln -s f l && ln l s && ls -ogi
   total 0
   6444 -rw-r--r-- 1 0 Feb  1 08:52 f
   6462 lrwxrwxrwx 2 1 Feb  1 08:52 l -> f
   6462 lrwxrwxrwx 2 1 Feb  1 08:52 s -> f
>>>
>>> Hi Bernhard,
>>> Thanks for the report.
>>>
>>> Here, you've already gotten into questionable territory, since the
>>> idea of a hard link to a symbolic link is not standardized.
>>
>> Actually, POSIX 2008 _did_ standardize the notion of a hard link to a
>> symlink, thanks to linkat().
>>
>>> For example, on FreeBSD 9.0, you cannot even create one:
>>
>> That's a POSIX-compliance bug in FreeBSD.  In that case, the best we can
>> do is emulate it by creating a new symlink with identical contents and
>> owner and as much of the timestamp as lutimens will allow.
>>
>>>
>>> It's a standards and kernel issue.
>>> POSIX explicitly says (of rename)
>>>
>>> If the old argument and the new argument resolve to the same existing
>>> file, rename( ) shall return successfully and perform no other action.
>>>
>>> though that particular wording might be slated to change with POSIX 2008,
>>> to allow different (more desirable) behavior.  Search the austin-group
>>> archives if you need to be sure.
>>
>> The wording for rename(2) did not change, but the wording for mv(1)
>> _did_ change to allow slightly more desirable behavior.  Here's the
>> latest wording, as modified by the latest bug report:
>>
>> http://austingroupbugs.net/view.php?id=534
>>
>> If the source_file operand and destination path resolve to either the
>> same existing directory entry or different directory entries for the
>> same existing file, then the destination path shall not be removed, and
>> one of the following shall occur:
>>  a. No change is made to source_file, no error occurs, and no diagnostic
>> is issued.
>>  b. No change is made to source_file, a diagnostic is issued to standard
>> error identifying the two names, and the exit status is affected.
>>  c. If the source_file operand and destination path name distinct
>> directory entries, then the source_file operand is removed, no error
>> occurs, and no diagnostic is issued.
>>
>> The mv utility shall do nothing more with the current source_file, and
>> go on to any remaining source_files.
>>
>>> Also, I don't know whether the above wording applies to this particular
>>> case of two hard links to the same symlink.  Again, I think we're in
>>> unspecified territory.
>>
>> POSIX actually specifies this quite well - two hardlinks to the same
>> symlink qualify as an instance of two file names that resolve to the
>> same inode after path resolution as checked with lstat().
>
> Thanks for the clarification and quotes, Eric.
>
> Bernhard, here's a better patch.
> With it, mv simply unlinks the "s" in your example:
>
> diff --git a/src/copy.c b/src/copy.c
> index 4810de8..73f5cc5 100644
> --- a/src/copy.c
> +++ b/src/copy.c
> @@ -1229,7 +1229,17 @@ same_file_ok (char const *src_name, struct stat const 
> *src_sb,
>   know this here IFF preserving symlinks), then it's ok -- as long
>   as they are distinct.  */
>if (S_ISLNK (src_sb->st_mode) && S_ISLNK (dst_sb->st_mode))
> -return ! same_name (src_name, dst_name);
> +{
> +  bool sn = same_name (src_name, dst_name);
> +  if ( ! sn && same_link)
> +{
> +  *unlink_src = true;
> +  *return_now = true;
> +  return true;
> +}
> +
> +  return ! sn;
> +}

The above wasn't quite right in that it failed to honor mv's --backup
option.  mv --backup s f would not have created the required backup file.
I've adjusted it to fix that, and added tests to cover both cases.
This is still not quite ready (i.e., the FIXME comment, where I'll add
some explanation), but otherwise should be fine.


>From 646456b8ce053b85d30174462620492df648e0e2 Mon Sep 17 00:00:00 2001
From: Jim Meyering 
Date: Wed, 1 Feb 2012 21:42:45 +0100
Subject: [PATCH] mv: "mv A B" would sometimes succeed, yet A would remain,
 ...

But only when both A and B were hard links to the same symlink.
* src/copy.c (same_file_ok): Handle another special case: the one
in which we are moving a symlink onto a hard link to itself.
In this case, we must explicitly tell the caller to unlink the
source file.  Otherwise, at least the linux-3.x kernel rename
function would do nothing, as mandated by POSIX 2008.
* tests/mv/symlink-onto-hardlink-to-self: New test.
* tests/Makefile.am (TESTS): Add it.
* NEWS (Bug fixes): Mention it.
Reported by Bernhard Voelker in http://bugs.gnu.org/10686
---
 NEWS   |5 +++
 src/copy.c 

bug#10472: [PATCH] canonicalize: fix // handling

2012-02-04 Thread Eric Blake
On Cygwin, and other platforms where // is detected as distinct
from / at configure time, the canonicalize routines were incorrectly
treating all instances of multiple leading slashes as //.
See also coreutils bug http://debbugs.gnu.org/10472

* lib/canonicalize.c (canonicalize_filename_mode): Don't convert
/// to //, since only // is special.

Signed-off-by: Eric Blake 
---
 ChangeLog  |6 ++
 lib/canonicalize.c |   12 +++-
 2 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 4bbe447..a9aa40a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2012-02-04  Eric Blake  
+
+   canonicalize: fix // handling
+   * lib/canonicalize.c (canonicalize_filename_mode): Don't convert
+   /// to //, since only // is special.
+
 2012-02-04  Bruno Haible  

ioctl: Fix test failure on native Windows.
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index d3e5645..ed094b7 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -145,7 +145,7 @@ canonicalize_filename_mode (const char *name, 
canonicalize_mode_t can_mode)
   rname_limit = rname + PATH_MAX;
   rname[0] = '/';
   dest = rname + 1;
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/')
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] != '/')
 *dest++ = '/';
 }

@@ -169,7 +169,7 @@ canonicalize_filename_mode (const char *name, 
canonicalize_mode_t can_mode)
   if (dest > rname + 1)
 while ((--dest)[-1] != '/');
   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
-  && *dest == '/')
+  && *dest == '/' && dest[1] != '/')
 dest++;
 }
   else
@@ -267,7 +267,8 @@ canonicalize_filename_mode (const char *name, 
canonicalize_mode_t can_mode)
   if (buf[0] == '/')
 {
   dest = rname + 1; /* It's an absolute symlink */
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && buf[1] == '/')
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT
+  && buf[1] == '/' && buf[2] != '/')
 *dest++ = '/';
 }
   else
@@ -277,7 +278,7 @@ canonicalize_filename_mode (const char *name, 
canonicalize_mode_t can_mode)
   if (dest > rname + 1)
 while ((--dest)[-1] != '/');
   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
-  && *dest == '/')
+  && *dest == '/' && dest[1] != '/')
 dest++;
 }

@@ -295,7 +296,8 @@ canonicalize_filename_mode (const char *name, 
canonicalize_mode_t can_mode)
 }
   if (dest > rname + 1 && dest[-1] == '/')
 --dest;
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && *dest == '/')
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
+  && *dest == '/' && dest[1] != '/')
 dest++;
   *dest = '\0';
   if (rname_limit != dest + 1)
-- 
1.7.7.6






bug#10472: [PATCH] canonicalize: fix // handling

2012-02-04 Thread Eric Blake
On 02/04/2012 09:56 AM, Eric Blake wrote:
> On Cygwin, and other platforms where // is detected as distinct
> from / at configure time, the canonicalize routines were incorrectly
> treating all instances of multiple leading slashes as //.
> See also coreutils bug http://debbugs.gnu.org/10472
> 
> * lib/canonicalize.c (canonicalize_filename_mode): Don't convert
> /// to //, since only // is special.
> 

> +++ b/lib/canonicalize.c
> @@ -145,7 +145,7 @@ canonicalize_filename_mode (const char *name, 
> canonicalize_mode_t can_mode)
>rname_limit = rname + PATH_MAX;
>rname[0] = '/';
>dest = rname + 1;
> -  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/')
> +  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] != '/')
>  *dest++ = '/';

This never initializes rname[1] if name is "///",

> @@ -295,7 +296,8 @@ canonicalize_filename_mode (const char *name, 
> canonicalize_mode_t can_mode)
>  }
>if (dest > rname + 1 && dest[-1] == '/')
>  --dest;
> -  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1 && *dest == '/')
> +  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && dest == rname + 1
> +  && *dest == '/' && dest[1] != '/')
>  dest++;

which meant this was reading uninitialized memory, and depending on what
was in the heap, might canonicalize "///" to "/" or "//".  I'm pushing
this additional fix to both files:

diff --git i/lib/canonicalize-lgpl.c w/lib/canonicalize-lgpl.c
index a61bef9..08e76fe 100644
--- i/lib/canonicalize-lgpl.c
+++ w/lib/canonicalize-lgpl.c
@@ -158,6 +158,7 @@ __realpath (const char *name, char *resolved)
   dest = rpath + 1;
   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] !=
'/')
 *dest++ = '/';
+  *dest = '\0';
 }

   for (start = end = name; *start; start = end)
diff --git i/lib/canonicalize.c w/lib/canonicalize.c
index ed094b7..2c73094 100644
--- i/lib/canonicalize.c
+++ w/lib/canonicalize.c
@@ -147,6 +147,7 @@ canonicalize_filename_mode (const char *name,
canonicalize_mode_t can_mode)
   dest = rname + 1;
   if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] !=
'/')
 *dest++ = '/';
+  *dest = '\0';
 }

   for (start = name; *start; start = end)


-- 
Eric Blake   ebl...@redhat.com+1-919-301-3266
Libvirt virtualization library http://libvirt.org



signature.asc
Description: OpenPGP digital signature


bug#10472: [PATCH] canonicalize: fix // handling

2012-02-04 Thread Eric Blake
On 02/04/2012 10:59 AM, Eric Blake wrote:
> On 02/04/2012 09:56 AM, Eric Blake wrote:
>> On Cygwin, and other platforms where // is detected as distinct
>> from / at configure time, the canonicalize routines were incorrectly
>> treating all instances of multiple leading slashes as //.
>> See also coreutils bug http://debbugs.gnu.org/10472
>>
>> * lib/canonicalize.c (canonicalize_filename_mode): Don't convert
>> /// to //, since only // is special.
>>

> 
> which meant this was reading uninitialized memory, and depending on what
> was in the heap, might canonicalize "///" to "/" or "//".  I'm pushing
> this additional fix to both files:
> 
> diff --git i/lib/canonicalize-lgpl.c w/lib/canonicalize-lgpl.c
> index a61bef9..08e76fe 100644
> --- i/lib/canonicalize-lgpl.c
> +++ w/lib/canonicalize-lgpl.c
> @@ -158,6 +158,7 @@ __realpath (const char *name, char *resolved)
>dest = rpath + 1;
>if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] !=
> '/')
>  *dest++ = '/';
> +  *dest = '\0';
>  }

Still not right.  If you have a symlink at //some/path whose contents is
/, then that would canonicalize to '//' without triggering any valgrind
complaints, because I missed the code that resets rpath on encountering
absolute symlink contents.  Meanwhile, pre-assigning *dest is a
pessimization on platforms where // and / are identical.  I'm pushing
this instead.

From d1f3998942236194f1894c45804ec947d07ed134 Mon Sep 17 00:00:00 2001
From: Eric Blake 
Date: Sat, 4 Feb 2012 11:11:40 -0700
Subject: [PATCH] canonicalize: avoid uninitialized memory use

When DOUBLE_SLASH_IS_DISTINCT_ROOT is non-zero, then we were
reading the contents of rpath[1] even when we had never written
anything there, which meant that "///" would usually canonicalize
to "/" but sometimes to "//" if a '/' was leftover in the heap.
This condition could also occur via 'ln -s / //some/path' and
canonicalizing //some/path, where we rewind rpath but do not
clear out the previous round.  Platforms where "//" and "/" are
equivalent do not suffer from this read-beyond-written bounds.

* lib/canonicalize-lgpl.c (__realpath): Avoid possibility of
random '/' left in dest.
* lib/canonicalize.c (canonicalize_filename_mode): Likewise.

Signed-off-by: Eric Blake 
---
 ChangeLog   |7 +++
 lib/canonicalize-lgpl.c |   17 -
 lib/canonicalize.c  |   17 -
 3 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8f08543..aeea7c8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2012-02-04  Eric Blake  
+
+   canonicalize: avoid uninitialized memory use
+   * lib/canonicalize-lgpl.c (__realpath): Avoid possibility of
+   random '/' left in dest.
+   * lib/canonicalize.c (canonicalize_filename_mode): Likewise.
+
 2012-02-04  Bruno Haible  

spawn-pipe tests: Fix a NULL program name in a diagnostic.
diff --git a/lib/canonicalize-lgpl.c b/lib/canonicalize-lgpl.c
index a61bef9..7aa2d92 100644
--- a/lib/canonicalize-lgpl.c
+++ b/lib/canonicalize-lgpl.c
@@ -156,8 +156,12 @@ __realpath (const char *name, char *resolved)
 {
   rpath[0] = '/';
   dest = rpath + 1;
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] !=
'/')
-*dest++ = '/';
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+{
+  if (name[1] == '/' && name[2] != '/')
+*dest++ = '/';
+  *dest = '\0';
+}
 }

   for (start = end = name; *start; start = end)
@@ -298,9 +302,12 @@ __realpath (const char *name, char *resolved)
   if (buf[0] == '/')
 {
   dest = rpath + 1; /* It's an absolute symlink */
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT
-  && buf[1] == '/' && buf[2] != '/')
-*dest++ = '/';
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+{
+  if (buf[1] == '/' && buf[2] != '/')
+*dest++ = '/';
+  *dest = '\0';
+}
 }
   else
 {
diff --git a/lib/canonicalize.c b/lib/canonicalize.c
index ed094b7..583c1a4 100644
--- a/lib/canonicalize.c
+++ b/lib/canonicalize.c
@@ -145,8 +145,12 @@ canonicalize_filename_mode (const char *name,
canonicalize_mode_t can_mode)
   rname_limit = rname + PATH_MAX;
   rname[0] = '/';
   dest = rname + 1;
-  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && name[1] == '/' && name[2] !=
'/')
-*dest++ = '/';
+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT)
+{
+  if (name[1] == '/' && name[2] != '/')
+*dest++ = '/';
+  *dest = '\0';
+}
 }

   for (start = name; *start; start = end)
@@ -267,9 +271,12 @@ canonicalize_filename_mode (const char *name,
canonicalize_mode_t can_mode)
   if (buf[0] == '/')
 {
   dest = rname + 1;   

bug#10472: [PATCH] realpath: fix problems with // handling

2012-02-04 Thread Eric Blake
On platforms like Cygwin where / and // are distinct, realpath was
incorrectly collapsing // into /.  http://debbugs.gnu.org/10472.

* src/realpath.c (path_common_prefix): When // is special, treat /
and // as having no common match.
(relpath): Allow for no match even without --relative-base.
---

This is the coreutils side of the patch; for this to work, we also
have to upgrade to the latest gnulib.

I'm not pushing this until we decide what to do about testing; I
guess we've already figured out how to make basename and dirname
tests conditional on whether they are on Linux or Cygwin (that is,
whether // and / are the same or different), so I should do something
similar to that for the realpath test.  But I can at least get a
code review on realpath.c while figuring out the testing situation.

 src/realpath.c |7 +++
 1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/src/realpath.c b/src/realpath.c
index 2dc5e11..f06fbcc 100644
--- a/src/realpath.c
+++ b/src/realpath.c
@@ -131,6 +131,10 @@ path_common_prefix (const char *path1, const char *path2)
   int i = 0;
   int ret = 0;

+  if (DOUBLE_SLASH_IS_DISTINCT_ROOT && *path1 == '/' && *path2 == '/'
+  && (path1[1] == '/') != (path2[1] == '/'))
+return 0;
+
   while (*path1 && *path2)
 {
   if (*path1 != *path2)
@@ -168,6 +172,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;

-- 
1.7.7.6