Subject: Re: bug#32455: cp gets confused by symlinks to parent directory

From: Assaf Gordon <[email-masked]>
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