views:

82

answers:

3

If I want to rename A to B, but only if B doesn't exist, the naive thing would be checking if B exists (with access("B", F_OK) or something like that), and if it doesn't proceeding with rename. Unfortunately this opens a window during which some other process might decide to create B, and then it gets overwritten - and even worse there's no indication that something like that ever happened.

Other file system access functions don't suffer from this - open has O_EXCL (so copying files is safe), and recently Linux got an entire family of *at syscalls that protect against most other race conditions - but not this particular one (renameat exists, but protects against an entirely different problem).

So does it have a solution?

+1  A: 

From the rename man page:

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.

So it is not possible to avoid renaming when the B file already exists. I think perhaps you simply have no choice but to check for existance (use stat() not access() for that) before you attempt the rename, if you don't want the rename to occur if the file already exists. Ignoring a race condition.

Otherwise, the solution presented below with link() seems to fit your requirements.

Jerub
If the file B does not exist, there is still a race condition if you test for existence - both processes may detect it is not there, and then both do the rename, with indeterminate results as regards which process renamed last. The only solution is for rename() to be atomic and to fail if the new filename already exists, which is what it does on Windows, and what I thought (incorrectly it seems) the C Standard specified.
anon
+7  A: 

You should be able to link(2) to the new file name. If the link fails then you give up because the file already exists. If the link succeeds, your file now exists under both the old and the new name. Then you unlink(2) the old name. No possible race condition.

rettops
What is the files are under different mount points?
Moron
@Moron: rename() doesn't work across different file systems.
Dummy00001
@Dummy00001: I see. I missed the brackets in the title. +1 then :-)
Moron
+4  A: 

You could link() to the existing file with the new filename you want, then remove the existing filename.

link() should succeed in creating a new link only if the new pathname doesn't already exist.

Something like:

int result = link( "A", "B");

if (result != 0) {
    // the link wasn't created for some reason (maybe because "B" already existed)
    // handle the failure however appropriate...
    return -1;
}

// at this point there are 2 filenames hardlinked to the contents of "A", 
//   filename "A" and filename "B"

// remove filename "A"
unlink( "A");

This technique is discussed in the docs for link() (see the discussion about modifying the passwd file):

Michael Burr