Subject: Re: bug#8419: cp -au : New hard links in source becomes new files at destination when using cp -au

From: Pádraig Brady <[email-masked]>
Date: Tue, 26 Jul 2011 14:43:30 +0100

On 26/07/11 14:23, Jim Meyering wrote:
> Pádraig Brady wrote:
> ...
>>>> The link-creation code there cannot be removed.
>>>> Consider this scenario:
>>>>
>>>>   src/{a,b}  # a and b are linked
>>>>   dst/src/a
>>>>
>>>> If cp -au encounters src/a first, then the new "remember_copied"
>>>> call records the required info so when it encounters src/b it can
>>>> make the desired link from the preexisting dst/src/a to dst/src/b.
>>>> In that case, the link-creation code is indeed unnecessary.
>>>>
>>>> However, what if it encounters src/b first?
>>>> In that case, it must copy src/b to dst/src/b (new inode!)
>>>> Then, when it encounters src/a, it must *remove* the preexisting dst/src/a
>>>
>>> This "must" is my interpretation that --preserve=link (implied
>>> by -a and -p) has a higher priority than the --update (-u) option.
>>
>> That's a bit surprising, thought understandable I think.
>> If the separate file in the dest is newer it will be replaced
>> by an older link from the source.  That could legitimately happen
>> I suppose if the original copy was with -rp, then the timestamps
>> would be newer than those in dest.
>> I'll update the test to enforce the replacement of newer files in dest
>>
>>>> and replace it with a hard link to dst/src/b.
>>>> As an alternative, cp could conceivably remove the just-copied dst/src/b,
>>>> replacing it with a hard-link to dst/src/a.
>>>>
>>>> The trouble I have with all of this is that we can see two different
>>>> outcomes, depending on the order in which "a" and "b" (in the src
>>>> directory) are processed by cp.  In one case, the preexisting and
>>>> up-to-date dst/src/a is not modified.  In the other, it is removed,
>>>> and replaced by a hard-link to potentially-differing-content "dst/src/b".
>>
>> TBH I don't understand all the implications.
>> Note the `touch` commands in the test operate on the separate inodes in dest.
> 
> Oh!  Sorry I missed that.
> 
>> Note also your original test didn't fail for me on ext4 on F15.
> 
> When I apply the change below, recompile "cp" and run
>   make check -C tests TESTS=cp/preserve-link VERBOSE=yes
> with the test tweaked to run cp via strace,
>   strace -e stat,lstat -o /tmp/cp-strace cp -au s t || fail=1
> I get this, which shows it's processing s/link before s/f.
> 
>   stat("t", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
>   lstat("s", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
>   lstat("t/s", {st_mode=S_IFDIR|0700, st_size=4096, ...}) = 0
>   lstat("s/link", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
>   stat("t/s/link", 0x7fffa85ca0a0)        = -1 ENOENT (No such file or directory)
>   lstat("s/f", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0
>   stat("t/s/f", {st_mode=S_IFREG|0600, st_size=0, ...}) = 0

confirmed.

stat("t", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
lstat("s", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
lstat("t/s", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
lstat("s/f", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
stat("t/s/f", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
lstat("s/link", {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
stat("t/s/link", 0x7fff731e22c0)        = -1 ENOENT (No such file or directory)
stat("s", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
stat("s", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
stat("t/s", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0

cheers,
Pádraig.