diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-10 05:27:48 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-10 05:27:48 -0500 |
| commit | ad512b0c8ff94c4f859e93b371ce594b911ac651 (patch) | |
| tree | 5e484f97af07fb49a8ce65a6372322630cf2ce3a | |
| parent | 01abc6994d9a477904af3fdf326ee2fc7163b3e1 (diff) | |
Fix VFS rename rejecting RENAME_NOREPLACE flag from Wine
Wine uses renameat2(RENAME_NOREPLACE) when translating MoveFileW() calls
where ReplaceIfExists is FALSE (e.g. xEdit saving plugins). The VFS was
rejecting all non-zero flags with EINVAL, which a real filesystem like
ext4 handles correctly. This caused "Could not rename" errors in xEdit
and similar tools.
Now properly handles RENAME_NOREPLACE: checks if the destination exists
in the VFS tree and returns EEXIST (matching ext4 behavior), otherwise
proceeds with the rename. Adds diagnostic logging on EACCES failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| -rw-r--r-- | src/src/vfs/mo2filesystem.cpp | 21 |
1 files changed, 20 insertions, 1 deletions
diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index 5fe6be5..d5a9201 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -1583,7 +1583,15 @@ void mo2_rename(fuse_req_t req, fuse_ino_t parent, const char* name, fuse_ino_t newparent, const char* newname, unsigned int flags) { Mo2FsContext* ctx = getContext(req); - if (ctx == nullptr || name == nullptr || newname == nullptr || flags != 0) { + if (ctx == nullptr || name == nullptr || newname == nullptr) { + fuse_reply_err(req, EINVAL); + return; + } + + // Reject unsupported flags (only RENAME_NOREPLACE is supported). + // Wine uses renameat2(RENAME_NOREPLACE) for MoveFileW() calls where + // ReplaceIfExists is FALSE (e.g. xEdit saving plugins). + if (flags & ~(unsigned int)RENAME_NOREPLACE) { fuse_reply_err(req, EINVAL); return; } @@ -1606,7 +1614,18 @@ void mo2_rename(fuse_req_t req, fuse_ino_t parent, const char* name, return; } + // RENAME_NOREPLACE: fail if destination already exists in the VFS + if (flags & RENAME_NOREPLACE) { + const auto destSnap = snapshotForPath(ctx, newRelative); + if (destSnap.found) { + fuse_reply_err(req, EEXIST); + return; + } + } + 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; } |
