Skip to content

Commit a9370d1

Browse files
authored
Breaking: Remove file::path() method (#176)
This pr makes removes `file` inheritance from `entry` and removes file's access to its path. - On POSIX systems, the path is unlinked immediately after file creation - On Windows, the flag "delete on close" is used This replicates implementations of C `std::tmpfile`
1 parent b42186d commit a9370d1

File tree

6 files changed

+65
-106
lines changed

6 files changed

+65
-106
lines changed

include/tmp/file

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ namespace tmp {
4444
/// // `tmp::file` object goes out of scope and is destroyed
4545
/// }
4646
/// @endcode
47-
class TMP_EXPORT file : public entry, public std::iostream {
47+
class TMP_EXPORT file : public std::iostream {
4848
public:
4949
/// Creates a unique temporary file and opens it for reading and writing
5050
/// @note std::ios::in | std::ios::out are always added to the `mode`
@@ -87,15 +87,12 @@ public:
8787
private:
8888
/// The underlying raw file device object
8989
filebuf sb;
90-
91-
/// Creates a unique temporary file
92-
/// @param handle A path to the created temporary file and a handle to it
93-
explicit file(std::pair<std::filesystem::path, filebuf> handle) noexcept
94-
TMP_NO_EXPORT;
9590
};
9691
} // namespace tmp
9792

9893
/// The template specialization of `std::hash` for `tmp::file`
99-
template<> struct std::hash<tmp::file> : std::hash<tmp::entry> {};
94+
template<> struct TMP_EXPORT std::hash<tmp::file> {
95+
std::size_t operator()(const tmp::file& file) const noexcept;
96+
};
10097

10198
#endif // TMP_FILE_H

src/create.cpp

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <tmp/filebuf>
44

55
#include <filesystem>
6+
#include <iostream>
67
#include <stdexcept>
78
#include <string_view>
89
#include <system_error>
@@ -85,32 +86,32 @@ const wchar_t* make_mdstring(std::ios::openmode mode) noexcept {
8586
switch (mode & ~std::ios::ate) {
8687
case std::ios::out:
8788
case std::ios::out | std::ios::trunc:
88-
return L"wx";
89+
return L"wxD";
8990
case std::ios::out | std::ios::app:
9091
case std::ios::app:
91-
return L"a";
92+
return L"aD";
9293
case std::ios::in:
93-
return L"r";
94+
return L"rD";
9495
case std::ios::in | std::ios::out:
9596
case std::ios::in | std::ios::out | std::ios::trunc:
96-
return L"w+x";
97+
return L"w+xD";
9798
case std::ios::in | std::ios::out | std::ios::app:
9899
case std::ios::in | std::ios::app:
99-
return L"a+";
100+
return L"a+D";
100101
case std::ios::out | std::ios::binary:
101102
case std::ios::out | std::ios::trunc | std::ios::binary:
102-
return L"wbx";
103+
return L"wbxD";
103104
case std::ios::out | std::ios::app | std::ios::binary:
104105
case std::ios::app | std::ios::binary:
105-
return L"ab";
106+
return L"abD";
106107
case std::ios::in | std::ios::binary:
107-
return L"rb";
108+
return L"rbD";
108109
case std::ios::in | std::ios::out | std::ios::binary:
109110
case std::ios::in | std::ios::out | std::ios::trunc | std::ios::binary:
110-
return L"w+bx";
111+
return L"w+bxD";
111112
case std::ios::in | std::ios::out | std::ios::app | std::ios::binary:
112113
case std::ios::in | std::ios::app | std::ios::binary:
113-
return L"a+b";
114+
return L"a+bD";
114115
default:
115116
return nullptr;
116117
}
@@ -128,9 +129,9 @@ void close(filebuf::open_handle_type handle) noexcept {
128129
}
129130
} // namespace
130131

131-
std::pair<fs::path, filebuf> create_file(std::ios::openmode mode) {
132+
filebuf create_file(std::ios::openmode mode) {
132133
std::error_code ec;
133-
std::pair<fs::path, filebuf> file = create_file(mode, ec);
134+
filebuf file = create_file(mode, ec);
134135

135136
if (ec) {
136137
throw fs::filesystem_error("Cannot create a temporary file", ec);
@@ -139,8 +140,7 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode) {
139140
return file;
140141
}
141142

142-
std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
143-
std::error_code& ec) {
143+
filebuf create_file(std::ios::openmode mode, std::error_code& ec) {
144144
#ifdef _WIN32
145145
fs::path::string_type path = make_path("");
146146
#else
@@ -169,6 +169,8 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
169169
ec = std::error_code(errno, std::system_category());
170170
return {};
171171
}
172+
173+
unlink(path.c_str());
172174
#endif
173175

174176
filebuf filebuf;
@@ -180,7 +182,7 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
180182
}
181183

182184
ec.clear();
183-
return std::make_pair(path, std::move(filebuf));
185+
return filebuf;
184186
}
185187

186188
fs::path create_directory(std::string_view prefix) {

src/create.hpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,16 @@ namespace fs = std::filesystem;
1515
/// Creates a temporary file in the system's temporary directory,
1616
/// and opens it for reading and writing
1717
/// @param[in] mode Specifies stream open mode
18-
/// @returns A path to the created temporary file and a handle to it
18+
/// @returns A handle to the created temporary file
1919
/// @throws fs::filesystem_error if cannot create a temporary file
20-
std::pair<fs::path, filebuf> create_file(std::ios::openmode mode);
20+
filebuf create_file(std::ios::openmode mode);
2121

22-
/// Creates a temporary file in the system's temporary directory,
22+
/// Creates a temporary file in the system's temporary directory,
2323
/// and opens it for reading and writing
2424
/// @param[in] mode Specifies stream open mode
2525
/// @param[out] ec Parameter for error reporting
26-
/// @returns A path to the created temporary file and a handle to it
27-
std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
28-
std::error_code& ec);
26+
/// @returns A handle to the created temporary file
27+
filebuf create_file(std::ios::openmode mode, std::error_code& ec);
2928

3029
/// Creates a temporary directory with the given prefix in the system's
3130
/// temporary directory

src/file.cpp

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ file::native_handle_type open(const fs::path& path, bool readonly,
5454
ec = std::error_code(GetLastError(), std::system_category());
5555
}
5656
#else
57+
constexpr mode_t mode = 0644;
5758
int oflag = readonly ? O_RDONLY | O_NONBLOCK : O_RDWR | O_TRUNC | O_CREAT;
58-
int handle = ::open(path.c_str(), oflag);
59+
int handle = ::open(path.c_str(), oflag, mode);
5960
if (handle == -1) {
6061
ec = std::error_code(errno, std::system_category());
6162
}
@@ -143,13 +144,9 @@ void copy_file(file::native_handle_type from, const fs::path& to,
143144
}
144145
} // namespace
145146

146-
file::file(std::pair<fs::path, filebuf> handle) noexcept
147-
: entry(std::move(handle.first)),
148-
std::iostream(std::addressof(sb)),
149-
sb(std::move(handle.second)) {}
150-
151147
file::file(std::ios::openmode mode)
152-
: file(create_file(mode)) {}
148+
: std::iostream(std::addressof(sb)),
149+
sb(create_file(mode)) {}
153150

154151
file file::copy(const fs::path& path, std::ios::openmode mode) {
155152
file tmpfile = file(mode);
@@ -180,31 +177,22 @@ void file::move(const fs::path& to) {
180177
if (ec) {
181178
throw fs::filesystem_error("Cannot move a temporary file", to, ec);
182179
}
183-
184-
entry::clear();
185180
}
186181

187-
file::~file() noexcept {
188-
sb.close();
189-
}
182+
file::~file() noexcept = default;
190183

191184
// NOLINTBEGIN(*-use-after-move)
192185
file::file(file&& other) noexcept
193-
: entry(std::move(other)),
194-
std::iostream(std::move(other)),
186+
: std::iostream(std::move(other)),
195187
sb(std::move(other.sb)) {
196188
set_rdbuf(std::addressof(sb));
197189
}
190+
// NOLINTEND(*-use-after-move)
198191

199-
file& file::operator=(file&& other) {
200-
std::iostream::operator=(std::move(other));
201-
202-
// The stream buffer must be assigned first to close the file;
203-
// otherwise `entry` may not be able to remove the file before reassigning
204-
sb = std::move(other.sb);
205-
entry::operator=(std::move(other));
192+
file& file::operator=(file&& other) = default;
193+
} // namespace tmp
206194

207-
return *this;
195+
std::size_t
196+
std::hash<tmp::file>::operator()(const tmp::file& file) const noexcept {
197+
return std::hash<tmp::file::native_handle_type>()(file.native_handle());
208198
}
209-
// NOLINTEND(*-use-after-move)
210-
} // namespace tmp

tests/directory.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#include <tmp/directory>
2-
#include <tmp/file>
32

43
#include <gtest/gtest.h>
54

@@ -90,8 +89,8 @@ TEST(directory, copy_directory) {
9089

9190
/// Tests creation of a temporary copy of a file
9291
TEST(directory, copy_file) {
93-
file tmpfile = file();
94-
EXPECT_THROW(directory::copy(tmpfile), fs::filesystem_error);
92+
std::ofstream("existing.txt", std::ios::binary) << "Hello, world!";
93+
EXPECT_THROW(directory::copy("existing.txt"), fs::filesystem_error);
9594
}
9695

9796
/// Tests `operator/` of directory

tests/file.cpp

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -54,30 +54,18 @@ TEST(file, type_traits) {
5454
/// Tests file creation
5555
TEST(file, create) {
5656
file tmpfile = file();
57-
fs::path parent = tmpfile.path().parent_path();
58-
59-
EXPECT_TRUE(fs::exists(tmpfile));
60-
EXPECT_TRUE(fs::is_regular_file(tmpfile));
61-
EXPECT_TRUE(fs::equivalent(parent, fs::temp_directory_path()));
6257
EXPECT_TRUE(is_open(tmpfile));
6358
EXPECT_TRUE(is_open(tmpfile.native_handle()));
6459

65-
fs::perms permissions = fs::status(tmpfile).permissions();
66-
#ifdef _WIN32
67-
// GetTempFileNameW creates a file with all permissions
68-
EXPECT_EQ(permissions, fs::perms::all);
69-
#else
70-
// mkstemp creates a file that can only be read and written by the owner
71-
EXPECT_EQ(permissions, fs::perms::owner_read | fs::perms::owner_write);
72-
#endif
60+
// TODO: test that the file is on the same filesystem as temp_directory_path
7361
}
7462

7563
/// Tests multiple file creation
7664
TEST(file, create_multiple) {
7765
file fst = file();
7866
file snd = file();
7967

80-
EXPECT_FALSE(fs::equivalent(fst, snd));
68+
EXPECT_NE(fst.native_handle(), snd.native_handle());
8169
}
8270

8371
/// Tests error handling with invalid open mode
@@ -91,22 +79,22 @@ TEST(file, ios_flags) {
9179
file tmpfile = file(std::ios::binary);
9280
tmpfile << "Hello, world!" << std::flush;
9381

94-
std::ifstream stream = std::ifstream(tmpfile.path());
95-
std::string content = std::string(std::istreambuf_iterator(stream), {});
82+
tmpfile.seekg(0, std::ios::beg);
83+
std::string content = std::string(std::istreambuf_iterator(tmpfile), {});
9684
EXPECT_EQ(content, "Hello, world!");
9785
}
9886

9987
/// Tests creation of a temporary copy of a file
10088
TEST(file, copy_file) {
101-
file tmpfile = file();
102-
tmpfile << "Hello, world!" << std::flush;
89+
std::ofstream original = std::ofstream("existing.txt", std::ios::binary);
90+
original << "Hello, world!" << std::flush;
10391

104-
file copy = file::copy(tmpfile);
105-
EXPECT_TRUE(fs::exists(tmpfile));
106-
EXPECT_TRUE(fs::exists(copy));
107-
EXPECT_FALSE(fs::equivalent(tmpfile, copy));
92+
file copy = file::copy("existing.txt");
93+
EXPECT_TRUE(fs::exists("existing.txt"));
94+
EXPECT_TRUE(is_open(copy));
10895

109-
EXPECT_TRUE(fs::is_regular_file(tmpfile));
96+
// To test that we actually copy the original file
97+
original << "Goodbye, world!" << std::flush;
11098

11199
// Test get file pointer position after copying
112100
std::streampos gstreampos = copy.tellg();
@@ -119,8 +107,8 @@ TEST(file, copy_file) {
119107
EXPECT_EQ(pstreampos, copy.tellp());
120108

121109
// Test file copy contents
122-
std::ifstream stream = std::ifstream(copy.path());
123-
std::string content = std::string(std::istreambuf_iterator(stream), {});
110+
copy.seekg(0, std::ios::beg);
111+
std::string content = std::string(std::istreambuf_iterator(copy), {});
124112
EXPECT_EQ(content, "Hello, world!");
125113
}
126114

@@ -179,16 +167,13 @@ TEST(file, move_to_non_existing_directory) {
179167

180168
/// Tests that destructor removes a file
181169
TEST(file, destructor) {
182-
fs::path path;
183170
file::native_handle_type handle;
184171

185172
{
186173
file tmpfile = file();
187-
path = tmpfile;
188174
handle = tmpfile.native_handle();
189175
}
190176

191-
EXPECT_FALSE(fs::exists(path));
192177
EXPECT_FALSE(is_open(handle));
193178
}
194179

@@ -199,8 +184,6 @@ TEST(file, move_constructor) {
199184

200185
file snd = file(std::move(fst));
201186

202-
EXPECT_FALSE(snd.path().empty());
203-
EXPECT_TRUE(fs::exists(snd));
204187
EXPECT_TRUE(is_open(snd));
205188

206189
snd.seekg(0);
@@ -217,19 +200,17 @@ TEST(file, move_assignment) {
217200
file snd = file();
218201
snd << "Hello!";
219202

220-
fs::path path1 = fst;
221-
fs::path path2 = snd;
203+
file::native_handle_type fst_handle = fst.native_handle();
204+
file::native_handle_type snd_handle = snd.native_handle();
222205

223206
fst = std::move(snd);
224207

225-
EXPECT_FALSE(fs::exists(path1));
226-
EXPECT_TRUE(fs::exists(path2));
227-
228-
EXPECT_TRUE(fs::exists(fst));
229-
EXPECT_TRUE(fs::equivalent(fst, path2));
208+
EXPECT_FALSE(is_open(fst_handle));
209+
EXPECT_TRUE(is_open(snd_handle));
210+
EXPECT_EQ(fst.native_handle(), snd_handle);
230211
}
231212

232-
EXPECT_FALSE(fst.path().empty());
213+
EXPECT_TRUE(is_open(fst));
233214

234215
fst.seekg(0);
235216
std::string content;
@@ -242,13 +223,13 @@ TEST(file, swap) {
242223
file fst = file();
243224
file snd = file();
244225

245-
fs::path fst_path = fst.path();
246-
fs::path snd_path = snd.path();
226+
file::native_handle_type fst_handle = fst.native_handle();
227+
file::native_handle_type snd_handle = snd.native_handle();
247228

248229
std::swap(fst, snd);
249230

250-
EXPECT_EQ(fst.path(), snd_path);
251-
EXPECT_EQ(snd.path(), fst_path);
231+
EXPECT_EQ(fst.native_handle(), snd_handle);
232+
EXPECT_EQ(snd.native_handle(), fst_handle);
252233
EXPECT_TRUE(is_open(fst));
253234
EXPECT_TRUE(is_open(snd));
254235
}
@@ -258,15 +239,8 @@ TEST(file, hash) {
258239
file tmpfile = file();
259240
std::hash hash = std::hash<file>();
260241

261-
EXPECT_EQ(hash(tmpfile), fs::hash_value(tmpfile.path()));
262-
}
263-
264-
/// Tests file relational operators
265-
TEST(file, relational) {
266-
file tmpfile = file();
267-
268-
EXPECT_TRUE(tmpfile == tmpfile);
269-
EXPECT_FALSE(tmpfile < tmpfile);
242+
EXPECT_EQ(hash(tmpfile),
243+
std::hash<file::native_handle_type>()(tmpfile.native_handle()));
270244
}
271245
} // namespace
272246
} // namespace tmp

0 commit comments

Comments
 (0)