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
14 changes: 7 additions & 7 deletions include/tmp/directory
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ namespace tmp {
class TMP_EXPORT directory : public entry {
public:
/// Creates a unique temporary directory
/// @param label A label to attach to the temporary directory path
/// @param prefix A prefix to attach to the temporary directory path
/// @throws std::filesystem::filesystem_error if cannot create a directory
/// @throws std::invalid_argument if the label is ill-formatted
explicit directory(std::string_view label = "");
/// @throws std::invalid_argument if the prefix is ill-formatted
explicit directory(std::string_view prefix = "");

/// Creates a unique temporary copy recursively from the given path
/// @param path A path to make a temporary copy from
/// @param label A label to attach to the temporary directory path
/// @param path A path to make a temporary copy from
/// @param prefix A prefix to attach to the temporary directory path
/// @returns The new temporary directory
/// @throws std::filesystem::filesystem_error if given path is not a directory
/// @throws std::invalid_argument if the label is ill-formatted
/// @throws std::invalid_argument if the prefix is ill-formatted
static directory copy(const std::filesystem::path& path,
std::string_view label = "");
std::string_view prefix = "");

/// Concatenates this directory path with a given source
/// @param source A string which represents a path name
Expand Down
83 changes: 37 additions & 46 deletions src/create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,33 @@
namespace tmp {
namespace {

/// Checks that the given label is valid to attach to a temporary entry path
/// @param[in] label The label to check validity for
/// @returns `true` if the label is valid, `false` otherwise
bool is_label_valid(const fs::path& label) {
return label.empty() || (++label.begin() == label.end() &&
label.is_relative() && !label.has_root_path() &&
label.filename() != "." && label.filename() != "..");
/// Checks if the given prefix is valid to attach to a temporary directory name
/// @param[in] prefix The prefix to check validity for
/// @returns `true` if the prefix is valid, `false` otherwise
bool is_prefix_valid(const fs::path& prefix) {
return prefix.empty() ||
(++prefix.begin() == prefix.end() && prefix.is_relative() &&
!prefix.has_root_path() && prefix.filename() != "." &&
prefix.filename() != "..");
}

/// Checks that the given label is valid to attach to a temporary entry path
/// @param label The label to check validity for
/// @throws std::invalid_argument if the label cannot be attached to a path
void validate_label(const fs::path& label) {
if (!is_label_valid(label)) {
/// Checks that the given prefix is valid to attach to a temporary entry path
/// @param prefix The prefix to check validity for
/// @throws std::invalid_argument if the prefix cannot be attached to a path
void validate_prefix(const fs::path& prefix) {
if (!is_prefix_valid(prefix)) {
throw std::invalid_argument(
"Cannot create a temporary entry: label must be empty or a valid "
"Cannot create a temporary entry: prefix must be empty or a valid "
"single-segmented relative pathname");
}
}

#ifdef _WIN32
/// Creates a temporary path with the given label
/// @note label must be valid
/// @param[in] label A label to attach to the path pattern
/// Creates a temporary path with the given prefix
/// @note prefix must be valid
/// @param[in] prefix A prefix to attach to the path pattern
/// @returns A unique temporary path
fs::path make_path(std::string_view label) {
fs::path make_path(std::string_view prefix) {
constexpr static std::size_t CHARS_IN_GUID = 39;
GUID guid;
CoCreateGuid(&guid);
Expand All @@ -58,25 +59,23 @@ fs::path make_path(std::string_view label) {
guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6],
guid.Data4[7]);

return fs::temp_directory_path() / label / name;
fs::path path = fs::temp_directory_path() / prefix;
path += name;

return path;
}
#else
/// Creates a temporary path pattern with the given label
/// @note label must be valid
/// @param[in] label A label to attach to the path pattern
/// Creates a temporary path pattern with the given prefix
/// @note prefix must be valid
/// @param[in] prefix A prefix to attach to the path pattern
/// @returns A path pattern for the unique temporary path
fs::path make_pattern(std::string_view label) {
return fs::temp_directory_path() / label / "XXXXXX";
}
#endif
fs::path make_pattern(std::string_view prefix) {
fs::path path = fs::temp_directory_path() / prefix;
path += "XXXXXX"; // TODO: add '.', like `com.github.bugdea1er.tmp.yotR2k`?

/// Creates the parent directory of the given path if it does not exist
/// @param[in] path The path for which the parent directory needs to be created
/// @param[out] ec Parameter for error reporting
/// @returns `true` if a parent directory was newly created, `false` otherwise
bool create_parent(const fs::path& path, std::error_code& ec) {
return fs::create_directories(path.parent_path(), ec);
return path;
}
#endif

#ifdef _WIN32
/// Makes a mode string for the `_wfdopen` function
Expand Down Expand Up @@ -147,10 +146,6 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
#else
fs::path::string_type path = make_pattern("");
#endif
create_parent(path, ec);
if (ec) {
return {};
}

mode |= std::ios::in | std::ios::out;

Expand Down Expand Up @@ -188,11 +183,11 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
return std::make_pair(path, std::move(filebuf));
}

fs::path create_directory(std::string_view label) {
validate_label(label); // throws std::invalid_argument with a proper text
fs::path create_directory(std::string_view prefix) {
validate_prefix(prefix); // throws std::invalid_argument with a proper text

std::error_code ec;
fs::path directory = create_directory(label, ec);
fs::path directory = create_directory(prefix, ec);

if (ec) {
throw fs::filesystem_error("Cannot create a temporary directory", ec);
Expand All @@ -201,21 +196,17 @@ fs::path create_directory(std::string_view label) {
return directory;
}

fs::path create_directory(std::string_view label, std::error_code& ec) {
if (!is_label_valid(label)) {
fs::path create_directory(std::string_view prefix, std::error_code& ec) {
if (!is_prefix_valid(prefix)) {
ec = std::make_error_code(std::errc::invalid_argument);
return fs::path();
}

#ifdef _WIN32
fs::path::string_type path = make_path(label);
fs::path::string_type path = make_path(prefix);
#else
fs::path::string_type path = make_pattern(label);
fs::path::string_type path = make_pattern(prefix);
#endif
create_parent(path, ec);
if (ec) {
return fs::path();
}

#ifdef _WIN32
if (!CreateDirectory(path.c_str(), nullptr)) {
Expand Down
14 changes: 7 additions & 7 deletions src/create.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ std::pair<fs::path, filebuf> create_file(std::ios::openmode mode);
std::pair<fs::path, filebuf> create_file(std::ios::openmode mode,
std::error_code& ec);

/// Creates a temporary directory with the given label in the system's
/// Creates a temporary directory with the given prefix in the system's
/// temporary directory
/// @param[in] label A label to attach to the temporary directory path
/// @param[in] prefix A prefix to attach to the temporary directory name
/// @returns A path to the created temporary directory
/// @throws fs::filesystem_error if cannot create a temporary directory
/// @throws std::invalid_argument if the label is ill-formatted
fs::path create_directory(std::string_view label);
/// @throws std::invalid_argument if the prefix is ill-formatted
fs::path create_directory(std::string_view prefix);

/// Creates a temporary directory with the given label in the system's
/// Creates a temporary directory with the given prefix in the system's
/// temporary directory
/// @param[in] label A label to attach to the temporary directory path
/// @param[in] prefix A prefix to attach to the temporary directory name
/// @param[out] ec Parameter for error reporting
/// @returns A path to the created temporary directory
fs::path create_directory(std::string_view label, std::error_code& ec);
fs::path create_directory(std::string_view prefix, std::error_code& ec);
} // namespace tmp

#endif // TMP_CREATE_H
8 changes: 4 additions & 4 deletions src/directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ constexpr fs::copy_options copy_options =
fs::copy_options::recursive | fs::copy_options::overwrite_existing;
} // namespace

directory::directory(std::string_view label)
: entry(create_directory(label)) {}
directory::directory(std::string_view prefix)
: entry(create_directory(prefix)) {}

directory directory::copy(const fs::path& path, std::string_view label) {
directory tmpdir = directory(label);
directory directory::copy(const fs::path& path, std::string_view prefix) {
directory tmpdir = directory(prefix);

std::error_code ec;
if (fs::is_directory(path)) {
Expand Down
5 changes: 0 additions & 5 deletions src/move.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,6 @@ void remove(const fs::path& path) noexcept {
try {
std::error_code ec;
fs::remove_all(path, ec);

fs::path parent = path.parent_path();
if (!fs::equivalent(parent, fs::temp_directory_path(), ec)) {
fs::remove(parent, ec);
}
} catch (const std::bad_alloc& ex) {
static_cast<void>(ex);
}
Expand Down
3 changes: 1 addition & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ find_package(GTest)
add_executable(${PROJECT_NAME} directory.cpp file.cpp)
target_link_libraries(${PROJECT_NAME} tmp::tmp GTest::gtest_main)
target_compile_definitions(${PROJECT_NAME}
PRIVATE LABEL="com.github.bugdea1er.tmp"
BUILD_DIR="${CMAKE_CURRENT_BINARY_DIR}")
PRIVATE BUILD_DIR="${CMAKE_CURRENT_BINARY_DIR}")

# On some platforms (e.g. Windows) CMake doesn't write load paths properly
# This solution to put outputs in the same directory is good enough
Expand Down
28 changes: 17 additions & 11 deletions tests/directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ namespace {

namespace fs = std::filesystem;

/// Tests directory creation with label
TEST(directory, create_with_label) {
directory tmpdir = directory(LABEL);
/// Temporary directory prefix for this test suite
constexpr std::string_view prefix = "com.github.bugdea1er.tmp";

/// Tests directory creation with prefix
TEST(directory, create_with_prefix) {
directory tmpdir = directory(prefix);
fs::path parent = tmpdir.path().parent_path();

EXPECT_TRUE(fs::exists(tmpdir));
EXPECT_TRUE(fs::is_directory(tmpdir));
EXPECT_TRUE(fs::equivalent(parent, fs::temp_directory_path() / LABEL));
EXPECT_TRUE(fs::equivalent(parent, fs::temp_directory_path()));

fs::path::string_type filename = tmpdir.path().filename();
EXPECT_EQ(filename.substr(0, prefix.length()), fs::path(prefix));

fs::perms permissions = fs::status(tmpdir).permissions();
#ifdef _WIN32
Expand All @@ -33,8 +39,8 @@ TEST(directory, create_with_label) {
#endif
}

/// Tests directory creation without label
TEST(directory, create_without_label) {
/// Tests directory creation without prefix
TEST(directory, create_without_prefix) {
directory tmpdir = directory();
fs::path parent = tmpdir.path().parent_path();

Expand All @@ -43,16 +49,16 @@ TEST(directory, create_without_label) {
EXPECT_TRUE(fs::equivalent(parent, fs::temp_directory_path()));
}

/// Tests multiple directories creation with the same label
/// Tests multiple directories creation with the same prefix
TEST(directory, create_multiple) {
directory fst = directory(LABEL);
directory snd = directory(LABEL);
directory fst = directory(prefix);
directory snd = directory(prefix);

EXPECT_FALSE(fs::equivalent(fst, snd));
}

/// Tests error handling with invalid labels
TEST(directory, create_invalid_label) {
/// Tests error handling with invalid prefixes
TEST(directory, create_invalid_prefix) {
EXPECT_THROW(directory("multi/segment"), std::invalid_argument);
EXPECT_THROW(directory("/root"), std::invalid_argument);
EXPECT_THROW(directory(".."), std::invalid_argument);
Expand Down
1 change: 0 additions & 1 deletion tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ unit_test = executable(
'file.cpp',
dependencies: [gtest_main_dep, tmp_dep],
cpp_args: [
'-DLABEL="com.github.bugdea1er.tmp"',
'-DBUILD_DIR="' + meson.current_build_dir() + '"',
],
build_by_default: false,
Expand Down
Loading