Skip to content

Commit 720157b

Browse files
authored
Breaking: Remove openmode parameter from file construction (#218)
According to C99 Rationale, unnamed temporary files are expected to be written first and then read as transparently as possible, so they are created in binary update mode > ### 7.19.4.3 The tmpfile function > The tmpfile function is intended to allow users to create binary “scratch” files. The as if principle implies that the information in such a file need never actually be stored on a file-structured device. > The temporary file is created in binary update mode because it will presumably be first written and then read as transparently as possible. Trailing null-character padding may cause problems for some existing programs. https://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf I don't see any point in implementing `tmp::file` in any other way, given that we haven't found a practical use for text or append modes in production for over a year now
1 parent 111e739 commit 720157b

File tree

4 files changed

+23
-145
lines changed

4 files changed

+23
-145
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ auto func() {
5151

5252
### Temporary files
5353

54-
`tmp::file` is a smart handle that manages a temporary file, ensuring its deletion when the handle goes out of scope. Upon creation, a `tmp::file` object generates a unique temporary file in the current user's temporary directory, opening it for both reading and writing.
54+
`tmp::file` is a smart handle that manages a binary temporary file, ensuring its deletion when the handle goes out of scope. Upon creation, a `tmp::file` object generates a unique temporary file, opening it for both reading and writing in binary format.
5555

5656
The temporary file is deleted of when either of the following happens:
5757
- the `tmp::file` object is destroyed
@@ -65,7 +65,7 @@ The example below demonstrates a usage of a `tmp::file` object to validate a req
6565
#include <tmp/file>
6666

6767
auto func(std::string_view content) {
68-
auto tmpfile = tmp::file(std::ios::binary);
68+
auto tmpfile = tmp::file();
6969
tmpfile << contents << std::flush;
7070
if (validate(tmpfile)) {
7171
// Unarchive the file to the persistent storage

include/tmp/file

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313

1414
namespace tmp {
1515

16-
/// tmp::file is a smart handle that manages a temporary file, ensuring
16+
/// tmp::file is a smart handle that manages a binary temporary file, ensuring
1717
/// its deletion when the handle goes out of scope
1818
///
19-
/// Upon creation, a tmp::file object generates a unique temporary file
20-
/// in the current user's temporary directory, opening it for both
21-
/// reading and writing
19+
/// Upon creation, a tmp::file object generates a unique temporary file,
20+
/// opening it for reading and writing in binary format
2221
///
2322
/// The temporary file is deleted of when either of the following happens:
2423
/// - the tmp::file object is destroyed
@@ -35,7 +34,7 @@ namespace tmp {
3534
/// #include <tmp/file>
3635
///
3736
/// auto func(std::string_view content) {
38-
/// auto tmpfile = tmp::file(std::ios::binary);
37+
/// auto tmpfile = tmp::file();
3938
/// tmpfile << contents << std::flush;
4039
/// if (validate(tmpfile)) {
4140
/// // Unarchive the file to the persistent storage
@@ -57,12 +56,9 @@ public:
5756
#error "Target platform not supported"
5857
#endif
5958

60-
/// Creates a unique file in the current user's temporary directory
61-
/// and opens it with `mode | std::ios::in | std::ios::out`
62-
/// @param mode The file opening mode
59+
/// Creates and opens a binary temporary file as if by POSIX `tmpfile`
6360
/// @throws std::filesystem::filesystem_error if cannot create a file
64-
/// @throws std::invalid_argument if the given open mode is invalid
65-
explicit file(std::ios::openmode mode = std::ios::in | std::ios::out);
61+
explicit file();
6662

6763
/// Returns an implementation-defined handle to this file
6864
/// @returns The underlying implementation-defined handle

src/file.cpp

Lines changed: 13 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ namespace {
2525

2626
namespace fs = std::filesystem;
2727

28+
#ifndef _MSC_VER
29+
/// Open mode for binary temporary files
30+
constexpr auto mode = std::ios::binary | std::ios::in | std::ios::out;
31+
#endif
32+
2833
/// Returns an implementation-defined handle to the file
2934
/// @param[in] file The file to the native handle for
3035
/// @returns The underlying implementation-defined handle
@@ -36,118 +41,23 @@ file::native_handle_type get_native_handle(std::FILE* file) noexcept {
3641
#endif
3742
}
3843

39-
/// Makes a mode string for opening a temporary file
40-
/// @param[in] mode The file opening mode
41-
/// @returns A suitable mode string
42-
const char* make_mdstring(std::ios::openmode mode) noexcept {
43-
// Special case: the C++ standard forbids `app` and `trunc` at the same time
44-
if ((mode & std::ios::app) != 0 && (mode & std::ios::trunc) != 0) {
45-
return nullptr;
46-
}
47-
48-
// - `std::ios::in` and `std::ios::out` are always applied
49-
// - `std::ios::trunc` has no effect on the empty file
50-
// - `std::ios::noreplace` has no effect for temporary files
51-
// - any other platform dependent flag is not supported
52-
unsigned filtered = mode & (std::ios::app | std::ios::binary);
53-
54-
switch (filtered) {
55-
case 0:
56-
#ifdef _WIN32
57-
return "w+TD";
58-
#else
59-
return "w+";
60-
#endif
61-
case std::ios::app:
62-
#ifdef _WIN32
63-
return "a+TD";
64-
#else
65-
return "a+";
66-
#endif
67-
case std::ios::binary:
68-
#ifdef _WIN32
69-
return "w+bTD";
70-
#else
71-
return "w+b";
72-
#endif
73-
case std::ios::app | std::ios::binary:
74-
#ifdef _WIN32
75-
return "a+bTD";
76-
#else
77-
return "a+b";
78-
#endif
79-
default:
80-
return nullptr;
81-
}
82-
}
83-
84-
/// Reopens the given temporary file with the given open mode
85-
/// @param[in] mdstring The temporary file opening mode
86-
/// @param[out] file The file to reopen
87-
/// @param[out] ec Parameter for error reporting
88-
void reopen_file(const char* mdstring, std::FILE* file,
89-
std::error_code& ec) noexcept {
90-
ec.clear();
91-
92-
#ifdef _WIN32
93-
HANDLE handle = get_native_handle(file);
94-
95-
std::string path;
96-
path.resize(MAX_PATH);
97-
DWORD ret = GetFinalPathNameByHandleA(handle, path.data(), MAX_PATH, 0);
98-
if (ret == 0) {
99-
ec.assign(GetLastError(), std::system_category());
100-
return;
101-
}
102-
103-
path.resize(ret);
104-
105-
// FIXME: freopen loses shared access rules
106-
file = freopen(path.c_str(), mdstring, file);
107-
if (file == nullptr) {
108-
ec.assign(errno, std::generic_category());
109-
}
110-
#else
111-
file = std::freopen(nullptr, mdstring, file);
112-
if (file == nullptr) {
113-
ec.assign(errno, std::generic_category());
114-
}
115-
#endif
116-
}
117-
118-
/// Creates and opens a temporary file in the current user's temporary directory
119-
/// @param[in] mode The file opening mode
120-
/// @returns A handle to the created temporary file
44+
/// Creates and opens a binary temporary file as if by POSIX `tmpfile`
45+
/// @returns A pointer to the file stream associated with the temporary file
12146
/// @throws fs::filesystem_error if cannot create a temporary file
122-
/// @throws std::invalid_argument if the given openmode is invalid
123-
std::FILE* create_file(std::ios::openmode mode) {
124-
const char* mdstring = make_mdstring(mode);
125-
if (mdstring == nullptr) {
126-
throw std::invalid_argument(
127-
"Cannot create a temporary file: invalid openmode");
128-
}
129-
130-
std::error_code ec;
47+
std::FILE* create_file() {
13148
std::FILE* file = std::tmpfile();
13249
if (file == nullptr) {
133-
ec.assign(errno, std::generic_category());
134-
} else {
135-
reopen_file(mdstring, file, ec);
136-
}
137-
138-
if (ec) {
50+
std::error_code ec = std::error_code(errno, std::generic_category());
13951
throw fs::filesystem_error("Cannot create a temporary file", ec);
14052
}
14153

14254
return file;
14355
}
14456
} // namespace
14557

146-
file::file(std::ios::openmode mode)
58+
file::file()
14759
: std::iostream(std::addressof(sb)),
148-
underlying(create_file(mode), &std::fclose) {
149-
mode |= std::ios::in | std::ios::out;
150-
60+
underlying(create_file(), &std::fclose) {
15161
#if defined(_MSC_VER)
15262
sb = std::filebuf(underlying.get());
15363
#elif defined(_LIBCPP_VERSION)
@@ -157,8 +67,8 @@ file::file(std::ios::openmode mode)
15767
#endif
15868

15969
if (!sb.is_open()) {
160-
throw std::invalid_argument(
161-
"Cannot create a temporary file: invalid openmode");
70+
std::error_code ec = std::make_error_code(std::io_errc::stream);
71+
throw fs::filesystem_error("Cannot create a temporary file", ec);
16272
}
16373
}
16474

tests/file.cpp

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,8 @@ TEST(file, create_multiple) {
6868
EXPECT_NE(fst.native_handle(), snd.native_handle());
6969
}
7070

71-
/// Tests error handling with invalid open mode
72-
TEST(file, create_invalid_openmode) {
73-
// C++ standard forbids opening a filebuf with `trunc | app`
74-
EXPECT_THROW(file(std::ios::trunc | std::ios::app), std::invalid_argument);
75-
}
76-
77-
/// Tests file default open mode
78-
TEST(file, openmode_default) {
71+
/// Tests file open mode
72+
TEST(file, openmode) {
7973
file tmpfile = file();
8074
tmpfile << "Hello, World!" << std::flush;
8175
tmpfile.seekg(0);
@@ -86,28 +80,6 @@ TEST(file, openmode_default) {
8680
EXPECT_EQ(content, "Goodbye!orld!");
8781
}
8882

89-
/// Tests file `app` open mode
90-
TEST(file, openmode_append) {
91-
file tmpfile = file(std::ios::app);
92-
tmpfile << "Hello, World!" << std::flush;
93-
tmpfile.seekg(0);
94-
tmpfile << "Goodbye!" << std::flush;
95-
96-
tmpfile.seekg(0);
97-
std::string content = std::string(std::istreambuf_iterator(tmpfile), {});
98-
EXPECT_EQ(content, "Hello, World!Goodbye!");
99-
}
100-
101-
/// Tests that file adds std::ios::in and std::ios::out flags
102-
TEST(file, ios_flags) {
103-
file tmpfile = file(std::ios::binary);
104-
tmpfile << "Hello, world!" << std::flush;
105-
106-
tmpfile.seekg(0, std::ios::beg);
107-
std::string content = std::string(std::istreambuf_iterator(tmpfile), {});
108-
EXPECT_EQ(content, "Hello, world!");
109-
}
110-
11183
/// Tests that destructor removes a file
11284
TEST(file, destructor) {
11385
file::native_handle_type handle;

0 commit comments

Comments
 (0)