| Document number | P2467R1 |
| Date | 2022-01-28 |
| Audience | LWG |
| Reply-to | Jonathan Wakely <cxx@kayari.org> |
Changes since R0
noreplace to existing lists.Historically, C++ iostreams libraries had anoreplace open mode thatcorresponded to theO_EXCL flag for POSIXopen.That mode was not included in the C++98 standard,presumably for portability reasons, because it wasn't in ISO C90.
Since then, ISO C added support for "exclusive" mode tofopen,so now C++'s<fstream> is missing a feature that is present in bothISO C and POSIX. We should fix this for C++23.
C11 added an 'x' modifier to thefopen flags for files opened in write mode.This opens the file in "exclusive" mode, meaning thefopen call fails if thefile already exists.This is quite an important feature for certain use cases.As theWG14 N1339proposal explained,"This is necessary to eliminatea time-of-creation to time-of-use race condition vulnerability. [...]fopen() does not indicate if an existing file has been opened for writingor a new file has been created.This may lead to a program overwriting or accessing an unintended file."See N1339 for additional rationale.
C++ already incorporates the C11 changes tofopen by reference,butstd::fstream has no way to achieve the same thing.To avoid the time-of-creation to time-of-use (TOCTTOU) problemit's necessary to usefopen andFILE*(or non-standard APIs to hand an existing file handle or file descriptorto an fstream).
The 'x' modifier is widely supported. It was already supported as an extension by Glibc'sfopen(since at least version 2.4 from 2006),and is in the draft for the next revision of POSIX(because it's rebasing on C11).
Support for opening an ofstream in exclusive mode isn't even a new idea,pre-ISO iostreams provided it. References to anoreplace flag can befound in texts such as:
The flag is still present in the MSVC library, asios_base::_Noreplace,and in the Apache stdcxx implementation, asios_base::noreplace.
The historical name was "noreplace" but we could consider something likeios_base::excl to correspond more closely with POSIX and C. I originallypreferred that, but have since decided that it's better to be consistentwith the historicalnoreplace name, which is still present asios_base::_Noreplace in MSVC. I think that the meaning of "noreplace"is a bit more intuitable than "exclusive". If you don't already know whatPOSIX or C means by "exclusive mode" then the name doesn't help you.
We could also consider not using anios_base::openmode for this,but just add new constructors tobasic_filebuf,basic_ofstream etc.to request the file be opened in exclusive mode. This would be novel,as all existing options for opening files (such as binary mode) are doneviaopenmode flags. There was no interest in doing it any differently whenthe idea was discussed on the LEWG reflector.
Niall Douglas raised a concern related to the ISO C specification forfopen,which is vague about what "exclusive" mode means, and allows it to be ignored.I feel we should not deviate from C, so any fixes should be done "upstream"in the C standard. That makes it simpler to implement the C++ feature in termsoffopen, rather than having to do use OS-specific APIs.
This is relative to theN4901working draft.
Add a feature test macro to [version.syn]:
#define __cpp_lib_ios_noreplace YYYYDDL // also in <ios>Add a newopenmode constant to theios_base synopsis in [ios.base.general]as indicated:
// [ios.openmode], openmode using openmode = T3; static constexpr openmode app = unspecified; static constexpr openmode ate = unspecified; static constexpr openmode binary = unspecified; static constexpr openmode in = unspecified;static constexpr openmode noreplace = unspecified; static constexpr openmode out = unspecified; static constexpr openmode trunc = unspecified;Add a new row to Table 123 [tag:ios.openmode]:
Element Effect(s) if set appseek to end before each write ateopen and seek to end immediately after opening binaryperform input and output in binary mode (as opposed to text mode) inopen for input noreplaceopen in exclusive mode outopen for output trunctruncate an existing stream when opening
Add a new column and several new rows to Table 130 [tab:filebuf.open.modes],as indicated:
binaryinouttruncappnoreplace+ "w" + + "wx" + + "w" + + + "wx" + + "a" + "a" + "r" + + "r+" + + + "w+" + + + + "w+x" + + + "a+" + + "a+" + + "wb" + + + "wbx" + + + "wb" + + + + "wbx" + + + "ab" + + "ab" + + "rb" + + + "r+b" + + + + "w+b" + + + + + "w+bx" + + + + "a+b" + + + "a+b"