diff options
author | Jim Meyering <meyering@redhat.com> | 2012-05-04 16:42:31 +0200 |
---|---|---|
committer | Jim Meyering <meyering@redhat.com> | 2012-05-07 13:39:48 +0200 |
commit | ee9e43460f366406edff96b5abfb3ff33587e062 (patch) | |
tree | 7451920817eb2c42bcc4aee062074f46e2f9d041 /src/copy.c | |
parent | 3468d26884800d7bc6fcc1ba52cd481175c655d8 (diff) | |
download | coreutils-ee9e43460f366406edff96b5abfb3ff33587e062.tar.gz coreutils-ee9e43460f366406edff96b5abfb3ff33587e062.tar.bz2 coreutils-ee9e43460f366406edff96b5abfb3ff33587e062.zip |
cp: handle a race condition more sensibly
* src/copy.c (copy_reg): In a narrow race (stat sees dest, yet
open-without-O_CREAT fails with ENOENT), retry the open with O_CREAT.
* tests/cp/nfs-removal-race: New file.
* tests/Makefile.am (TESTS): Add it.
* NEWS (Bug fixes): Mention it.
Reported by Philipp Thomas and Neil F. Brown in
http://bugs.gnu.org/11100
Diffstat (limited to 'src/copy.c')
-rw-r--r-- | src/copy.c | 19 |
1 files changed, 19 insertions, 0 deletions
diff --git a/src/copy.c b/src/copy.c index 844ebcd78..2558fea14 100644 --- a/src/copy.c +++ b/src/copy.c @@ -892,6 +892,8 @@ copy_reg (char const *src_name, char const *dst_name, if (*new_dst) { + open_with_O_CREAT:; + int open_flags = O_WRONLY | O_CREAT | O_BINARY; dest_desc = open (dst_name, open_flags | O_EXCL, dst_mode & ~omitted_permissions); @@ -942,6 +944,23 @@ copy_reg (char const *src_name, char const *dst_name, if (dest_desc < 0) { + /* If we've just failed due to ENOENT for an ostensibly preexisting + destination (*new_dst was 0), that's a bit of a contradiction/race: + the prior stat/lstat said the file existed (*new_dst was 0), yet + the subsequent open-existing-file failed with ENOENT. With NFS, + the race window is wider still, since its meta-data caching tends + to make the stat succeed for a just-removed remote file, while the + more-definitive initial open call will fail with ENOENT. When this + situation arises, we attempt to open again, but this time with + O_CREAT. Do this only when not in move-mode, since when handling + a cross-device move, we must never open an existing destination. */ + if (dest_errno == ENOENT && ! *new_dst && ! x->move_mode) + { + *new_dst = 1; + goto open_with_O_CREAT; + } + + /* Otherwise, it's an error. */ error (0, dest_errno, _("cannot create regular file %s"), quote (dst_name)); return_val = false; |