Subject: bug#25342: GNU coreutils: race condition in "ln -f source dest"

From: Mirsad Goran Todorovac <[email-masked]>
Date: Tue, 3 Jan 2017 03:32:57 +0000 (UTC)

Dear Sir,
It would suffice that:
linkat (AT_CWDFD, source, AT_CWDFD, dest, 0) == 0 || unlink (dest) == 0 && linkat (AT_CWDFD, source, AT_CWDFD, dest, 0) == 0;
be replaced with:
linkat (AT_CWDFD, source, AT_CWDFD, dest_tmp, 0) == 0 && renameat (AT_CWDFD, dest_tmp, AT_CWDFD, dest) == 0;
. This is exact equivalent of "ln -f A B.tmp; mv B.tmp B", true, as it had been cited, the rest is generating unique filename (MT safety is not yet done) and installing -o (--force-overwrite) option to command line.
Alternate behavior could be that default is to replace existing file atomically by default, and option is required or already existing to no-clobber.
If I do, for example:
# ln -f /etc/resolv.conf /etc/resolv.conf.orig# ln -f /etc/resolv.conf.ISP /etc/resolv.conf
there is a window in which ln comand can be preempted, and other processes trying i.e. gethostbyname() may or will fail (unless they have cached result).
This is equivalent to workaround with mv you provided above, but I think unlink() variant involves a race condition. And if I may assert - what is then forced? If desired behavior is to unlink() old existing target, then race condition involved is clearly a bug. IMHO.

I hope I am not too arrogant in these observations. And thank you very much for your time.

Regards,MT

    On Tuesday, January 3, 2017 2:07 AM, Paul Eggert <[email-masked]> wrote:

 Although I like the idea of changing "ln -f A B" to do the equivalent of "ln -f 
A B.tmp; mv B.tmp B" when B already exists, I'm not sure I'd want to have a new 
option to restore the old behavior, as that would add complexity for little real 
benefit.

Also, this behavior shouldn't be limited to ln; it should also be done by cp -l.