Subject: bug#69532: mv's new -x option should be made orthogonal to -t/-T/default

From: Paul Eggert <[email-masked]>
Date: Wed, 20 Mar 2024 15:10:26 -0700

On 3/17/24 04:32, Pádraig Brady wrote:
> I think the --no-copy situation is brittle, as scripts not using it now
> would be atomic, but then if we ever supported cross fs swaps
> it may become non atomic. I'd at least doc with a line in the --exchange
> description in usage() to say something like:
>    "Use --no-copy to enforce atomic operation"

But --no-copy doesn't mean atomic operation; it simply means don't copy.

On systems that don't have an atomic exchange, we can emulate "mv 
--exchange --no-copy a b" with three calls link("b", "b.tmp"); 
rename("a","b"); rename("b.tmp","a"). This wouldn't copy, but it 
wouldn't be atomic.

Although atomicity is important, currently the coreutils documentation 
doesn't cover it and the code doesn't always handle it well. For 
example, if A and B are regular files "mv -b A B" briefly has a moment 
when B stops existing. To my mind this is a bug: an existing destination 
shouldn't stop existing merely because you're replacing it. At some 
point this stuff should be documented better and this (and probably some 
other) atomicity bugs fixed.

One thought I had while looking into this was that we could add an 
--atomic option to mv and ln, such that the command fails if the 
destination cannot be updated atomically. That would be a stronger 
option than --no-copy. (In some cases we could support --atomic even for 
'cp', no?)

Anyway, for now I installed the patch with some minor changes to the 
documentation's --exchange section to try to document this tricky area 
more clearly. Here's the revised doc section. It also incorporates your 
later suggestion to mention both data and metadata.

----

@item --exchange
@opindex --exchange
Exchange source and destination instead of renaming source to destination.
Both files must exist; they need not be the same type.
This exchanges all data and metadata.

This option can be used to replace one directory with another.
When used this way, it should be combined with
@code{--no-target-directory} (@option{-T})
to avoid confusion about the destination location.
For example, you might use @samp{mv -T --exchange @var{d1} @var{d2}}
to exchange two directories @var{d1} and @var{d2}.

Exchanges are atomic if the source and destination are both in a
single file system that supports atomic exchange.
Non-atomic exchanges are not yet supported.

If the source and destination might not be on the same file system,
using @code{--no-copy} will prevent future versions of @command{mv}
from implementing the exchange by copying.