From 916fc35663b21f43536667ccb0ce23fc62adad24 Mon Sep 17 00:00:00 2001 From: Yike Ye Date: Wed, 8 Jul 2026 16:32:51 +0800 Subject: [PATCH] fix: defer FD_CLOEXEC fcntl until after daemonize on macOS On macOS with macFUSE 5.3.3+, fuse_mount() only records the mount request; the real mount and its XPC channel are created lazily on first use. fuse_session_fd() is one such first use: since macFUSE commit 867b5cdb ("Defer mount process until first use on macOS") it routes through fuse_session_mfch(), which triggers the delayed mount and marks the mount as started. sshfs calls fuse_session_fd() between fuse_mount() and fuse_daemonize() only to set FD_CLOEXEC on the fuse device, so on 5.3.3 that call now materializes the mount before daemonizing, and fuse_daemonize() then refuses to fork ("daemonize requested after mount started; continuing in foreground"). sshfs can no longer background itself on macOS even though its own code is unchanged. Defer the FD_CLOEXEC fcntl on macOS until after fuse_daemonize(). The fuse fd does not exist yet while the first ssh child is spawned in ssh_connect(), so nothing leaks in that window; the fcntl then runs in the daemon child, triggering the mount there (where its XPC connection survives) and still setting FD_CLOEXEC before any ssh child is re-spawned on reconnect. The upstream mount -> connect -> daemonize order is otherwise unchanged, and non-macOS platforms keep the fcntl in its original place (before ssh_connect), where it must stay because the fuse fd exists from fuse_mount() onward. Refs: https://github.com/libfuse/sshfs/issues/338 Co-Authored-By: Claude Opus 4.8 --- sshfs.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/sshfs.c b/sshfs.c index c97f66d..095563d 100644 --- a/sshfs.c +++ b/sshfs.c @@ -4518,7 +4518,11 @@ int main(int argc, char *argv[]) exit(1); } -#if !defined(__CYGWIN__) + /* + * On macOS the FD_CLOEXEC fcntl is deferred until after + * fuse_daemonize() below; see the comment there. + */ +#if !defined(__CYGWIN__) && !defined(__APPLE__) res = fcntl(fuse_session_fd(se), F_SETFD, FD_CLOEXEC); if (res == -1) perror("WARNING: failed to set FD_CLOEXEC on fuse device"); @@ -4542,6 +4546,30 @@ int main(int argc, char *argv[]) exit(1); } +#ifdef __APPLE__ + /* + * macFUSE 5.3.3+ mounts lazily: fuse_mount() only records the request, + * and the real mount (and its XPC channel) is created on first use. + * On macOS fuse_session_fd() is such a first use -- it triggers the + * delayed mount and marks the mount as started, after which + * fuse_daemonize() refuses to fork and stays in the foreground + * ("daemonize requested after mount started; continuing in foreground"). + * + * So defer the FD_CLOEXEC fcntl until here, after daemonizing: the + * mount is then created in the daemon child, where its XPC connection + * survives, and daemonization still forks as before. The fuse fd does + * not exist yet while the first ssh child is spawned in ssh_connect() + * above, so nothing can leak in that window; setting FD_CLOEXEC now + * still protects the ssh children spawned later on reconnect. + * + * See macFUSE commit 867b5cdb ("Defer mount process until first use on + * macOS") and https://github.com/libfuse/sshfs/issues/338 + */ + res = fcntl(fuse_session_fd(se), F_SETFD, FD_CLOEXEC); + if (res == -1) + perror("WARNING: failed to set FD_CLOEXEC on fuse device"); +#endif + if (sshfs.singlethread) res = fuse_loop(fuse); else