Skip to content
Closed
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
76 changes: 30 additions & 46 deletions src/directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,48 +28,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, the underlying `MoveFileExW` fails when moving a directory
// between drives; in that case we copy the directory manually
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,12 +60,38 @@ fs::path directory::operator/(const fs::path& source) const {
}

void directory::move(const fs::path& to) {
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 = path().root_name() != to.root_name();
if (copying) {
fs::copy(path(), to, fs::copy_options::recursive);
} else {
fs::rename(path(), to);
}
#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
std::error_code ec;
move_directory(*this, to, ec);
fs::rename(path(), to, ec);
copying = ec == std::errc::cross_device_link;
if (copying) {
fs::copy(path(), to, fs::copy_options::recursive);
} else {
// Here we can safely call `fs::rename` to throw a guaranteed
// implementation-defined exception
fs::rename(path(), to);
}
#endif

if (ec) {
throw fs::filesystem_error("Cannot move a temporary directory", path(), to,
ec);
if (copying) {
// Here we intentionally call the throwing overload of `fs::remove_all`
// to report errors with exceptions when deleting the old directory
fs::remove_all(path());
}

pathobject.clear();
Expand Down
Loading