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

2019-01-18 Thread Assaf Gordon

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






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

2018-08-16 Thread Mike Crowe
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.

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 one fails
cp -vd temp/src/self temp/dest/self
# This one works again
cp -vd temp/src/self temp/dest/self
-->8--

Output:

--8<--
+ rm -rf temp
+ mkdir -p temp/src temp/dest
+ ln -s . temp/src/self
+ cp -vd temp/src/self temp/dest/self
'temp/src/self' -> 'temp/dest/self'
+ cp -vd temp/src/self temp/dest/self
removed 'temp/dest/self/self'
'temp/src/self' -> 'temp/dest/self/self'
cp: cannot create symbolic link 'temp/dest/self/self': No such file or directory
+ cp -vd temp/src/self temp/dest/self
'temp/src/self' -> 'temp/dest/self'
-->8--

(coreutils 8.26, 8.28 and 8.30 all appear to behave the same way.)

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.

Or, maybe I'm fundamentally misunderstanding something. I couldn't find
this mentioned in the FAQ or "gotchas".

Thanks.

Mike.