diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 07:11:59 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 07:11:59 -0500 |
| commit | 03fc1da756d26944d66cff8997d712521b1fde2c (patch) | |
| tree | 1ae93be8d334a8486425e9b6aa3307a4bbb0f342 /src | |
| parent | b3f1ba196c384e3d96f84862a1e6cf21311a9cef (diff) | |
Remove FUSE passthrough, flake.nix, and stop archiving build output
FUSE passthrough caused Wine DLL loading failures (ERROR_NOACCESS 0x3E6)
for all file types, not just executables. Root cause is a kernel-level
incompatibility between FUSE passthrough I/O and Wine's file access
patterns. Removed all passthrough code, the passthrough helper binary,
libcap dependency, and the settings UI toggle.
Also removed flake.nix (unused with Docker build system) and stopped
tar.gz'ing the build output since GitHub zips release assets automatically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/src/fuseconnector.cpp | 9 | ||||
| -rw-r--r-- | src/src/fuseconnector.h | 2 | ||||
| -rw-r--r-- | src/src/organizercore.cpp | 60 | ||||
| -rw-r--r-- | src/src/settingsdialog.ui | 34 | ||||
| -rw-r--r-- | src/src/settingsdialogproton.cpp | 58 | ||||
| -rw-r--r-- | src/src/vfs/mo2filesystem.cpp | 93 | ||||
| -rw-r--r-- | src/src/vfs/mo2filesystem.h | 12 |
8 files changed, 2 insertions, 269 deletions
diff --git a/src/src/CMakeLists.txt b/src/src/CMakeLists.txt index fa5d658..d7a7642 100644 --- a/src/src/CMakeLists.txt +++ b/src/src/CMakeLists.txt @@ -10,7 +10,6 @@ find_package(Boost CONFIG REQUIRED COMPONENTS find_package(spdlog CONFIG REQUIRED) find_package(PkgConfig REQUIRED) pkg_check_modules(FUSE3 REQUIRED IMPORTED_TARGET fuse3) -pkg_check_modules(LIBCAP REQUIRED IMPORTED_TARGET libcap) find_package(Threads REQUIRED) qt_standard_project_setup() @@ -111,8 +110,6 @@ target_link_libraries(organizer PRIVATE spdlog::spdlog_header_only # FUSE3 (low-level API) PkgConfig::FUSE3 - # libcap (runtime capability check for passthrough) - PkgConfig::LIBCAP # Linux system libs dl) diff --git a/src/src/fuseconnector.cpp b/src/src/fuseconnector.cpp index b146359..1581423 100644 --- a/src/src/fuseconnector.cpp +++ b/src/src/fuseconnector.cpp @@ -300,8 +300,6 @@ bool FuseConnector::mount( m_context->backing_dir_fd = m_backingFd; m_context->uid = ::getuid(); m_context->gid = ::getgid(); - m_context->passthrough_requested = m_passthroughEnabled; - // NOTE: Do NOT include mount_point here — low-level API passes it // separately to fuse_session_mount(). Including it here causes // "fuse: unknown option(s)" error. @@ -446,13 +444,6 @@ void FuseConnector::setTrackingFilePath(const std::string& path) std::fprintf(stderr, "[VFS] setTrackingFilePath: '%s'\n", path.c_str()); } -void FuseConnector::setPassthroughEnabled(bool enabled) -{ - m_passthroughEnabled = enabled; - std::fprintf(stderr, "[VFS] passthrough %s by user\n", - enabled ? "enabled" : "disabled"); -} - std::shared_ptr<TrackedWrites> FuseConnector::trackedWrites() const { return m_trackedWrites; diff --git a/src/src/fuseconnector.h b/src/src/fuseconnector.h index 6d089bb..a13f014 100644 --- a/src/src/fuseconnector.h +++ b/src/src/fuseconnector.h @@ -42,7 +42,6 @@ public: void setPluginLoadOrder(const std::vector<std::string>& load_order); void setTrackingFilePath(const std::string& path); - void setPassthroughEnabled(bool enabled); std::shared_ptr<TrackedWrites> trackedWrites() const; void unmount(); @@ -97,7 +96,6 @@ private: std::thread m_fuseThread; bool m_mounted = false; bool m_discardStaging = false; - bool m_passthroughEnabled = false; }; #endif diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index 0b60fdd..372e9ac 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -662,66 +662,6 @@ void OrganizerCore::prepareVFS() m_USVFS.setTrackingFilePath(trackPath.toStdString());
}
- // FUSE passthrough: read the per-instance setting and pass it to the VFS.
- // The binary needs CAP_SYS_ADMIN for passthrough to work — it can be lost
- // when the binary is copied, updated, or extracted to a new location.
- {
- bool passthrough =
- QSettings().value("fluorine/fuse_passthrough", false).toBool();
-
- if (passthrough) {
- // Check if the binary actually has cap_sys_admin right now.
- const QString binary = QCoreApplication::applicationFilePath();
- QProcess getcap;
- getcap.setProgram("getcap");
- getcap.setArguments({binary});
- getcap.start();
- getcap.waitForFinished(5000);
- const QString caps = QString::fromUtf8(getcap.readAllStandardOutput());
- const bool hasCap = caps.contains("cap_sys_admin");
-
- if (!hasCap) {
- std::fprintf(stderr,
- "[VFS] passthrough enabled but binary lacks cap_sys_admin "
- "— requesting via pkexec...\n");
-
- // When cap_sys_admin is set on a binary, glibc's ld.so enters
- // AT_SECURE mode and ignores $ORIGIN in RPATH. Patch RPATH to
- // absolute paths before applying the capability.
- const QFileInfo fi(binary);
- const QString binDir = fi.absolutePath();
- const QString absRpath = binDir + "/lib:" + binDir + "/python/lib";
-
- // The binary may be running (text file busy), so copy → patch → replace.
- QProcess patchelf;
- patchelf.setProgram("pkexec");
- patchelf.setArguments({"bash", "-c",
- QString("cp '%1' '%1.tmp' && "
- "patchelf --force-rpath --set-rpath '%2' '%1.tmp' && "
- "setcap cap_sys_admin+ep '%1.tmp' && "
- "mv -f '%1.tmp' '%1'")
- .arg(binary, absRpath)});
- patchelf.start();
- patchelf.waitForFinished(60000);
-
- if (patchelf.exitCode() != 0) {
- std::fprintf(stderr,
- "[VFS] failed to set cap_sys_admin — disabling passthrough\n");
- passthrough = false;
- } else {
- // The capability is on the file now, but the running process won't
- // have it until next exec. Disable passthrough for this session —
- // it will work automatically on next launch.
- std::fprintf(stderr,
- "[VFS] cap_sys_admin + absolute RPATH applied — passthrough "
- "will activate on next launch\n");
- passthrough = false;
- }
- }
- }
-
- m_USVFS.setPassthroughEnabled(passthrough);
- }
#endif
m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString()));
}
diff --git a/src/src/settingsdialog.ui b/src/src/settingsdialog.ui index 7704b56..646b9b6 100644 --- a/src/src/settingsdialog.ui +++ b/src/src/settingsdialog.ui @@ -1883,40 +1883,6 @@ If you disable this feature, MO will only display official DLCs this way. Please </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_vfsPerformance"> - <property name="title"> - <string>VFS Performance</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_vfsPerf"> - <item> - <widget class="QCheckBox" name="fusePassthroughCheckBox"> - <property name="text"> - <string>Enable FUSE Passthrough (near-native I/O)</string> - </property> - <property name="toolTip"> - <string>Let the kernel serve game file reads directly, bypassing userspace. Dramatically improves I/O performance for loading textures, meshes, and archives. Requires a one-time privilege grant (sudo prompt). Needs kernel 6.9+ and libfuse 3.16+.</string> - </property> - </widget> - </item> - <item> - <widget class="QLabel" name="passthroughStatusLabel"> - <property name="text"> - <string/> - </property> - <property name="wordWrap"> - <bool>true</bool> - </property> - <property name="font"> - <font> - <pointsize>9</pointsize> - </font> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item> <spacer name="verticalSpacer_16"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/src/settingsdialogproton.cpp b/src/src/settingsdialogproton.cpp index 207ba6d..8aa2481 100644 --- a/src/src/settingsdialogproton.cpp +++ b/src/src/settingsdialogproton.cpp @@ -45,64 +45,6 @@ ProtonSettingsTab::ProtonSettingsTab(Settings& s, SettingsDialog& d) ui->launchWrapperEdit->setPlaceholderText("mangohud --dlsym"); ui->launchWrapperEdit->setText(QSettings().value("fluorine/launch_wrapper").toString()); - // FUSE passthrough toggle — requires CAP_SYS_ADMIN on the binary. - ui->fusePassthroughCheckBox->setChecked( - QSettings().value("fluorine/fuse_passthrough", false).toBool()); - ui->passthroughStatusLabel->setText(QString()); - - QObject::connect(ui->fusePassthroughCheckBox, &QCheckBox::toggled, this, - [this](bool checked) { - if (!checked) { - // Disabling doesn't need sudo — just save the setting. - QSettings().setValue("fluorine/fuse_passthrough", false); - ui->passthroughStatusLabel->setText(tr("Passthrough disabled. Takes effect on next game launch.")); - return; - } - - // Enabling requires setting CAP_SYS_ADMIN on the binary. - // Find the actual binary path (ModOrganizer-core). - const QString binary = QCoreApplication::applicationFilePath(); - if (binary.isEmpty()) { - ui->fusePassthroughCheckBox->setChecked(false); - ui->passthroughStatusLabel->setText(tr("Could not determine binary path.")); - return; - } - - ui->passthroughStatusLabel->setText( - tr("Granting FUSE passthrough capability... (sudo prompt)")); - - // Use pkexec to patch RPATH to absolute paths and set - // the file capability. $ORIGIN in RPATH is ignored by - // glibc for capability binaries (AT_SECURE mode). - const QFileInfo fi(binary); - const QString binDir = fi.absolutePath(); - const QString absRpath = binDir + "/lib:" + binDir + "/python/lib"; - - // The binary may be running (text file busy), so copy → patch → replace. - QProcess proc; - proc.setProgram("pkexec"); - proc.setArguments({"bash", "-c", - QString("cp '%1' '%1.tmp' && " - "patchelf --force-rpath --set-rpath '%2' '%1.tmp' && " - "setcap cap_sys_admin+ep '%1.tmp' && " - "mv -f '%1.tmp' '%1'") - .arg(binary, absRpath)}); - proc.start(); - proc.waitForFinished(60000); // 60s timeout for user to auth - - if (proc.exitCode() == 0) { - QSettings().setValue("fluorine/fuse_passthrough", true); - ui->passthroughStatusLabel->setText( - tr("Passthrough enabled. Takes effect on next game launch.")); - } else { - ui->fusePassthroughCheckBox->setChecked(false); - const QString err = QString::fromUtf8(proc.readAllStandardError()).trimmed(); - ui->passthroughStatusLabel->setText( - tr("Failed to set capability: %1").arg( - err.isEmpty() ? tr("authorization denied or pkexec not found") : err)); - } - }); - populateProtons(); QObject::connect(ui->protonVersionCombo, &QComboBox::currentIndexChanged, this, diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index 908c5bb..2b1ef5f 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -2,7 +2,6 @@ #include <fcntl.h> #include <linux/fs.h> -#include <sys/capability.h> #include <sys/time.h> #include <unistd.h> @@ -567,50 +566,6 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) conn->max_background = 128; conn->congestion_threshold = 96; - // FUSE passthrough: if requested by the user and supported by the kernel, - // enable kernel-level passthrough for read-only file opens. This lets the - // kernel serve reads directly from the backing file without round-tripping - // through userspace — near-native I/O performance for game file reads. - // Requires: kernel 6.9+, libfuse 3.16+, CAP_SYS_ADMIN on the binary. - ctx->passthrough_active = false; - if constexpr (FUSE_CAP_PASSTHROUGH != 0) { - // Check if the process actually has CAP_SYS_ADMIN in its effective set. - // Without it, fuse_passthrough_open will fail with EPERM, and negotiating - // passthrough at the kernel level can break Wine/Proton DLL loading. - bool hasCap = false; - { - cap_t caps = cap_get_proc(); - if (caps) { - cap_flag_value_t val = CAP_CLEAR; - cap_get_flag(caps, CAP_SYS_ADMIN, CAP_EFFECTIVE, &val); - hasCap = (val == CAP_SET); - cap_free(caps); - } - } - - if (ctx->passthrough_requested && hasCap && - (conn->capable & FUSE_CAP_PASSTHROUGH)) { - conn->want |= FUSE_CAP_PASSTHROUGH; - ctx->passthrough_active = true; - std::fprintf(stderr, "[VFS] FUSE passthrough enabled (kernel supports it)\n"); - } else if (ctx->passthrough_requested && !hasCap) { - std::fprintf(stderr, - "[VFS] FUSE passthrough requested but process lacks " - "CAP_SYS_ADMIN. Falling back to userspace I/O.\n"); - } else if (ctx->passthrough_requested) { - std::fprintf(stderr, - "[VFS] FUSE passthrough requested but NOT supported by kernel " - "(capable=0x%x). Falling back to userspace I/O.\n", - conn->capable); - } - } else { - if (ctx->passthrough_requested) { - std::fprintf(stderr, - "[VFS] FUSE passthrough requested but NOT available at compile time " - "(libfuse too old). Falling back to userspace I/O.\n"); - } - } - // Increase max read/write buffer to 1MB (kernel 4.20+ supports this). // Reduces FUSE round-trips for large file reads (textures, meshes, BSAs). constexpr unsigned int ONE_MB = 1048576; @@ -623,11 +578,10 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) std::fprintf(stderr, "[VFS] init: auto_inval=%d explicit_inval=%d readdirplus=%d " - "passthrough=%d max_bg=%u max_readahead=%u\n", + "max_bg=%u max_readahead=%u\n", (conn->want & FUSE_CAP_AUTO_INVAL_DATA) ? 1 : 0, (conn->want & FUSE_CAP_EXPLICIT_INVAL_DATA) ? 1 : 0, (conn->want & FUSE_CAP_READDIRPLUS) ? 1 : 0, - ctx->passthrough_active ? 1 : 0, conn->max_background, conn->max_readahead); } @@ -1061,44 +1015,6 @@ void mo2_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) fi->fh = fh; fi->keep_cache = 1; - // FUSE passthrough: for read-only opens, let the kernel serve reads directly - // from the backing file. This bypasses userspace entirely — the kernel handles - // read() syscalls by reading the real file, giving near-native I/O performance. - // Only eligible for non-writable, non-COW files (pure reads). - if constexpr (FUSE_CAP_PASSTHROUGH != 0) { - if (ctx->passthrough_active && !writable && !cowPending && fd < 0) { - // Open the real file so the kernel can passthrough reads from it. - int ptFd = -1; - if (isBacking && ctx->backing_dir_fd >= 0) { - ptFd = openat(ctx->backing_dir_fd, realPath.c_str(), O_RDONLY); - } else { - ptFd = open(realPath.c_str(), O_RDONLY); - } - if (ptFd >= 0) { - int backingId = fuse_passthrough_open(req, ptFd); - if (backingId > 0) { - fi->backing_id = backingId; - // Store the fd so we can close it on release - std::scoped_lock lock(ctx->open_files_mutex); - auto it = ctx->open_files.find(fh); - if (it != ctx->open_files.end()) { - it->second.fd = ptFd; - it->second.backing_id = backingId; - } - } else { - // Passthrough registration failed (e.g. missing cap_sys_admin at - // runtime). Disable passthrough for the rest of this session to - // avoid spamming "Operation not permitted" on every open. - close(ptFd); - ctx->passthrough_active = false; - std::fprintf(stderr, - "[VFS] fuse_passthrough_open failed — disabling " - "passthrough for this session\n"); - } - } - } - } - fuse_reply_open(req, fi); } @@ -1687,7 +1603,7 @@ void mo2_mkdir(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t /*mod replyEntryFromSnapshot(req, ctx, dirIno, snap); } -void mo2_release(fuse_req_t req, fuse_ino_t /*ino*/, struct fuse_file_info* fi) +void mo2_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) { Mo2FsContext* ctx = getContext(req); if (ctx == nullptr || fi == nullptr) { @@ -1699,11 +1615,6 @@ void mo2_release(fuse_req_t req, fuse_ino_t /*ino*/, struct fuse_file_info* fi) std::scoped_lock lock(ctx->open_files_mutex); auto it = ctx->open_files.find(fi->fh); if (it != ctx->open_files.end()) { - if constexpr (FUSE_CAP_PASSTHROUGH != 0) { - if (it->second.backing_id > 0) { - fuse_passthrough_close(req, it->second.backing_id); - } - } if (it->second.fd >= 0) { close(it->second.fd); } diff --git a/src/src/vfs/mo2filesystem.h b/src/src/vfs/mo2filesystem.h index 9b27d76..3877bbb 100644 --- a/src/src/vfs/mo2filesystem.h +++ b/src/src/vfs/mo2filesystem.h @@ -16,13 +16,6 @@ #include <unordered_map> #include <vector> -// FUSE passthrough support (kernel 6.9+, libfuse 3.16+). -// When available, the kernel serves reads directly from the backing file -// without round-tripping through userspace — near-native I/O performance. -#ifndef FUSE_CAP_PASSTHROUGH -#define FUSE_CAP_PASSTHROUGH 0 -#endif - struct Mo2FsContext { std::shared_ptr<VfsTree> tree; @@ -39,7 +32,6 @@ struct Mo2FsContext struct OpenFile { int fd = -1; - int backing_id = 0; // FUSE passthrough backing ID (0 = not using passthrough) std::string real_path; bool writable = false; bool is_backing = false; @@ -99,10 +91,6 @@ struct Mo2FsContext uid_t uid = 0; gid_t gid = 0; - // FUSE passthrough: when true AND kernel supports it, read-only file opens - // use kernel-level passthrough (zero-copy I/O bypassing userspace). - bool passthrough_requested = false; - bool passthrough_active = false; // set in mo2_init after capability check }; void mo2_init(void* userdata, struct fuse_conn_info* conn); |
