diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-02 16:27:30 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-02 16:27:30 -0600 |
| commit | 378d4cdf1501c5ff186f311a2edcdc85a2e03e30 (patch) | |
| tree | cb1cde374392614403de5c58db6eb698945d4bee | |
| parent | eaabd444c0f54b58e3179c6312b867d1e63e54c1 (diff) | |
Fix VFS staging permissions and createTarget routing
Two fixes:
1. copyOnWrite() preserves source file permissions via fs::copy_file().
If the source was read-only (Steam game files, Windows mod archives),
the staging copy was also read-only, causing writes to fail with
EACCES. Now adds owner_write permission after copying.
2. createTarget from the saves mapping (which targets __MO_Saves in
My Games, not the Data dir) was incorrectly setting m_customOutputDir,
causing ALL VFS staging files to flush to the profile saves directory
instead of overwrite. Now only data-dir mappings can set the custom
output directory.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | src/src/fuseconnector.cpp | 41 | ||||
| -rw-r--r-- | src/src/vfs/overwritemanager.cpp | 8 |
2 files changed, 35 insertions, 14 deletions
diff --git a/src/src/fuseconnector.cpp b/src/src/fuseconnector.cpp index 3ba5c69..f9a1aeb 100644 --- a/src/src/fuseconnector.cpp +++ b/src/src/fuseconnector.cpp @@ -384,24 +384,37 @@ void FuseConnector::updateMapping(const MappingType& mapping) auto mods = buildModsFromMapping(mapping, dataDirPath, overwriteDir); - // Check if any mapping has createTarget set — that mod directory should - // receive newly created files instead of the overwrite directory. + // Check if any data-dir mapping has createTarget set — that directory + // should receive newly created files instead of the overwrite directory. + // Only consider mappings that target the data directory; non-data-dir + // mappings (e.g. profile-specific saves → __MO_Saves in My Games) are + // deployed as real symlinks and should NOT redirect VFS staging output. m_customOutputDir.clear(); - for (const auto& map : mapping) { - if (map.createTarget) { - log::debug("Found createTarget mapping: source='{}', dest='{}', isDir={}", - map.source, map.destination, map.isDirectory); - } - if (map.createTarget && map.isDirectory) { - m_customOutputDir = - QDir::cleanPath(QDir::fromNativeSeparators(map.source)).toStdString(); - log::debug("Custom output directory set to: {}", - QString::fromStdString(m_customOutputDir)); - break; + { + const QString cleanDataDir = QDir::cleanPath(dataDirPath); + const QString dataPrefix = cleanDataDir + QStringLiteral("/"); + for (const auto& map : mapping) { + if (!map.createTarget || !map.isDirectory) { + continue; + } + const QString dst = + QDir::cleanPath(QDir::fromNativeSeparators(map.destination)); + const bool targetsDataDir = + (dst == cleanDataDir || dst.startsWith(dataPrefix)); + log::debug("Found createTarget mapping: source='{}', dest='{}', " + "targetsDataDir={}", + map.source, map.destination, targetsDataDir); + if (targetsDataDir) { + m_customOutputDir = + QDir::cleanPath(QDir::fromNativeSeparators(map.source)).toStdString(); + log::debug("Custom output directory set to: {}", + QString::fromStdString(m_customOutputDir)); + break; + } } } if (m_customOutputDir.empty()) { - log::debug("No createTarget mapping found, using overwrite dir"); + log::debug("No data-dir createTarget mapping found, using overwrite dir"); } // Deploy non-data-dir mappings as real symlinks and collect file-level diff --git a/src/src/vfs/overwritemanager.cpp b/src/src/vfs/overwritemanager.cpp index 7edfe7d..092a756 100644 --- a/src/src/vfs/overwritemanager.cpp +++ b/src/src/vfs/overwritemanager.cpp @@ -53,6 +53,9 @@ std::string OverwriteManager::copyOnWrite(const std::string& source_path, fs::create_directories(dest.parent_path(), ec); if (fs::exists(dest, ec)) { + // Ensure write permission even for pre-existing staging files (e.g. + // orphaned from a previous crash). + fs::permissions(dest, fs::perms::owner_write, fs::perm_options::add, ec); return dest.string(); } @@ -61,6 +64,11 @@ std::string OverwriteManager::copyOnWrite(const std::string& source_path, if (ec) { throw fs::filesystem_error("copyOnWrite", fs::path(source_path), dest, ec); } + // copy_file preserves source permissions. If the source was read-only + // (common for Steam game files or mods extracted from Windows archives), + // the staging copy would also be read-only, causing subsequent writes + // to fail with EACCES. Ensure the owner can always write. + fs::permissions(dest, fs::perms::owner_write, fs::perm_options::add, ec); } else { std::ofstream out(dest, std::ios::binary); out.close(); |
