Skip to content

Commit b42186d

Browse files
authored
Do not use file::path() in file::move (#175)
This is the next step to removing the path() method of the temporary file (#165): now we can move a temporary file without using its path Technically, it always copies the file at this point, since I couldn't figure out how to hardlink a file without hardlinks
1 parent e74ec4d commit b42186d

File tree

2 files changed

+50
-52
lines changed

2 files changed

+50
-52
lines changed

src/file.cpp

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,29 @@ constexpr std::size_t block_size = 4096;
3333
/// A type of buffer for I/O file operations
3434
using buffer_type = std::array<std::byte, block_size>;
3535

36-
/// Opens a file in read-only mode
37-
/// @param[in] path The path to the file to open
38-
/// @param[out] ec Parameter for error reporting
36+
/// Opens a file and returns its handle
37+
/// @param[in] path The path to the file to open
38+
/// @param[in] readonly Whether to open file only for reading
39+
/// @param[out] ec Parameter for error reporting
3940
/// @returns A handle to the open file
40-
file::native_handle_type open(const fs::path& path,
41+
file::native_handle_type open(const fs::path& path, bool readonly,
4142
std::error_code& ec) noexcept {
4243
ec.clear();
4344

4445
#ifdef _WIN32
46+
DWORD access = readonly ? GENERIC_READ : GENERIC_WRITE;
47+
DWORD creation_disposition = readonly ? OPEN_EXISTING : CREATE_ALWAYS;
48+
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
49+
4550
HANDLE handle =
46-
CreateFile(path.c_str(), GENERIC_READ,
47-
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
48-
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
51+
CreateFile(path.c_str(), access, share_mode, nullptr,
52+
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
4953
if (handle == INVALID_HANDLE_VALUE) {
5054
ec = std::error_code(GetLastError(), std::system_category());
5155
}
5256
#else
53-
int handle = ::open(path.c_str(), O_RDONLY | O_NONBLOCK);
57+
int oflag = readonly ? O_RDONLY | O_NONBLOCK : O_RDWR | O_TRUNC | O_CREAT;
58+
int handle = ::open(path.c_str(), oflag);
5459
if (handle == -1) {
5560
ec = std::error_code(errno, std::system_category());
5661
}
@@ -69,28 +74,23 @@ void close(file::native_handle_type handle) noexcept {
6974
#endif
7075
}
7176

72-
/// Copies a file contents from the given path to the given file descriptor
73-
/// @param[in] from The path to the source file
77+
/// Copies file contents from one file descriptor to another
78+
/// @param[in] from The source file descriptor
7479
/// @param[in] to The target file descriptor
7580
/// @param[out] ec Parameter for error reporting
76-
void copy_file(const fs::path& from, file::native_handle_type to,
81+
void copy_file(file::native_handle_type from, file::native_handle_type to,
7782
std::error_code& ec) noexcept {
7883
// TODO: can be optimized using `sendfile`, `copyfile` or other system API
79-
file::native_handle_type source = open(from, ec);
80-
if (ec) {
81-
return;
82-
}
83-
8484
buffer_type buffer = buffer_type();
8585
while (true) {
8686
#ifdef _WIN32
8787
DWORD bytes_read;
88-
if (!ReadFile(source, buffer.data(), block_size, &bytes_read, nullptr)) {
88+
if (!ReadFile(from, buffer.data(), block_size, &bytes_read, nullptr)) {
8989
ec = std::error_code(GetLastError(), std::system_category());
9090
break;
9191
}
9292
#else
93-
ssize_t bytes_read = read(source, buffer.data(), block_size);
93+
ssize_t bytes_read = read(from, buffer.data(), block_size);
9494
if (bytes_read < 0) {
9595
ec = std::error_code(errno, std::system_category());
9696
break;
@@ -114,8 +114,32 @@ void copy_file(const fs::path& from, file::native_handle_type to,
114114
}
115115
#endif
116116
}
117+
}
117118

118-
close(source);
119+
/// Copies file contents from the given path to the given file descriptor
120+
/// @param[in] from The path to the source file
121+
/// @param[in] to The target file descriptor
122+
/// @param[out] ec Parameter for error reporting
123+
void copy_file(const fs::path& from, file::native_handle_type to,
124+
std::error_code& ec) noexcept {
125+
file::native_handle_type source = open(from, /*readonly=*/true, ec);
126+
if (!ec) {
127+
copy_file(source, to, ec);
128+
close(source);
129+
}
130+
}
131+
132+
/// Copies file contents from the given path to the given file descriptor
133+
/// @param[in] from The source file descriptor
134+
/// @param[in] to The path to the target file
135+
/// @param[out] ec Parameter for error reporting
136+
void copy_file(file::native_handle_type from, const fs::path& to,
137+
std::error_code& ec) noexcept {
138+
file::native_handle_type target = open(to, /*readonly=*/false, ec);
139+
if (!ec) {
140+
copy_file(from, target, ec);
141+
close(target);
142+
}
119143
}
120144
} // namespace
121145

@@ -145,13 +169,16 @@ file::native_handle_type file::native_handle() const noexcept {
145169
}
146170

147171
void file::move(const fs::path& to) {
148-
sb.close();
172+
// TODO: I couldn't figure out how to create a hard link to a file without
173+
// hard links, so I just copy it even within the same file system
174+
175+
seekg(0, std::ios::beg);
149176

150177
std::error_code ec;
151-
tmp::move(*this, to, ec);
178+
copy_file(native_handle(), to, ec);
152179

153180
if (ec) {
154-
throw fs::filesystem_error("Cannot move a temporary file", path(), to, ec);
181+
throw fs::filesystem_error("Cannot move a temporary file", to, ec);
155182
}
156183

157184
entry::clear();

tests/file.cpp

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -133,49 +133,20 @@ TEST(file, copy_errors) {
133133
EXPECT_THROW(file::copy("nonexistent.txt"), fs::filesystem_error);
134134
}
135135

136-
/// Tests that moving a temporary file to itself does nothing
137-
TEST(file, move_to_self) {
138-
fs::path path;
139-
140-
{
141-
file tmpfile = file();
142-
tmpfile << "Hello, world!";
143-
144-
path = tmpfile;
145-
146-
tmpfile.move(tmpfile);
147-
}
148-
149-
EXPECT_TRUE(fs::exists(path));
150-
151-
{
152-
std::ifstream stream = std::ifstream(path);
153-
std::string content = std::string(std::istreambuf_iterator(stream), {});
154-
EXPECT_EQ(content, "Hello, world!");
155-
}
156-
157-
fs::remove_all(path);
158-
}
159-
160136
/// Tests moving a temporary file to existing non-directory file
161137
TEST(file, move_to_existing_file) {
162-
fs::path path;
163-
164138
fs::path to = fs::path(BUILD_DIR) / "move_file_to_existing_test";
165139
std::ofstream(to) << "Goodbye, world!";
166140

167141
{
168142
file tmpfile = file();
169-
tmpfile << "Hello, world!";
170-
171-
path = tmpfile;
143+
tmpfile << "Hello, world!" << std::flush;
172144

173145
tmpfile.move(to);
174146
}
175147

176148
std::error_code ec;
177149
EXPECT_TRUE(fs::exists(to, ec));
178-
EXPECT_FALSE(fs::exists(path, ec));
179150

180151
{
181152
std::ifstream stream = std::ifstream(to);

0 commit comments

Comments
 (0)