Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/tmp/file
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public:

/// Creates a unique temporary copy from the given path
/// @note std::ios::in | std::ios::out are always added to the `mode`
/// @note after creating a copy, its I/O position indicator points to the end
/// @param path A path to make a temporary copy from
/// @param mode Specifies stream open mode
/// @returns The new temporary file
Expand Down
107 changes: 106 additions & 1 deletion src/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "create.hpp"
#include "move.hpp"

#include <array>
#include <filesystem>
#include <fstream>
#include <ios>
Expand All @@ -12,7 +13,111 @@
#include <system_error>
#include <utility>

#ifdef _WIN32
#define NOMINMAX
#define UNICODE
#include <Windows.h>
#else
#include <cerrno>
#include <fcntl.h>
#include <unistd.h>
#endif

namespace tmp {
namespace {

/// A block size for file reading
/// @note should always be less than INT_MAX
constexpr std::size_t block_size = 4096;

/// A type of buffer for I/O file operations
using buffer_type = std::array<std::byte, block_size>;

/// Opens a file in read-only mode
/// @param[in] path The path to the file to open
/// @param[out] ec Parameter for error reporting
/// @returns A handle to the open file
file::native_handle_type open(const fs::path& path,
std::error_code& ec) noexcept {
ec.clear();

#ifdef _WIN32
HANDLE handle =
CreateFile(path.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (handle == INVALID_HANDLE_VALUE) {
ec = std::error_code(GetLastError(), std::system_category());
}
#else
int handle = ::open(path.c_str(), O_RDONLY | O_NONBLOCK);
if (handle == -1) {
ec = std::error_code(errno, std::system_category());
}
#endif

return handle;
}

/// Closes the given handle, ignoring any errors
/// @param[in] handle The handle to close
void close(file::native_handle_type handle) noexcept {
#ifdef _WIN32
CloseHandle(handle);
#else
::close(handle);
#endif
}

/// Copies a file contents from the given path to the given file descriptor
/// @param[in] from The path to the source file
/// @param[in] to The target file descriptor
/// @param[out] ec Parameter for error reporting
void copy_file(const fs::path& from, file::native_handle_type to,
std::error_code& ec) noexcept {
// TODO: can be optimized using `sendfile`, `copyfile` or other system API
file::native_handle_type source = open(from, ec);
if (ec) {
return;
}

buffer_type buffer = buffer_type();
while (true) {
#ifdef _WIN32
DWORD bytes_read;
if (!ReadFile(source, buffer.data(), block_size, &bytes_read, nullptr)) {
ec = std::error_code(GetLastError(), std::system_category());
break;
}
#else
ssize_t bytes_read = read(source, buffer.data(), block_size);
if (bytes_read < 0) {
ec = std::error_code(errno, std::system_category());
break;
}
#endif
if (bytes_read == 0) {
break;
}

#ifdef _WIN32
DWORD written;
if (!WriteFile(to, buffer.data(), bytes_read, &written, nullptr)) {
ec = std::error_code(GetLastError(), std::system_category());
return;
}
#else
ssize_t written = write(to, buffer.data(), bytes_read);
if (written < 0) {
ec = std::error_code(errno, std::system_category());
break;
}
#endif
}

close(source);
}
} // namespace

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

std::error_code ec;
fs::copy_file(path, tmpfile, fs::copy_options::overwrite_existing, ec);
copy_file(path, tmpfile.native_handle(), ec);

if (ec) {
throw fs::filesystem_error("Cannot create a temporary copy", path, ec);
Expand Down
22 changes: 18 additions & 4 deletions tests/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,29 @@ TEST(file, copy_file) {

EXPECT_TRUE(fs::is_regular_file(tmpfile));

// Test get file pointer position after copying
std::streampos gstreampos = copy.tellg();
copy.seekg(0, std::ios::end);
EXPECT_EQ(gstreampos, copy.tellg());

// Test put file pointer position after copying
std::streampos pstreampos = copy.tellp();
copy.seekp(0, std::ios::end);
EXPECT_EQ(pstreampos, copy.tellp());

// Test file copy contents
std::ifstream stream = std::ifstream(copy.path());
std::string content = std::string(std::istreambuf_iterator(stream), {});
EXPECT_EQ(content, "Hello, world!");
}

/// Tests creation of a temporary copy of a directory
TEST(file, copy_directory) {
directory tmpdir = directory();
EXPECT_THROW(file::copy(tmpdir), fs::filesystem_error);
/// Tests creation of copy errors
TEST(file, copy_errors) {
// `file::copy` cannot copy a directory
EXPECT_THROW(file::copy(directory()), fs::filesystem_error);

// `file::copy` cannot copy a non-existent file
EXPECT_THROW(file::copy("nonexistent.txt"), fs::filesystem_error);
}

/// Tests that moving a temporary file to itself does nothing
Expand Down