The explanation at
http://man7.org/linux/man-pages/man2/open.2.html
about why openat
is needed, reads in part:
openat() allows an application to avoid race conditions that could occur when using open() to open files in directories other than the current working directory. These race conditions result from the fact that some component of the directory prefix given to open() could be changed in parallel with the call to open(). Suppose, for example, that we wish to create the file path/to/xxx.dep if the file path/to/xxx exists. The problem is that between the existence check and the file creation step, path or to (which might be symbolic links) could be modified to point to a different location.
I don't see why this race is a problem. If an app wants to check for the existence of some file and if so, create a different file, then, of course these are two steps, and the app either should ensure that nothing interferes in between, or accept the consequences of doing a two-step operation. Only if a single call to open()
could cause a race condition, might some other syscall, such as openat()
be needed. Otherwise, this is not for syscalls to solve, but it is an application's responsibility to deal with.
What am I not understanding here?
(1) Move the lock file outside the main loop, so that two instances of your program cannot run their main loop at the same time. If one is running, the other will have to wait until it's done, then start replacing the output file. (2) Make the two instances cooperate, by examining what the contents of the file are.
This means use the fchown( ), fstat( ), or fchmod( ) system calls, instead of the functions taking filenames such as chown(), chgrp(), and chmod(). Doing so will prevent the file from being replaced while your program is running (a possible race condition).
A race condition is an undesirable situation that occurs when a device or system attempts to perform two or more operations at the same time, but because of the nature of the device or system, the operations must be done in the proper sequence to be done correctly.
TL;DRopenat()
allows you to lock down an entire directory path, resolving the race condition only once, then safely open files relative to that path without worrying about race conditions.
Details
You are correct that the race condition is still the responsibility of your program to anticipate and handle, but the openat()
function allows you to only do it once for multiple files. If you want to open multiple files within the same directory, you could do it with individual calls to open()
, but you will have to expect and handle the race condition every time. Instead, with openat()
, you can grab a file descriptor to the parent directory first, which will prevent other processes from modifying or removing that path. You can now use openat()
to open multiple files relative to that locked path safely, without worrying about the race conditions that opening absolute paths normally entails.
Other Use Case
Also note that the race condition isn't necessarily between your program opening files and other programs changing or deleting paths - it's also between threads within your program. openat()
is useful when you want to work with relative paths and are in a multithreaded environment. Remember that if you change your working directory in one thread, it changes it for the entire process and its threads.
So when you fork off multiple threads, they can each grab a file descriptor for different directories, and use openat()
with those directory file descriptors and relative paths from them to open files, without worrying about what the other threads are doing with the whole processes' working directory, or maintaining a load of absolute paths.
This use case is described by the second note in the man page for openat
:
Second, openat() allows the implementation of a per-thread "current working directory", via file descriptor(s) maintained by the application. (This functionality can also be obtained by tricks based on the use of /proc/self/fd/dirfd, but less efficiently.
Sidenote
I don't want to get too deep into a discussion of what responsibilities should be assumed by system calls because it will get subjective, but note that openat
doesn't really resolve race conditions - you can still have one when you try to lock down the parent directory, and that's entirely your program's responsibility to handle. It is a tool to help you prevent race conditions, after you've achieved resolution once. I think that's a useful and reasonable mechanism to include in an OS as a system call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With