Skip to content

Commit e74ec4d

Browse files
authored
Do not use file::path() in file::copy (#174)
This is the next step to removing the `path()` method of the temporary file (#165): we now can create a temporary copy without using the copy result path
1 parent 80f0d49 commit e74ec4d

File tree

3 files changed

+125
-5
lines changed

3 files changed

+125
-5
lines changed

include/tmp/file

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public:
5454

5555
/// Creates a unique temporary copy from the given path
5656
/// @note std::ios::in | std::ios::out are always added to the `mode`
57+
/// @note after creating a copy, its I/O position indicator points to the end
5758
/// @param path A path to make a temporary copy from
5859
/// @param mode Specifies stream open mode
5960
/// @returns The new temporary file

src/file.cpp

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "create.hpp"
55
#include "move.hpp"
66

7+
#include <array>
78
#include <filesystem>
89
#include <fstream>
910
#include <ios>
@@ -12,7 +13,111 @@
1213
#include <system_error>
1314
#include <utility>
1415

16+
#ifdef _WIN32
17+
#define NOMINMAX
18+
#define UNICODE
19+
#include <Windows.h>
20+
#else
21+
#include <cerrno>
22+
#include <fcntl.h>
23+
#include <unistd.h>
24+
#endif
25+
1526
namespace tmp {
27+
namespace {
28+
29+
/// A block size for file reading
30+
/// @note should always be less than INT_MAX
31+
constexpr std::size_t block_size = 4096;
32+
33+
/// A type of buffer for I/O file operations
34+
using buffer_type = std::array<std::byte, block_size>;
35+
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
39+
/// @returns A handle to the open file
40+
file::native_handle_type open(const fs::path& path,
41+
std::error_code& ec) noexcept {
42+
ec.clear();
43+
44+
#ifdef _WIN32
45+
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);
49+
if (handle == INVALID_HANDLE_VALUE) {
50+
ec = std::error_code(GetLastError(), std::system_category());
51+
}
52+
#else
53+
int handle = ::open(path.c_str(), O_RDONLY | O_NONBLOCK);
54+
if (handle == -1) {
55+
ec = std::error_code(errno, std::system_category());
56+
}
57+
#endif
58+
59+
return handle;
60+
}
61+
62+
/// Closes the given handle, ignoring any errors
63+
/// @param[in] handle The handle to close
64+
void close(file::native_handle_type handle) noexcept {
65+
#ifdef _WIN32
66+
CloseHandle(handle);
67+
#else
68+
::close(handle);
69+
#endif
70+
}
71+
72+
/// Copies a file contents from the given path to the given file descriptor
73+
/// @param[in] from The path to the source file
74+
/// @param[in] to The target file descriptor
75+
/// @param[out] ec Parameter for error reporting
76+
void copy_file(const fs::path& from, file::native_handle_type to,
77+
std::error_code& ec) noexcept {
78+
// 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+
84+
buffer_type buffer = buffer_type();
85+
while (true) {
86+
#ifdef _WIN32
87+
DWORD bytes_read;
88+
if (!ReadFile(source, buffer.data(), block_size, &bytes_read, nullptr)) {
89+
ec = std::error_code(GetLastError(), std::system_category());
90+
break;
91+
}
92+
#else
93+
ssize_t bytes_read = read(source, buffer.data(), block_size);
94+
if (bytes_read < 0) {
95+
ec = std::error_code(errno, std::system_category());
96+
break;
97+
}
98+
#endif
99+
if (bytes_read == 0) {
100+
break;
101+
}
102+
103+
#ifdef _WIN32
104+
DWORD written;
105+
if (!WriteFile(to, buffer.data(), bytes_read, &written, nullptr)) {
106+
ec = std::error_code(GetLastError(), std::system_category());
107+
return;
108+
}
109+
#else
110+
ssize_t written = write(to, buffer.data(), bytes_read);
111+
if (written < 0) {
112+
ec = std::error_code(errno, std::system_category());
113+
break;
114+
}
115+
#endif
116+
}
117+
118+
close(source);
119+
}
120+
} // namespace
16121

17122
file::file(std::pair<fs::path, filebuf> handle) noexcept
18123
: entry(std::move(handle.first)),
@@ -26,7 +131,7 @@ file file::copy(const fs::path& path, std::ios::openmode mode) {
26131
file tmpfile = file(mode);
27132

28133
std::error_code ec;
29-
fs::copy_file(path, tmpfile, fs::copy_options::overwrite_existing, ec);
134+
copy_file(path, tmpfile.native_handle(), ec);
30135

31136
if (ec) {
32137
throw fs::filesystem_error("Cannot create a temporary copy", path, ec);

tests/file.cpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,29 @@ TEST(file, copy_file) {
108108

109109
EXPECT_TRUE(fs::is_regular_file(tmpfile));
110110

111+
// Test get file pointer position after copying
112+
std::streampos gstreampos = copy.tellg();
113+
copy.seekg(0, std::ios::end);
114+
EXPECT_EQ(gstreampos, copy.tellg());
115+
116+
// Test put file pointer position after copying
117+
std::streampos pstreampos = copy.tellp();
118+
copy.seekp(0, std::ios::end);
119+
EXPECT_EQ(pstreampos, copy.tellp());
120+
121+
// Test file copy contents
111122
std::ifstream stream = std::ifstream(copy.path());
112123
std::string content = std::string(std::istreambuf_iterator(stream), {});
113124
EXPECT_EQ(content, "Hello, world!");
114125
}
115126

116-
/// Tests creation of a temporary copy of a directory
117-
TEST(file, copy_directory) {
118-
directory tmpdir = directory();
119-
EXPECT_THROW(file::copy(tmpdir), fs::filesystem_error);
127+
/// Tests creation of copy errors
128+
TEST(file, copy_errors) {
129+
// `file::copy` cannot copy a directory
130+
EXPECT_THROW(file::copy(directory()), fs::filesystem_error);
131+
132+
// `file::copy` cannot copy a non-existent file
133+
EXPECT_THROW(file::copy("nonexistent.txt"), fs::filesystem_error);
120134
}
121135

122136
/// Tests that moving a temporary file to itself does nothing

0 commit comments

Comments
 (0)