GNU bug report logs -
#25342
ln: avoid race condition with "ln -f src dst"
Previous Next
To reply to this bug, email your comments to 25342 AT debbugs.gnu.org.
Toggle the display of automated, internal messages from the tracker.
Report forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Mon, 02 Jan 2017 23:12:01 GMT)
Full text and
rfc822 format available.
Acknowledgement sent
to
Mirsad Goran Todorovac <mtodorov3_69 <at> yahoo.com>
:
New bug report received and forwarded. Copy sent to
bug-coreutils <at> gnu.org
.
(Mon, 02 Jan 2017 23:12:01 GMT)
Full text and
rfc822 format available.
Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
When "dest" is existing, ln -f source dest does an attempt of linkat(AT_FCWDFD, source, AT_FCWDFD, dest), which fails with EEXIST if dest exists, and then attempts an unlink(dest), followed by repeated linkat().
Between unlink() and second linkat() process maybe preempted, leaving no system file.
An option -o (--force-overwrite) is proposed, so existing scripts would work w/o breaking, and linkat() can be made atomic. By rights, there oughta be an option for linkat() to do it, but right now it was done by spurious idea (Thanking the grace of Lord Merciful and Longsuffering).
Patch follows.
All the best,Mirsad X. Todorovac
__________________________________________________________________________________________________
marvin <at> marvin-desktop:~$ diff -r -U 3 coreutils-8.26 coreutils-8.26p1
diff -r -U 3 coreutils-8.26/src/ln.c coreutils-8.26p1/src/ln.c
--- coreutils-8.26/src/ln.c 2016-11-14 13:05:20.000000000 +0100
+++ coreutils-8.26p1/src/ln.c 2017-01-02 22:23:53.608194338 +0100
@@ -59,6 +59,9 @@
/* If true, remove existing files unconditionally. */
static bool remove_existing_files;
+/* If true, remove existing files unconditionally, even if losing data. */
+static bool force_remove_existing_files;
+
/* If true, list each file as it is moved. */
static bool verbose;
@@ -90,6 +93,7 @@
{"no-dereference", no_argument, NULL, 'n'},
{"no-target-directory", no_argument, NULL, 'T'},
{"force", no_argument, NULL, 'f'},
+ {"force-overwrite", no_argument, NULL, 'o'},
{"interactive", no_argument, NULL, 'i'},
{"suffix", required_argument, NULL, 'S'},
{"target-directory", required_argument, NULL, 't'},
@@ -283,7 +287,8 @@
if (backup_type != no_backups)
{
dest_backup = find_backup_file_name (dest, backup_type);
- if (rename (dest, dest_backup) != 0)
+ if (logical && rename (dest, dest_backup) != 0 ||
+ !logical && link (dest, dest_backup) != 0)
{
int rename_errno = errno;
free (dest_backup);
@@ -301,10 +306,31 @@
if (relative)
source = rel_source = convert_abs_rel (source, dest);
- ok = ((symbolic_link ? symlink (source, dest)
+ if (logical || !force_remove_existing_files)
+ ok = ((symbolic_link ? symlink (source, dest)
: linkat (AT_FDCWD, source, AT_FDCWD, dest,
logical ? AT_SYMLINK_FOLLOW : 0))
== 0);
+ else {
+ char *tmpname = NULL;
+
+ if (force_remove_existing_files &&
+ asprintf (&tmpname, "%s.%06d.bak", source, (int)getpid()) < strlen(source) + 1 + 6 + 4) {
+ int asprintf_errno = errno;
+ if (tmpname)
+ free (tmpname);
+ free (dest_backup);
+ free (rel_source);
+ error (0, asprintf_errno, _("cannot backup %s: cannot allocate temp string"),
+ quoteaf (dest));
+ return false;
+ }
+
+ ok = ((symbolic_link ? symlink (source, dest)
+ : linkat (AT_FDCWD, source, AT_FDCWD, tmpname, 0) == 0
+ && renameat (AT_FDCWD, tmpname, AT_FDCWD, dest) == 0));
+ }
+
/* If the attempt to create a link failed and we are removing or
backing up destinations, unlink the destination and try again.
@@ -417,6 +443,7 @@
directories (note: will probably fail due to\n\
system restrictions, even for the superuser)\n\
-f, --force remove existing destination files\n\
+ -o, --overwrite force remove existing destination files\n\
"), stdout);
fputs (_("\
-i, --interactive prompt whether to remove destinations\n\
@@ -467,10 +494,10 @@
atexit (close_stdin);
- symbolic_link = remove_existing_files = interactive = verbose
+ symbolic_link = force_remove_existing_files = remove_existing_files = interactive = verbose
= hard_dir_link = false;
- while ((c = getopt_long (argc, argv, "bdfinrst:vFLPS:T", long_options, NULL))
+ while ((c = getopt_long (argc, argv, "bdfinorst:vFLPS:T", long_options, NULL))
!= -1)
{
switch (c)
@@ -484,6 +511,8 @@
case 'F':
hard_dir_link = true;
break;
+ case 'o':
+ force_remove_existing_files = true;
case 'f':
remove_existing_files = true;
interactive = false;
marvin <at> marvin-desktop:~$
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Tue, 03 Jan 2017 01:09:01 GMT)
Full text and
rfc822 format available.
Message #8 received at 25342 <at> debbugs.gnu.org (full text, mbox):
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.
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Tue, 03 Jan 2017 03:34:02 GMT)
Full text and
rfc822 format available.
Message #11 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
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 <eggert <at> cs.ucla.edu> 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.
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Tue, 03 Jan 2017 05:39:01 GMT)
Full text and
rfc822 format available.
Message #14 received at 25342 <at> debbugs.gnu.org (full text, mbox):
Mirsad Goran Todorovac wrote:
> 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;
It isn't that simple, since renameat is a no-op if the source and destination
are already hard links. So the patch you sent in would not work.
Anyway, I'm not disagreeing with the basic idea, I'm merely saying I don't see
why we should have an option to keep the old behavior. Anybody who really wants
the old behavior can do 'rm -f new && ln old new' instead.
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Wed, 04 Jan 2017 21:03:01 GMT)
Full text and
rfc822 format available.
Message #17 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
I did not understand this no-op behavior. It is unlike what my Ubuntu kernel experienced. I may later try to see what 4.x kernels do ...
Please consider the trace below. Thank you.
M.T.
marvin <at> marvin-desktop:~$ uname -a
Linux marvin-desktop 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 athlon i686 GNU/Linux
marvin <at> marvin-desktop:~$
marvin <at> marvin-desktop:~$ rm -f copy copy2 copy2~ ; cp /etc/resolv.conf.orig copy ; cp /etc/resolv.conf copy2
marvin <at> marvin-desktop:~$ cat copy copy2
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1
search lan
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 194.146.109.223
nameserver 194.146.109.224
search lan
marvin <at> marvin-desktop:~$ strace coreutils-devel/bin/ln -b -fo copy copy2
execve("coreutils-devel/bin/ln", ["coreutils-devel/bin/ln", "-b", "-fo", "copy", "copy2"], [/* 65 vars */]) = 0
brk(NULL) = 0xa036000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7745000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=127411, ...}) = 0
mmap2(NULL, 127411, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7725000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\320\207\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1786484, ...}) = 0
mmap2(NULL, 1792540, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb756f000
mprotect(0xb771e000, 4096, PROT_NONE) = 0
mmap2(0xb771f000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1af000) = 0xb771f000
mmap2(0xb7722000, 10780, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb7722000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb756e000
set_thread_area({entry_number:-1, base_addr:0xb756e940, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 (entry_number:6)
mprotect(0xb771f000, 8192, PROT_READ) = 0
mprotect(0x8058000, 4096, PROT_READ) = 0
mprotect(0xb776a000, 4096, PROT_READ) = 0
munmap(0xb7725000, 127411) = 0
brk(NULL) = 0xa036000
brk(0xa057000) = 0xa057000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=4229984, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb736e000
mmap2(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0x2d4000) = 0xb7744000
close(3) = 0
stat64("copy2", {st_mode=S_IFREG|0644, st_size=216, ...}) = 0
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2995, ...}) = 0
read(3, "# Locale name alias data base.\n#"..., 4096) = 2995
read(3, "", 4096) = 0
close(3) = 0
open("/home/marvin/coreutils-devel/share/locale/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/home/marvin/coreutils-devel/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=619, ...}) = 0
mmap2(NULL, 619, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7743000
close(3) = 0
lstat64("copy", {st_mode=S_IFREG|0644, st_size=183, ...}) = 0
lstat64("copy2", {st_mode=S_IFREG|0644, st_size=216, ...}) = 0
open(".", O_RDONLY|O_NONBLOCK|O_LARGEFILE|O_DIRECTORY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFDIR|0755, st_size=12288, ...}) = 0
getdents64(3, /* 152 entries */, 32768) = 5808
getdents64(3, /* 0 entries */, 32768) = 0
close(3) = 0
link("copy2", "copy2~") = 0
getpid() = 3085
linkat(AT_FDCWD, "copy", AT_FDCWD, "copy.003085.bak", 0) = 0
renameat(AT_FDCWD, "copy.003085.bak", AT_FDCWD, "copy2") = 0
_llseek(0, 0, 0xbf9087a8, SEEK_CUR) = -1 ESPIPE (Illegal seek)
close(0) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
marvin <at> marvin-desktop:~$
marvin <at> marvin-desktop:~$ cat copy copy2 copy2~
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1
search lan
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 127.0.1.1
search lan
# Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
# DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
nameserver 194.146.109.223
nameserver 194.146.109.224
search lan
marvin <at> marvin-desktop:~$
On Tuesday, January 3, 2017 6:38 AM, Paul Eggert <eggert <at> cs.ucla.edu> wrote:
Mirsad Goran Todorovac wrote:
> 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;
It isn't that simple, since renameat is a no-op if the source and destination
are already hard links. So the patch you sent in would not work.
Anyway, I'm not disagreeing with the basic idea, I'm merely saying I don't see
why we should have an option to keep the old behavior. Anybody who really wants
the old behavior can do 'rm -f new && ln old new' instead.
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Thu, 05 Jan 2017 07:58:01 GMT)
Full text and
rfc822 format available.
Message #20 received at 25342 <at> debbugs.gnu.org (full text, mbox):
Mirsad Goran Todorovac wrote:
> Please consider the trace below.
As I don't know what you're tracing, I don't know what to consider.
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Thu, 05 Jan 2017 08:52:02 GMT)
Full text and
rfc822 format available.
Message #23 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
Pity, 'cause it works for me with a small workaround:link("copy2", "copy2~") = 0
getpid() = 3085
linkat(AT_FDCWD, "copy", AT_FDCWD, "copy.003085.bak", 0) = 0
renameat(AT_FDCWD, "copy.003085.bak", AT_FDCWD, "copy2") = 0
I did not exactly understand? What else can be moved, if not two hard links?Directory entry can be one of pointer to inode (file, directory or less likely something else), or pointer to another directory entry (symbolic link), ain't that right?
Can you please provide me with a reference of such behavior of renameat()? I realized just now that you want to make your code universal and protable.On Ubuntu Linux it works for me.
marvin <at> marvin-desktop:~$ uname -a
Linux marvin-desktop 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 athlon i686 GNU/Linux
marvin <at> marvin-desktop:~$ dpkg -l | egrep 'libc6'
ii libc6:i386 2.23-0ubuntu5 i386 GNU C
If you can provide me with a reference, I'll try to understand renameat() problem.
Best of luck in New Year,M.T.
On Thursday, January 5, 2017 8:57 AM, Paul Eggert <eggert <at> cs.ucla.edu> wrote:
Mirsad Goran Todorovac wrote:
> Please consider the trace below.
As I don't know what you're tracing, I don't know what to consider.
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Thu, 05 Jan 2017 09:25:02 GMT)
Full text and
rfc822 format available.
Message #26 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
The trace above should do a ln -b -f src dest, linking (hard linking to existing file providing a backup in "dest~", without at suffix and stuff, which is not important for the story):
EXAMPLE WITH BACKUP: ln -b -f src dest
initially we have: "src" -> inode1 "dest" -> inode2
link ("dest", "dest~");
now: "src" -> inode1
"dest" -> inode2 "dest~" -> inode2
link ("src", "src.######.bak"); now: "src" -> inode1 "src.######.bak" -> inode1 "dest" -> inode2 "dest~" -> inode2
rename ("src.######.bak", "dest"); finally: "src" -> inode1 "dest" -> inode1 "dest~" -> inode2
Result is that "new" is backed up in "new~" "simulaneously", that is in single command while hard link "old" overwrites hard link "new".
The -b (backup) is optional, in which case we have only black parts, as follows:
WITHOUT BACKUP: ln -f src dest
initially we have: "src" -> inode1 "dest" -> inode2
link ("src", "src.######.bak"); now: "src" -> inode1 "src.######.bak" -> inode1 "src" -> inode2rename ("src.######.bak", "dest"); finally: "src" -> inode1 "dest" -> inode1
Where initial content of file "dest" was lost.Of course, it would be much better if linkat() had additional flag:
int linkat (AT_CWDFD, "source", AT_CWDFD, "dest", AT_SYMLINK_FOLLOW | AT_FORCE_OVERWRITE);
but as you have correctly said, we cannot influence POSIX and Linux kernel makers.
Sorry if I took too much of your time. Thank Heavens for such good and logical design of UN*X filesystems.
Regards and All the best,M.T.
_______________________________________________________________________________________
On Thursday, January 5, 2017 9:50 AM, Mirsad Goran Todorovac <mtodorov3_69 <at> yahoo.com> wrote:
Pity, 'cause it works for me with a small workaround:link("copy2", "copy2~") = 0
getpid() = 3085
linkat(AT_FDCWD, "copy", AT_FDCWD, "copy.003085.bak", 0) = 0
renameat(AT_FDCWD, "copy.003085.bak", AT_FDCWD, "copy2") = 0
I did not exactly understand? What else can be moved, if not two hard links?Directory entry can be one of pointer to inode (file, directory or less likely something else), or pointer to another directory entry (symbolic link), ain't that right?
Can you please provide me with a reference of such behavior of renameat()? I realized just now that you want to make your code universal and protable.On Ubuntu Linux it works for me.
marvin <at> marvin-desktop:~$ uname -a
Linux marvin-desktop 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 athlon i686 GNU/Linux
marvin <at> marvin-desktop:~$ dpkg -l | egrep 'libc6'
ii libc6:i386 2.23-0ubuntu5 i386 GNU C
If you can provide me with a reference, I'll try to understand renameat() problem.
Best of luck in New Year,M.T.
On Thursday, January 5, 2017 8:57 AM, Paul Eggert <eggert <at> cs.ucla.edu> wrote:
Mirsad Goran Todorovac wrote:
> Please consider the trace below.
As I don't know what you're tracing, I don't know what to consider.
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Thu, 05 Jan 2017 09:31:02 GMT)
Full text and
rfc822 format available.
Message #29 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
P.S.
Obvious advantage is that unlink ("dest") is not required if it exists, thus we offer a workaround for race condition.In first example:
# ln -f /etc/resolv.conf.new /etc/resolv.conf
there was a time period between
unlink ("/etc/resolv.conf"); andlinkat (AT_CDWFD, "/etc/resolv.conf.new", AT_CWDFD, "/etc/resolv.conf", logical ? AT_SYMLINK_FOLLOW : 0);
between these two syscalls process or thread may be preempted and other process (like browser) could fail in gethostbyname().This is just one example.
I will torment you no longer. Thank you for your patience Sir if you've read thus far. God bless!
little brother Mirsad
On Thursday, January 5, 2017 10:24 AM, Mirsad Goran Todorovac <mtodorov3_69 <at> yahoo.com> wrote:
The trace above should do a ln -b -f src dest, linking (hard linking to existing file providing a backup in "dest~", without at suffix and stuff, which is not important for the story):
EXAMPLE WITH BACKUP: ln -b -f src dest
initially we have: "src" -> inode1 "dest" -> inode2
link ("dest", "dest~");
now: "src" -> inode1
"dest" -> inode2 "dest~" -> inode2
link ("src", "src.######.bak"); now: "src" -> inode1 "src.######.bak" -> inode1 "dest" -> inode2 "dest~" -> inode2
rename ("src.######.bak", "dest"); finally: "src" -> inode1 "dest" -> inode1 "dest~" -> inode2
Result is that "new" is backed up in "new~" "simulaneously", that is in single command while hard link "old" overwrites hard link "new".
The -b (backup) is optional, in which case we have only black parts, as follows:
WITHOUT BACKUP: ln -f src dest
initially we have: "src" -> inode1 "dest" -> inode2
link ("src", "src.######.bak"); now: "src" -> inode1 "src.######.bak" -> inode1 "src" -> inode2rename ("src.######.bak", "dest"); finally: "src" -> inode1 "dest" -> inode1
Where initial content of file "dest" was lost.Of course, it would be much better if linkat() had additional flag:
int linkat (AT_CWDFD, "source", AT_CWDFD, "dest", AT_SYMLINK_FOLLOW | AT_FORCE_OVERWRITE);
but as you have correctly said, we cannot influence POSIX and Linux kernel makers.
Sorry if I took too much of your time. Thank Heavens for such good and logical design of UN*X filesystems.
Regards and All the best,M.T.
_______________________________________________________________________________________
On Thursday, January 5, 2017 9:50 AM, Mirsad Goran Todorovac <mtodorov3_69 <at> yahoo.com> wrote:
Pity, 'cause it works for me with a small workaround:link("copy2", "copy2~") = 0
getpid() = 3085
linkat(AT_FDCWD, "copy", AT_FDCWD, "copy.003085.bak", 0) = 0
renameat(AT_FDCWD, "copy.003085.bak", AT_FDCWD, "copy2") = 0
I did not exactly understand? What else can be moved, if not two hard links?Directory entry can be one of pointer to inode (file, directory or less likely something else), or pointer to another directory entry (symbolic link), ain't that right?
Can you please provide me with a reference of such behavior of renameat()? I realized just now that you want to make your code universal and protable.On Ubuntu Linux it works for me.
marvin <at> marvin-desktop:~$ uname -a
Linux marvin-desktop 3.5.0-34-generic #55-Ubuntu SMP Thu Jun 6 20:20:19 UTC 2013 i686 athlon i686 GNU/Linux
marvin <at> marvin-desktop:~$ dpkg -l | egrep 'libc6'
ii libc6:i386 2.23-0ubuntu5 i386 GNU C
If you can provide me with a reference, I'll try to understand renameat() problem.
Best of luck in New Year,M.T.
On Thursday, January 5, 2017 8:57 AM, Paul Eggert <eggert <at> cs.ucla.edu> wrote:
Mirsad Goran Todorovac wrote:
> Please consider the trace below.
As I don't know what you're tracing, I don't know what to consider.
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Sun, 08 Jan 2017 00:27:01 GMT)
Full text and
rfc822 format available.
Message #32 received at 25342 <at> debbugs.gnu.org (full text, mbox):
[Message part 1 (text/plain, inline)]
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
It isn't that simple, since renameat is a no-op if the source and destination
are already hard links. So the patch you sent in would not work.
Dear Sir,
I might have then misinterpreted following paragraph fromman renameat(2) man page:
DESCRIPTION
rename() renames a file, moving it between directories if required. Any other hard
links to the file (as created using link(2)) are unaffected. Open file descriptors for
oldpath are also unaffected.
If newpath already exists, it will be atomically replaced (subject to a few conditions;
see ERRORS below), so that there is no point at which another process attempting to
access newpath will find it missing.
If oldpath and newpath are existing hard links referring to the same file, then
rename() does nothing, and returns a success status.
In my test code, renamat (AT_CWDFD, oldpath, AT_CWDFD, newpath, flags); successfully renames directory entry, yet inode number name in dir refers to is unchanged.
Do you think our development team could write a paper on this issue? Then I could justify drawing a schematic diagram?Is it trivial or is there some justified reason to publish and explain in detail?
Thank you very much.
Regards,
M.T.
On Thursday, January 5, 2017 8:57 AM, Paul Eggert <eggert <at> cs.ucla.edu> wrote:
Mirsad Goran Todorovac wrote:
> Please consider the trace below.
As I don't know what you're tracing, I don't know what to consider.
But really, the basic idea is simple: renameat mishandles the case where old and
new names are already hard links, and any code based on renameat needs to work
around this problem. (We can't easily change renameat's behavior, as the
behavior is required by POSIX.)
[Message part 2 (text/html, inline)]
Information forwarded
to
bug-coreutils <at> gnu.org
:
bug#25342
; Package
coreutils
.
(Sun, 08 Jan 2017 00:30:02 GMT)
Full text and
rfc822 format available.
Message #35 received at 25342 <at> debbugs.gnu.org (full text, mbox):
Mirsad Goran Todorovac wrote:
> Do you think our development team could write a paper on this issue? Then I could justify drawing a schematic diagram?Is it trivial or is there some justified reason to publish and explain in detail?
Sorry, I don't know what "development team" you're talking about. I don't see a
need to write a paper about it, no. It's just code that needs to be written and
tested, that's all.
Severity set to 'wishlist' from 'normal'
Request was from
Assaf Gordon <assafgordon <at> gmail.com>
to
control <at> debbugs.gnu.org
.
(Sun, 28 Oct 2018 07:48:02 GMT)
Full text and
rfc822 format available.
Changed bug title to 'ln: avoid race condition with "ln -f src dst"' from 'GNU coreutils: race condition in "ln -f source dest"'
Request was from
Assaf Gordon <assafgordon <at> gmail.com>
to
control <at> debbugs.gnu.org
.
(Sun, 28 Oct 2018 07:48:02 GMT)
Full text and
rfc822 format available.
This bug report was last modified 6 years and 234 days ago.
Previous Next
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.