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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The temporary file is deleted of when either of the following happens:

`tmp::file` inherits from the `std::iostream` class, allowing it to be used seamlessly with standard input/output operations and simplifying file handling while maintaining the flexibility of stream operations.

The example below demonstrates a usage of a tmp::file object to validate a request content and then move it to persistent storage; note that after the `move()` call, the temporary file will not be deleted, as its ownership is transferred to the specified location:
The example below demonstrates a usage of a `tmp::file` object to validate a request content and then unarchive it to persistent storage:

```cpp
#include <tmp/file>
Expand All @@ -68,8 +68,8 @@ auto func(std::string_view content) {
auto tmpfile = tmp::file(std::ios::binary);
tmpfile << contents << std::flush;
if (validate(tmpfile)) {
// Save the file to the persistent storage
tmpfile.move(storage / "new_file");
// Unarchive the file to the persistent storage
archive::unzip(tmpfile, storage);
} else {
// The file is deleted automatically
throw InvalidRequestError();
Expand Down
7 changes: 0 additions & 7 deletions include/tmp/directory
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,6 @@ public:
/// @returns The result of path concatenation
std::filesystem::path operator/(const std::filesystem::path& source) const;

/// Moves this directory recursively to the given path, releasing
/// ownership of the directory, as if by `std::filesystem::rename`
/// even when moving between filesystems
/// @param to The target path
/// @throws std::filesystem::filesystem_error if cannot move the directory
void move(const std::filesystem::path& to);

/// Deletes this directory recursively
~directory() noexcept;

Expand Down
15 changes: 4 additions & 11 deletions include/tmp/file
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ namespace tmp {
/// used seamlessly with standard input/output operations and simplifying file
/// handling while maintaining the flexibility of stream operations
///
/// The example below demonstrates a usage of a tmp::file object to validate
/// a request content and then move it to persistent storage; note that after
/// the move() call, the temporary file will not be deleted, as its ownership
/// is transferred to the specified location:
/// The example below demonstrates a usage of a `tmp::file` object to validate
/// a request content and then unarchive it to persistent storage:
///
/// @code{.cpp}
/// #include <tmp/file>
Expand All @@ -40,8 +38,8 @@ namespace tmp {
/// auto tmpfile = tmp::file(std::ios::binary);
/// tmpfile << contents << std::flush;
/// if (validate(tmpfile)) {
/// // Save the file to the persistent storage
/// tmpfile.move(storage / "new_file");
/// // Unarchive the file to the persistent storage
/// archive::unzip(tmpfile, storage);
/// } else {
/// // The file is deleted automatically
/// throw InvalidRequestError();
Expand Down Expand Up @@ -81,11 +79,6 @@ public:
/// @returns The underlying implementation-defined handle
native_handle_type native_handle() const noexcept;

/// Flushes buffered input and copies this file to the given path
/// @param to The target path
/// @throws std::filesystem::filesystem_error if cannot move the file
void move(const std::filesystem::path& to);

/// Closes and deletes this file
~file() noexcept override;

Expand Down
59 changes: 0 additions & 59 deletions src/directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
namespace tmp {
namespace {

/// Options for recursive overwriting copying
constexpr fs::copy_options copy_options =
fs::copy_options::recursive | fs::copy_options::overwrite_existing;

/// Deletes a directory recursively, ignoring any errors
/// @param[in] path The path to the directory to delete
void remove_directory(const fs::path& path) noexcept {
Expand All @@ -28,49 +24,6 @@ void remove_directory(const fs::path& path) noexcept {
}
}
}

/// Moves the directory recursively as if by `std::filesystem::rename`
/// even when moving between filesystems
/// @param[in] from The path to the directory to move
/// @param[in] to The target path
/// @param[out] ec Parameter for error reporting
void move_directory(const fs::path& from, const fs::path& to,
std::error_code& ec) {
// FIXME: fs::is_directory can throw here
// FIXME: Time-of-check to time-of-use
if (fs::exists(to) && !fs::is_directory(to)) {
ec = std::make_error_code(std::errc::not_a_directory);
return;
}

bool copying = false;

#ifdef _WIN32
// On Windows, it is unspecified what error will be returned when
// renaming between multiple volumes; so we need to check this manually
// before taking any action
copying = from.root_name() != to.root_name();
if (copying) {
fs::copy(from, to, copy_options, ec);
} else {
fs::rename(from, to, ec);
}
#else
// On POSIX-compliant systems, the underlying `rename` function may return
// `EXDEV` if the implementation does not support links between file systems;
// so we try to rename the file, and if we fail with `EXDEV`, move it manually
fs::rename(from, to, ec);
copying = ec == std::errc::cross_device_link;
if (copying) {
fs::remove_all(to);
fs::copy(from, to, copy_options, ec);
}
#endif

if (!ec && copying) {
remove_directory(from);
}
}
} // namespace

directory::directory(std::string_view prefix)
Expand Down Expand Up @@ -102,18 +55,6 @@ fs::path directory::operator/(const fs::path& source) const {
return path() / source;
}

void directory::move(const fs::path& to) {
std::error_code ec;
move_directory(*this, to, ec);

if (ec) {
throw fs::filesystem_error("Cannot move a temporary directory", path(), to,
ec);
}

pathobject.clear();
}

directory::~directory() noexcept {
(void)reserved; // Old compilers do not want to accept `[[maybe_unused]]`
remove_directory(*this);
Expand Down
43 changes: 7 additions & 36 deletions src/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,17 @@ const file::native_handle_type invalid_handle = nullptr;
const file::native_handle_type invalid_handle = -1;
#endif

/// Opens a file and returns its handle
/// Opens a file for reading and returns its handle
/// @param[in] path The path to the file to open
/// @param[in] readonly Whether to open file only for reading
/// @param[out] ec Parameter for error reporting
/// @returns A handle to the open file
file::native_handle_type open_file(const fs::path& path, bool readonly,
file::native_handle_type open_file(const fs::path& path,
std::error_code& ec) noexcept {
#ifdef _WIN32
DWORD access = readonly ? GENERIC_READ : GENERIC_WRITE;
DWORD creation_disposition = readonly ? OPEN_EXISTING : CREATE_ALWAYS;
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

HANDLE handle =
CreateFile(path.c_str(), access, share_mode, nullptr,
creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
CreateFile(path.c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (handle == INVALID_HANDLE_VALUE) {
ec = std::error_code(GetLastError(), std::system_category());
return invalid_handle;
Expand All @@ -78,9 +74,7 @@ file::native_handle_type open_file(const fs::path& path, bool readonly,
return invalid_handle;
}
#else
constexpr mode_t mode = 0644;
int oflag = readonly ? O_RDONLY | O_NONBLOCK : O_RDWR | O_TRUNC | O_CREAT;
int handle = open(path.c_str(), oflag, mode);
int handle = open(path.c_str(), O_RDONLY | O_NONBLOCK);
if (handle == -1) {
ec = std::error_code(errno, std::system_category());
return invalid_handle;
Expand Down Expand Up @@ -232,7 +226,7 @@ file file::copy(const fs::path& path, std::ios::openmode mode) {
file tmpfile = file(mode);

std::error_code ec;
native_handle_type source = open_file(path, /*readonly=*/true, ec);
native_handle_type source = open_file(path, ec);
if (!ec) {
copy_file(source, tmpfile.native_handle(), ec);
close_file(source);
Expand All @@ -255,29 +249,6 @@ file::native_handle_type file::native_handle() const noexcept {
#endif
}

void file::move(const fs::path& to) {
flush();
seekg(0, std::ios::beg);

// There is no way to create a hard link to a file without hard links
// on POSIX and Windows. The only option is to use `AT_EMPTY_PATH`
// with `linkat` on Linux platforms; but given that the file is created
// in tmpfs, and the persistent storage where the file is moved
// is not in tmpfs, this optimization is useless.
// So, we copy the file

std::error_code ec;
native_handle_type target = open_file(to, /*readonly=*/false, ec);
if (!ec) {
copy_file(native_handle(), target, ec);
close_file(target);
}

if (ec) {
throw fs::filesystem_error("Cannot move a temporary file", to, ec);
}
}

file::~file() noexcept = default;

// NOLINTBEGIN(*-use-after-move)
Expand Down
64 changes: 0 additions & 64 deletions tests/directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,70 +120,6 @@ TEST(directory, subpath) {
EXPECT_TRUE(fs::equivalent(expected, tmpdir / fs::path(L"child")));
}

/// Tests that moving a temporary directory to itself does nothing
TEST(directory, move_to_self) {
fs::path path;

{
directory tmpdir = directory();
std::ofstream(tmpdir / "file") << "Hello, world!";

path = tmpdir;

tmpdir.move(tmpdir);
}

EXPECT_TRUE(fs::exists(path));

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

fs::remove_all(path);
}

/// Tests moving a temporary directory to existing directory
TEST(directory, move_to_existing_directory) {
fs::path path;

fs::path to = fs::path(BUILD_DIR) / "move_directory_to_existing_test";
std::ofstream(to / "file2") << "Goodbye, world!";

{
directory tmpdir = directory();
std::ofstream(tmpdir / "file") << "Hello, world!";

path = tmpdir;

tmpdir.move(to);
}

EXPECT_TRUE(fs::exists(to));
EXPECT_FALSE(fs::exists(path));

{
std::ifstream stream = std::ifstream(to / "file");
std::string content = std::string(std::istreambuf_iterator(stream), {});
EXPECT_EQ(content, "Hello, world!");
}

EXPECT_FALSE(fs::exists(to / "file2"));

fs::remove_all(to);
}

/// Tests moving a temporary directory to an existing file
TEST(directory, move_to_existing_file) {
fs::path to = fs::path(BUILD_DIR) / "existing_file";
std::ofstream(to) << "Goodbye, world!";

EXPECT_THROW(directory().move(to), fs::filesystem_error);

fs::remove_all(to);
}

/// Tests that destructor removes a directory
TEST(directory, destructor) {
fs::path path;
Expand Down
44 changes: 0 additions & 44 deletions tests/file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,50 +188,6 @@ TEST(file, copy_errors) {
EXPECT_THROW(file::copy("nonexistent.txt"), fs::filesystem_error);
}

/// Tests moving a temporary file to existing non-directory file
TEST(file, move_to_existing_file) {
fs::path to = fs::path(BUILD_DIR) / "move_file_to_existing_test";
std::ofstream(to) << "Goodbye, world!";

{
file tmpfile = file();
tmpfile << "Hello, world!";

tmpfile.move(to);
}

std::error_code ec;
EXPECT_TRUE(fs::exists(to, ec));

{
std::ifstream stream = std::ifstream(to);
std::string content = std::string(std::istreambuf_iterator(stream), {});
EXPECT_EQ(content, "Hello, world!");
}

fs::remove_all(to);
}

/// Tests moving a temporary file to an existing directory
TEST(file, move_to_existing_directory) {
fs::path directory = fs::path(BUILD_DIR) / "existing_directory";
fs::create_directories(directory);

EXPECT_THROW(file().move(directory), fs::filesystem_error);

fs::remove_all(directory);
}

/// Tests moving a temporary file to a non-existing directory
TEST(file, move_to_non_existing_directory) {
fs::path parent = fs::path(BUILD_DIR) / "non-existing2";
fs::path to = parent / "path/";

EXPECT_THROW(file().move(to), fs::filesystem_error);

fs::remove_all(parent);
}

/// Tests that destructor removes a file
TEST(file, destructor) {
file::native_handle_type handle;
Expand Down