diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 04:02:27 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-03-11 04:02:27 -0500 |
| commit | eb8c20695643923598d0516e86b1fcef144f65fa (patch) | |
| tree | d2a4df0c6084bc9f112bd01b3e7ec35e01731aa3 | |
| parent | 983c9c04666bca79033766263094fe1d36588ec9 (diff) | |
Fix FUSE passthrough: use proper API, drop LD_LIBRARY_PATH, auto-apply cap
- Use fuse_passthrough_open/close API instead of raw fd assignment to
fi->backing_id, which caused EIO on every file open through the VFS
- Remove LD_LIBRARY_PATH from launcher script — DT_RPATH handles lib
resolution, and LD_LIBRARY_PATH triggers AT_SECURE which drops file
capabilities (cap_sys_admin) needed for passthrough
- Auto-detect missing cap_sys_admin at mount time and prompt via pkexec
to re-apply it (handles binary copy/update/extract losing the cap)
- Disable passthrough for session if fuse_passthrough_open fails, avoiding
"Operation not permitted" spam in terminal
- Fix Nix flake: tinyxml2 → tinyxml-2, xorg.lib* → top-level names
- CI: eliminate artifact zip round-trip for GitHub releases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | .github/workflows/ci.yml | 18 | ||||
| -rwxr-xr-x | docker/build-inner.sh | 5 | ||||
| -rw-r--r-- | flake.nix | 55 | ||||
| -rw-r--r-- | src/src/organizercore.cpp | 42 | ||||
| -rw-r--r-- | src/src/vfs/mo2filesystem.cpp | 30 | ||||
| -rw-r--r-- | src/src/vfs/mo2filesystem.h | 1 |
6 files changed, 101 insertions, 50 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3ca6be..0212a31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: build: runs-on: ubuntu-latest permissions: - contents: read + contents: write packages: write steps: @@ -75,23 +75,11 @@ jobs: with: name: Fluorine-Manager path: artifact/fluorine-manager.tar.gz + compression-level: 0 if-no-files-found: warn - release: - if: startsWith(github.ref, 'refs/tags/v') - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - name: Fluorine-Manager - path: artifact/ - - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: files: artifact/fluorine-manager.tar.gz diff --git a/docker/build-inner.sh b/docker/build-inner.sh index d035e34..e9add61 100755 --- a/docker/build-inner.sh +++ b/docker/build-inner.sh @@ -349,7 +349,10 @@ RUN="${BIN_DST}" PYTHON_DIR="${RUN}/python" export PATH="${RUN}:${PATH}" -export LD_LIBRARY_PATH="${RUN}/lib:${PYTHON_DIR}/lib:${LD_LIBRARY_PATH:-}" +# NOTE: Do NOT set LD_LIBRARY_PATH here. The binary uses DT_RPATH +# ($ORIGIN/lib:$ORIGIN/python/lib) to find its libraries. Setting +# LD_LIBRARY_PATH on a capability-bearing binary (cap_sys_admin for FUSE +# passthrough) triggers AT_SECURE mode, which drops all file capabilities. export MO2_BASE_DIR="${RUN}" export MO2_PLUGINS_DIR="${RUN}/plugins" export MO2_DLLS_DIR="${RUN}/dlls" @@ -142,7 +142,7 @@ # System libraries sqlite - tinyxml2 + tinyxml-2 spdlog fuse3 lz4 @@ -382,7 +382,8 @@ RUN="''${BIN_DST}" PYTHON_DIR="''${RUN}/python" export PATH="''${RUN}:''${PATH}" -export LD_LIBRARY_PATH="''${RUN}/lib:''${PYTHON_DIR}/lib:''${LD_LIBRARY_PATH:-}" +# NOTE: Do NOT set LD_LIBRARY_PATH — DT_RPATH handles library resolution. +# Setting it on a capability-bearing binary drops file capabilities (AT_SECURE). export MO2_BASE_DIR="''${RUN}" export MO2_PLUGINS_DIR="''${RUN}/plugins" export MO2_DLLS_DIR="''${RUN}/dlls" @@ -443,19 +444,19 @@ LAUNCH vulkan-loader mesa - xorg.libX11 - xorg.libxcb - xorg.libXext - xorg.libXrandr - xorg.libXcursor - xorg.libXi - xorg.libXfixes - xorg.libXrender - xorg.libXcomposite - xorg.libXdamage - xorg.libXtst - xorg.libXScrnSaver - xorg.libXinerama + libx11 + libxcb + libxext + libxrandr + libxcursor + libxi + libxfixes + libxrender + libxcomposite + libxdamage + libxtst + libxscrnsaver + libxinerama libxkbcommon wayland @@ -490,18 +491,18 @@ LAUNCH fontconfig zlib glib - xorg.libX11 - xorg.libxcb - xorg.libXext - xorg.libXrandr - xorg.libXcursor - xorg.libXi - xorg.libXfixes - xorg.libXrender - xorg.libXcomposite - xorg.libXdamage - xorg.libXtst - xorg.libXinerama + libx11 + libxcb + libxext + libxrandr + libxcursor + libxi + libxfixes + libxrender + libxcomposite + libxdamage + libxtst + libxinerama wayland libxkbcommon gnutls diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index 3896039..b42c3ed 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -663,9 +663,49 @@ void OrganizerCore::prepareVFS() }
// 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.
{
- const bool passthrough =
+ 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");
+ QProcess setcap;
+ setcap.setProgram("pkexec");
+ setcap.setArguments({"setcap", "cap_sys_admin+ep", binary});
+ setcap.start();
+ setcap.waitForFinished(60000);
+
+ if (setcap.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 applied to binary — passthrough "
+ "will activate on next launch\n");
+ passthrough = false;
+ }
+ }
+ }
+
m_USVFS.setPassthroughEnabled(passthrough);
}
#endif
diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index 42e90c0..6c8cb87 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -1056,12 +1056,25 @@ void mo2_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) ptFd = open(realPath.c_str(), O_RDONLY); } if (ptFd >= 0) { - fi->backing_id = static_cast<uint32_t>(ptFd); - // 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; + 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"); } } } @@ -1667,6 +1680,11 @@ 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 2883a18..9b27d76 100644 --- a/src/src/vfs/mo2filesystem.h +++ b/src/src/vfs/mo2filesystem.h @@ -39,6 +39,7 @@ 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; |
