N2357        Exclusive Access

Change Request for fopen exclusive access

Submitter: Nick Stoughton

Submission Date: 2019-03-15

Source: Austin Group

Reference Document:

Subject:Exclusive Access

Summary

C11 introduced a wx mode to the fopen function, where (7.21.5.3 p5)

Opening a file with exclusive mode ('x' as the last character in the mode argument) fails if the file already exists or cannot be created. Otherwise, the file is created with exclusive (also known as non-shared) access to the extent that the underlying system supports exclusive access.

The proposal for adding this ‘x’ flag was in n1339 and updated in n1358.  Between the two it gained the above “exclusive access” wording, adapted from fopen_s() in TR 24731-1.

In the process of drafting requirements for POSIX alignment with C17, the question has arisen as to how "to the extent that the underlying system supports exclusive access" should be interpreted when the underlying system is a POSIX implementation. All POSIX systems support a limited form of exclusion related to file access: file permissions. So was the intent that, in the absence of something better (which we'll come to), fopen() should set the permissions bits to zero so that no other opens can be done on the file (except from processes with appropriate privileges)? Apparently not, because the description of fopen_s() in Annex K talks about "exclusive access" and "a file permission that prevents other users on the system from accessing the file" as two different things.

Considering other forms of exclusion:

The C standard also doesn't say what exactly is to have access, to the exclusion of other things. If we take the narrowest interpretation then that thing is the calling thread. So should fopen() call flockfile() on the stream to prevent other threads from using it?

All of these things seem highly unlikely as being intended by the C committee.

So now we come to the "something better" alluded to earlier. That thing is mandatory file locking. On systems that support it, if fopen() enables it and sets a whole-file exclusive (write) lock on the file, then that would prevent others from reading or writing (or locking) any part of the file. This seems much more like the kind of exclusive access alluded to.

But there's a problem: if fopen() creates the file and sets a lock on the file as two separate operations, this would introduce a race condition whereby another process could open the file and write to it (or set a lock) in between those two operations.

Therefore it seems that on systems which support mandatory file locking (hereafter abbreviated as MFL), since enabling MFL and setting a whole-file write lock constitutes the extent to which these systems support exclusive access, fopen() must be able to set this up when it creates the file, in order to conform to C11/C17. And in order that it can do so without a race condition, this means that implementations which support MFL must also support a way of creating a file with a whole-file write lock set, as a single operation.

With wording the requirement the way it is, it would appear to be forcing POSIX implementations that support MFL to add this way of creating and locking a file. If it had been worded slightly differently, there would have been a loophole that these implementations could exploit. If the wording had been "... to the extent that the underlying system supports enabling exclusive access on file creation", then MFL (without a way of creating a file with a whole-file write lock set) would not satisfy this (because exclusive access can only be enabled after creation) and the only change implementations would need to make is adding the O_EXCL flag when the mode ends with "x".

Solaris 11.4 supports "x" in the fopen() mode argument, and supports MFL, but according to the fopen(3C) man page all the "x" does is cause fopen() to fail if the file exists; no mention of exclusive access. Apparently support was added for compatibility with other implementations, not in order to conform to C11. The current glibc also supports "x" in the fopen() mode argument, and Linux supports MFL. The glibc online manual entry for fopen() says that using "x" is equivalent to O_EXCL; no mention of exclusive access. However, glibc has a get-out here because Linux needs a mount option ("mand") to enable MFL on a file system. So glibc can just document that as part of setting up a conforming environment, Linux systems must not have any file systems mounted with the mand option.

The consensus among the POSIX standard developers is that when the “exclusive access” wording was written for fopen_s() in TR 24731-1, the author(s) probably had the behavior of MS Windows in mind, and did not intend it to relate to mandatory file locking on UNIX/POSIX systems. In that case, the current wording needs to change.

Possible Change

Change

Otherwise, the file is created with exclusive (also known as non-shared) access to the extent that the underlying system supports exclusive access.

to

Otherwise, the file is created with exclusive (also known as non-shared) access to the extent that the underlying system supports enabling exclusive access on file creation without setting the file permissions differently than when ‘x’ is not present in the mode argument, without setting any kind of lock on the file, and with the exclusivity being either to the executing program or to the returned stream.

OR

Change

Otherwise, the file is created with exclusive (also known as non-shared) access to the extent that the underlying system supports exclusive access.

To

Otherwise, the file is created with, optionally, an implementation defined form of exclusive (also known as non-shared) access enabled.