From ad512b0c8ff94c4f859e93b371ce594b911ac651 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Fri, 10 Apr 2026 05:27:48 -0500 Subject: 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) --- src/src/vfs/mo2filesystem.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'src') 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; } -- cgit v1.3.1