From 366238dec3f97aa6ab09e2c4de7268855e458b5f Mon Sep 17 00:00:00 2001 From: Tom McLaughlin Date: Thu, 30 Jul 2026 16:42:15 -0700 Subject: [PATCH 1/4] mount9p-fuse: add --default-permissions flag --- src/fuse9p.rs | 7 ++++++- src/lib.rs | 5 +---- src/main.rs | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/fuse9p.rs b/src/fuse9p.rs index eae40f1..fbf3b70 100644 --- a/src/fuse9p.rs +++ b/src/fuse9p.rs @@ -286,6 +286,8 @@ impl Fuse9p { // the same path (the default); leaving it undetached keeps failed I/O visible as ENOTCONN // rather than briefly exposing whatever is underneath. detach_on_transport_loss: bool, + // Whether to mount with `default_permissions`. + default_permissions: bool, ) -> Result<(), Box> { tracing::info!(?tuning, "mount9p-fuse: tuning"); // Attach as `uid` so the server acts as that user for file ops (a multiuser server like diod @@ -324,8 +326,10 @@ impl Fuse9p { let mut options = vec![ MountOption::FSName("p9fuse".to_string()), MountOption::Subtype("9p".to_string()), - MountOption::DefaultPermissions, ]; + if default_permissions { + options.push(MountOption::DefaultPermissions); + } // When mounting as root, the mount is root-owned but processes running as another uid can // only traverse it with `allow_other`. Only root may set allow_other without // `user_allow_other` in /etc/fuse.conf, so gate it on euid 0 -- an unprivileged mount is @@ -333,6 +337,7 @@ impl Fuse9p { if nix::unistd::geteuid().as_raw() == 0 { options.push(MountOption::AllowOther); } + let mp = mountpoint.to_path_buf(); tracing::info!(?mp, "mount9p-fuse: mounting FUSE filesystem"); // Use Session (not fuser::mount2) so we can take a Notifier: that's how out-of-band changes diff --git a/src/lib.rs b/src/lib.rs index f2d7795..aaf8de2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,9 +42,6 @@ use std::path::Path; /// - `uid` is the identity to attach as (`n_uname`); the server acts as this user for file ops. /// - `aname` is the export name to attach (must match the server's export, e.g. `"/export"`). /// - `tuning` controls the caching / write-back knobs (see [`Tuning`]). -/// -/// On 9p transport loss this exits and detaches the mount so a supervisor can remount cleanly (the -/// default). Call [`Fuse9p::run`] directly to control that with `detach_on_transport_loss`. pub async fn mount( transport: Box, mountpoint: &Path, @@ -53,5 +50,5 @@ pub async fn mount( aname: &str, tuning: Tuning, ) -> Result<(), Box> { - Fuse9p::run(transport, mountpoint, msize, uid, aname, tuning, true).await + Fuse9p::run(transport, mountpoint, msize, uid, aname, tuning, true, true).await } diff --git a/src/main.rs b/src/main.rs index 7d07c4e..e8b922b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,6 +97,9 @@ enum Cmd { /// place (I/O then fails with ENOTCONN rather than briefly exposing what's underneath). #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] detach_on_transport_loss: bool, + /// Let the kernel enforce permissions against the owner/mode the 9p server reports. + #[arg(long, default_value_t = true, action = clap::ArgAction::Set)] + default_permissions: bool, mountpoint: PathBuf, }, @@ -141,6 +144,7 @@ async fn main() -> Result<(), Box> { writeback, wb_depth, detach_on_transport_loss, + default_permissions, mountpoint, } => { let transport = build_transport(&connect, &parse_headers(&headers)?).await?; @@ -161,6 +165,7 @@ async fn main() -> Result<(), Box> { &aname, tuning, detach_on_transport_loss, + default_permissions, ) .await } From dca0fbb22468813c2bbefb23d1d7839b1293f39a Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 31 Jul 2026 15:38:10 -0700 Subject: [PATCH 2/4] Run cargo fmt --- src/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index e8b922b..664efc1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -219,10 +219,14 @@ async fn build_transport( headers: &[(String, String)], ) -> Result, Box> { if let Some(addr) = connect.strip_prefix("tcp://") { - Ok(Box::new(retry_connect(|| TcpTransport::connect(addr)).await?)) + Ok(Box::new( + retry_connect(|| TcpTransport::connect(addr)).await?, + )) } else if let Some(path) = connect.strip_prefix("unix://") { let path = Path::new(path); - Ok(Box::new(retry_connect(|| UnixTransport::connect(path)).await?)) + Ok(Box::new( + retry_connect(|| UnixTransport::connect(path)).await?, + )) } else if connect.starts_with("ws://") || connect.starts_with("wss://") { Ok(Box::new( WebSocketTransport::connect(connect, headers).await?, From bf16d9c293b2e7b0b0883887d1b2a748fa8d3f89 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 31 Jul 2026 15:39:27 -0700 Subject: [PATCH 3/4] ci: try running on arc --- .github/workflows/ci.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e25c86d..8459c1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,5 @@ name: CI -# Runs on the self-hosted Nix runners, where nix (with flakes) and a setuid `fusermount3` -# (/run/wrappers/bin) are already present -- so every tool comes from the flake: no apt, no rustup, -# no nix-installer. `nix flake check` runs build + clippy (-D warnings) + rustfmt; the FUSE -# integration tests run under `nix develop` because they need /dev/fuse. - on: push: pull_request: @@ -12,17 +7,13 @@ on: jobs: ci: - runs-on: [self-hosted, Linux] + runs-on: arc-medium if: "!contains(github.event.head_commit.message, 'noci')" steps: - uses: actions/checkout@v5 - # Build + clippy (-D warnings) + rustfmt, all defined as flake checks. - name: nix flake check run: nix flake check -L - # Integration tests mount a real diod export over FUSE, so they need /dev/fuse and a *setuid* - # `fusermount3` -- put /run/wrappers/bin ahead of the dev shell's non-setuid fusermount3 (set - # inside `nix develop`, since nix re-prepends its own bins). - name: integration tests (diod + FUSE) run: nix develop -c bash -c 'export PATH="/run/wrappers/bin:$PATH"; cargo test' From f5323804a5cc9edee5ba6ead2cdae2c3b573ddd5 Mon Sep 17 00:00:00 2001 From: thomasjm Date: Fri, 31 Jul 2026 15:59:29 -0700 Subject: [PATCH 4/4] Fix a clippy warning --- src/fuse9p.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fuse9p.rs b/src/fuse9p.rs index fbf3b70..84b102f 100644 --- a/src/fuse9p.rs +++ b/src/fuse9p.rs @@ -273,6 +273,7 @@ fn to_fileattr(ino: u64, a: &Attr) -> FileAttr { impl Fuse9p { /// Mount at `mountpoint`, blocking until unmounted. Builds the client, attaches, then runs the /// FUSE session on a blocking thread (callbacks bridge back to the runtime via `Handle`). + #[allow(clippy::too_many_arguments)] pub async fn run( transport: Box, mountpoint: &Path,