Search code examples
linuxunixrace-condition

How to rename() without race conditions?


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?


Solution

  • As of Linux kernel 3.15 (released in June 2014), this can be done with syscall(__NR_renameat2, AT_FDCWD, "source-file", AT_FDCWD, "dest-file", RENAME_NOREPLACE) (include <syscall.h>, <fcntl.h> and <linux/fs.h>).

    This is better than link(), because there is never a point where both filenames exist simultaneously (in particular, with link(), a precisely-timed power outage could cause both names to remain forever).

    glibc 2.28 (released in August 2018) adds a renameat2() wrapper, so you can use that instead of syscall.h and linux/fs.h (though you'll most likely need <stdio.h> and #define __GNU_SOURCE instead).

    For more details, see http://man7.org/linux/man-pages/man2/rename.2.html (though it does, as of writing, not know that glibc now has a renameat2 wrapper).