aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThéophane Hufschmitt <theophane@hufschmitt.net>2022-03-17 16:13:29 +0100
committerThéophane Hufschmitt <theophane.hufschmitt@tweag.io>2022-08-03 10:27:25 +0200
commit6f89fb60088c4bc1513a005f0350c2bc13068892 (patch)
treea8f2c53173ff5dc8dfabb9f0fb43c8806315ac85
parentc2de0a232c1cfddb1f1385ffd23dd43a2099458e (diff)
rename: Fallback to a copy if the filesystems mismatch
In `nix::rename`, if the call to `rename` fails with `EXDEV` (failure because the source and the destination are in a different filesystems) switch to copying and removing the source. To avoid having to re-implement the copy manually, I switched the function to use the c++17 `filesystem` library (which has a `copy` function that should do what we want). Fix #6262
-rw-r--r--src/libutil/filesystem.cc15
1 files changed, 13 insertions, 2 deletions
diff --git a/src/libutil/filesystem.cc b/src/libutil/filesystem.cc
index 33a8d81a6..cf99b848a 100644
--- a/src/libutil/filesystem.cc
+++ b/src/libutil/filesystem.cc
@@ -1,8 +1,11 @@
#include <sys/time.h>
+#include <filesystem>
#include "util.hh"
#include "types.hh"
+namespace fs = std::filesystem;
+
namespace nix {
void createSymlink(const Path & target, const Path & link,
@@ -42,8 +45,16 @@ void replaceSymlink(const Path & target, const Path & link,
void moveFile(const Path & oldName, const Path & newName)
{
- if (::rename(oldName.c_str(), newName.c_str()))
- throw SysError("renaming '%1%' to '%2%'", oldName, newName);
+ auto oldPath = fs::path(oldName);
+ auto newPath = fs::path(newName);
+ try {
+ fs::rename(oldPath, newPath);
+ } catch (fs::filesystem_error & e) {
+ if (e.code().value() == EXDEV) {
+ fs::copy(oldName, newName, fs::copy_options::copy_symlinks);
+ fs::remove_all(oldName);
+ }
+ }
}
}