GNU bug report logs - #32455
cp gets confused by symlinks to parent directory

Previous Next

Package: coreutils;

Reported by: Mike Crowe <mac <at> mcrowe.com>

Date: Thu, 16 Aug 2018 14:48:01 UTC

Severity: normal

Tags: notabug

Done: Assaf Gordon <assafgordon <at> gmail.com>

Bug is archived. No further changes may be made.

Full log


View this message in rfc822 format

From: Assaf Gordon <assafgordon <at> gmail.com>
To: Mike Crowe <mac <at> mcrowe.com>, 32455 <at> debbugs.gnu.org
Subject: bug#32455: cp gets confused by symlinks to parent directory
Date: Fri, 18 Jan 2019 03:37:46 -0700
tags 32455 notabug
close 32455
stop

Hello,

It seems your message has not been replied to in a long while.
Sorry about that.

On 2018-08-16 8:47 a.m., Mike Crowe wrote:
> If cp is passed the -d option and told to copy a symlink to the directory
> containing the symlink then it ends up removing the target directory so it
> is unable to create the symlink.

If my understanding is correct, the "-d" flag is not relevant to the
issue. The problem is that "self" is a symlink to a directory:

> 
> Reproduction script:
> 
> --8<--
> #!/bin/sh
> set -x
> 
> rm -rf temp
> mkdir -p temp/src temp/dest
> 
> ln -s . temp/src/self
> 
> # This one works
> cp -vd temp/src/self temp/dest/self

This works because "temp/dest/self" does not exist.
In this case, "temp/dest" is taken as the destination directory,
and "self" is taken as the name of the file/dir/symlink to create.

That is, you could run "cp -vd temp/src/self temp/dest/foobar"
to create "foobar" as a copy of "self".

> # This one fails
> cp -vd temp/src/self temp/dest/self

Here, "temp/dest/self" already exists, and it is a symlink to
a directory.
Meaning, the request is: copy "temp/src/self" into the directory
"temp/dest/self/" (and create "temp/dest/self/self").

This would have gone well, except that because "self" is a symlink
to ".", it can be resolved indefinitely:

  $ file temp/dest/self
  temp/dest/self: symbolic link to .

  $ file temp/dest/self/self/self/self/self/self
  temp/dest/self/self/self/self/self/self: symbolic link to .

  $ file temp/dest/self/self/self/self/self/self/self
  temp/dest/self/self/self/self/self/self/self: symbolic link to .

"cp" first removes "temp/dest/self/self" (which is valid),
but then, "temp/dest/self" is gone (since it is the same file path after 
resolving it).

Hence, "cp" fails by saying "no such directory" on "temp/dest/self/self".

When this step is done, "temp/dest/self" does not exist,
and so:

> # This one works again
> cp -vd temp/src/self temp/dest/self

This works as before.

You can observe what happens on the kernel level
by adding "strace -e trace=file" before the "cp" commands,
this might help in deeper understanding.


To illustrate this differently:

When creating regular directories and files,
then deleting the innermost files, it is naively expected
that the parent directories still exist:

    mkdir -p a/b/c/d
    touch a/b/c/d/e
    rm a/b/c/d/e

That is, a normal program can call "dirname("a/b/c/d/e")"
to get the parent directory of "e", and expect it to still
exist even after "e" is deleted.

But with your case:

   $ mkdir a
   $ ln -s . a/self
   $ rm a/self/self/self/self/self/self

All the "apparent" parent directories ("self/self/self/self/self")
are gone!


> Expected behaviour:
> 
> There should be no error message emitted by the second invocation of cp,
> and the target directory should be in the same state as it is after the
> first or third attempts to copy the symlink.

Not exactly.

What you want is for the DEST parameter of "cp" to always be a file,
never to be considered a directory, i.e. "temp/dest/self"
should always be interpreted as file "self" in directory "temp/dest",
never as directory "temp/dest/self".
Luckily, there is already an option for that:

     -T, --no-target-directory
              treat DEST as a normal file

With "-T", repeated commands work as you expected:

  $ mkdir -p temp/src temp/dest
  $ ln -s . temp/src/self
  $ cp -vdT temp/src/self temp/dest/self
  'temp/src/self' -> 'temp/dest/self'
  $ cp -vdT temp/src/self temp/dest/self
  removed 'temp/dest/self'
  'temp/src/self' -> 'temp/dest/self'
  $ cp -vdT temp/src/self temp/dest/self
  removed 'temp/dest/self'
  'temp/src/self' -> 'temp/dest/self'
  $ cp -vdT temp/src/self temp/dest/self
  removed 'temp/dest/self'
  'temp/src/self' -> 'temp/dest/self'
  [... ad infinitum ...]


I hope this addresses the issue,
I'm closing this as "not a bug", but discussion can continue by replying
to this thread.

regards,
 - assaf





This bug report was last modified 6 years and 163 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.