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