diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-11 15:55:55 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-11 15:55:55 -0500 |
| commit | 7bac34cf083fc0a0036b2df210e4943a0f7707be (patch) | |
| tree | 5b4e19a1b3c35d53cd182aab9377e5d9860cbb1d /src | |
| parent | eefa488da7292876f93e952af09638b8cca81ef2 (diff) | |
Fix VFS rename failing for backing and mod files
xEdit (and similar tools) rename game files to create backups before
saving (e.g. DLCHorseArmor.esp -> TES4Edit Backups/...backup...).
OverwriteManager::rename() only searches staging and overwrite
directories, so renames of backing (game) or mod files failed with
EACCES since those files live in the real game/mod directories.
Now when OverwriteManager::rename() can't find the source file, the
VFS copies it to staging at the destination path via COW instead of
failing. The file disappears from its old path and appears at the new
path in the virtual view, without modifying the real game/mod dirs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/src/vfs/mo2filesystem.cpp | 39 |
1 files changed, 31 insertions, 8 deletions
diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index d5a9201..b8b56f1 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -1623,11 +1623,32 @@ void mo2_rename(fuse_req_t req, fuse_ino_t parent, const char* name, } } + std::string newRealPath; + if (!ctx->overwrite->rename(oldRelative, newRelative)) { - std::fprintf(stderr, "[VFS] rename EACCES: '%s' -> '%s' (flags=%u, backing=%d)\n", - oldRelative.c_str(), newRelative.c_str(), flags, oldSnap.is_backing); - fuse_reply_err(req, EACCES); - return; + // Source file is not in staging or overwrite — it's a backing (game) file + // or a mod file. Copy it to staging at the destination path instead of + // moving the original. This is the VFS equivalent of a rename: the file + // appears at the new path and disappears from the old path in the virtual + // view, but we never modify the real game/mod directories. + if (oldSnap.is_directory) { + fuse_reply_err(req, EACCES); + return; + } + + try { + if (oldSnap.is_backing && ctx->backing_dir_fd >= 0) { + newRealPath = ctx->overwrite->copyOnWriteFromFd(ctx->backing_dir_fd, + newRelative); + } else { + newRealPath = ctx->overwrite->copyOnWrite(oldSnap.real_path, newRelative); + } + } catch (...) { + std::fprintf(stderr, "[VFS] rename COW failed: '%s' -> '%s'\n", + oldRelative.c_str(), newRelative.c_str()); + fuse_reply_err(req, EIO); + return; + } } { @@ -1638,10 +1659,12 @@ void mo2_rename(fuse_req_t req, fuse_ino_t parent, const char* name, if (oldSnap.is_directory) { ctx->tree->root.insertDirectory(splitPath(newRelative)); } else { - const std::string staged = ctx->overwrite->stagingPath(newRelative); - const std::string over = ctx->overwrite->overwritePath(newRelative); - const std::string real = fs::exists(staged) ? staged : over; - ctx->tree->root.insertFile(splitPath(newRelative), real, oldSnap.size, + if (newRealPath.empty()) { + const std::string staged = ctx->overwrite->stagingPath(newRelative); + const std::string over = ctx->overwrite->overwritePath(newRelative); + newRealPath = fs::exists(staged) ? staged : over; + } + ctx->tree->root.insertFile(splitPath(newRelative), newRealPath, oldSnap.size, oldSnap.mtime, "Staging"); } invalidateNodeCache(ctx, newRelative); |
