| Synchronous FUSE_INIT Protocol |
| ================================ |
| |
| Overview |
| -------- |
| |
| The sync-init feature enables the FUSE library to start worker threads and |
| perform initialization ioctl calls BEFORE the actual mount() syscall happens. |
| This is required for the kernel's synchronous FUSE_INIT feature, where the |
| mount() syscall blocks until the FUSE daemon processes the INIT request. |
| |
| Without this feature, there would be a deadlock: |
| - mount() blocks waiting for INIT response |
| - Worker threads can't start because mount() hasn't returned |
| - INIT request can't be processed because worker threads aren't running |
| |
| |
| Protocol Flow |
| ------------- |
| |
| Traditional mount flow: |
| 1. Library calls fusermount3 |
| 2. fusermount3 opens /dev/fuse |
| 3. fusermount3 performs mount() syscall |
| 4. fusermount3 sends fd to library |
| 5. Library starts worker threads |
| 6. Worker threads process FUSE requests |
| |
| Sync-init mount flow: |
| 1. Library calls fusermount3 with --sync-init flag |
| 2. fusermount3 opens /dev/fuse |
| 3. fusermount3 sends fd to library |
| 4. Library receives fd |
| 5. Library performs FUSE_DEV_IOC_SYNC_INIT ioctl |
| 6. Library starts worker threads |
| 7. Library sends "proceed" signal to fusermount3 |
| 8. fusermount3 performs mount() syscall (blocks until INIT completes) |
| 9. Worker threads process INIT request |
| 10. mount() syscall completes |
| 11. fusermount3 exits |
| |
| |
| Implementation Details |
| ---------------------- |
| |
| Bidirectional Communication: |
| - Uses the existing unix socket (_FUSE_COMMFD environment variable) |
| - Simple 1-byte protocol for signaling |
| - Library signals fusermount3 when ready to proceed with mount |
| |
| fusermount3 Changes: |
| - New --sync-init command-line option |
| - Split mount operation into two phases: |
| * mount_fuse_prepare(): Opens device, prepares parameters |
| * mount_fuse_finish_fsmount(): Performs actual mount() syscall |
| - wait_for_signal(): Waits for library to signal readiness |
| - struct mount_context: Preserves state between phases |
| |
| Library Changes: |
| - fuse_session_mount_new_api(): Uses new protocol when available |
| - Sends "proceed" signal after worker thread is ready |
| - Handles both old and new mount protocols for compatibility |
| |
| |
| Backward Compatibility |
| ---------------------- |
| |
| The implementation maintains full backward compatibility: |
| - Old library + new fusermount3: Works (uses traditional flow) |
| - New library + old fusermount3: Falls back to traditional flow |
| - New library + new fusermount3: Uses sync-init flow when appropriate |
| |
| |
| Error Handling |
| -------------- |
| |
| If any step fails during the sync-init flow: |
| - fusermount3 closes the fd and exits with error |
| - Library detects failure and cleans up |
| - No mount is left in inconsistent state |
| |
| Connection closure: |
| - If library closes socket before signaling, fusermount3 detects and exits |
| - If fusermount3 crashes, library detects closed socket |
| |
| |
| Security Considerations |
| ----------------------- |
| |
| The sync-init protocol does not introduce new security concerns: |
| - Uses the same privilege separation as traditional mount |
| - Socket communication is already established and trusted |
| - No new privileged operations are added |
| - File descriptor passing uses existing SCM_RIGHTS mechanism |
| |
| |
| Performance Impact |
| ------------------ |
| |
| Minimal performance impact: |
| - One additional recv() call in fusermount3 |
| - One additional send() call in library |
| - Total overhead: ~2 context switches |
| - Only affects mount time, not runtime performance |
| |
| |
| Future Enhancements |
| ------------------- |
| |
| Potential improvements: |
| - Extended protocol for more complex initialization sequences |
| - Support for multiple worker threads coordination |
| - Enhanced error reporting through the socket |
| - Timeout mechanisms for detecting hung initialization |
| |
| |
| ASCII Workflow Diagrams |
| ======================== |
| |
| 1. Traditional Mount Flow (without --sync-init, async INIT) |
| ------------------------------------------------------------ |
| |
| Library fusermount3 Kernel |
| | | | |
| |--- spawn fusermount3 ---->| | |
| | | | |
| | [open /dev/fuse] | |
| | |------- open -------->| |
| | |<------ fd ---------- | |
| | | | |
| | [mount() syscall] | |
| | |------ mount -------->| |
| | |<----- success ------ | [mount returns immediately] |
| | | | [INIT queued in kernel] |
| | [send_fd(fd)] | |
| |<------- fd --------------| | |
| | | | |
| | [fusermount3 exits] | |
| | | |
| | [start worker thread] | |
| | [worker reads /dev/fuse] | |
| |---------------------------------------- read -->| |
| |<--------------------------------------- INIT ---| [dequeued from kernel] |
| | | |
| | OK: INIT was queued, worker reads it later | |
| | Works fine for async INIT | |
| |
| |
| 1b. Problem: Synchronous INIT without --sync-init |
| -------------------------------------------------- |
| |
| Library fusermount3 Kernel |
| | | | |
| |--- spawn fusermount3 ---->| | |
| | | | |
| | [open /dev/fuse] | |
| | |------- open -------->| |
| | |<------ fd ---------- | |
| | | | |
| | [mount() syscall] | |
| | |------ mount -------->| |
| | | | [mount BLOCKS waiting for INIT] |
| | | (BLOCKED) | [needs worker to process INIT] |
| | | | |
| | [waiting for fd...] | | |
| | | | |
| | | | |
| | DEADLOCK: mount() waits for INIT response | |
| | but worker thread not started yet | |
| | because we're waiting for fd | |
| |
| |
| 2. Sync-Init Mount Flow (with --sync-init) |
| ------------------------------------------- |
| |
| Library fusermount3 Kernel |
| | | | |
| |--- spawn fusermount3 ---->| | |
| | with --sync-init | | |
| | | | |
| | [open /dev/fuse] | |
| | |------- open -------->| |
| | |<------ fd ---------- | |
| | | | |
| | [send_fd(fd)] | |
| |<------- fd --------------| | |
| | | | |
| | [wait_for_signal()] | |
| | | (BLOCKED) | |
| | | | |
| | [ioctl SYNC_INIT] | | |
| |---------------------------------------- ioctl -->| |
| | | |
| | [start worker thread] | |
| | [worker ready] | |
| | | | |
| |--- "proceed" signal ----->| | |
| | [signal received] | |
| | | | |
| | [mount() syscall] | |
| | |------ mount -------->| |
| | | | [mount blocks] |
| | | | [sends INIT] |
| |<------------------------------------------------ | |
| | | | |
| | [worker processes INIT] | | |
| |------------------------------------------------->| |
| | | | [mount unblocks] |
| | |<----- success ------ | |
| | | | |
| | [fusermount3 exits] | |
| | | |
| | SUCCESS: Worker ready before mount() | |
| | INIT processed synchronously | |
| |
| |
| 3. Error Scenario: Library Crashes Before Signaling |
| ---------------------------------------------------- |
| |
| Library fusermount3 Kernel |
| | | | |
| |--- spawn fusermount3 ---->| | |
| | with --sync-init | | |
| | | | |
| | [open /dev/fuse] | |
| | |------- open -------->| |
| | |<------ fd ---------- | |
| | | | |
| | [send_fd(fd)] | |
| |<------- fd --------------| | |
| | | | |
| | [wait_for_signal()] | |
| | | (BLOCKED) | |
| | | | |
| X [library crashes] | | |
| | | | |
| | [recv() returns 0] | |
| | [socket closed] | |
| | | | |
| | [cleanup and exit] | |
| | X | |
| | | |
| | RESULT: Clean failure, no mount performed | |
| |
| |
| 4. Detailed Function Call Flow |
| ------------------------------- |
| |
| Library (lib/fuse_lowlevel.c): |
| fuse_session_mount_new_api() |
| | |
| +-- fuse_kern_mount_prepare() [lib/mount.c] |
| | | |
| | +-- fuse_mount_fusermount() [lib/mount_util.c] |
| | | |
| | +-- socketpair() [create comm socket] |
| | | |
| | +-- fork() |
| | | |
| | +-- [child] execl("fusermount3", "--sync-init", ...) |
| | | |
| | +-- [parent] receive_fd() <--- BLOCKS until fd arrives |
| | | |
| | +-- recvmsg(SCM_RIGHTS) |
| | | |
| | +-- return fd |
| | |
| +-- session_start_sync_init() [lib/fuse_lowlevel.c] |
| | | |
| | +-- ioctl(fd, FUSE_DEV_IOC_SYNC_INIT) |
| | | |
| | +-- pthread_create(worker_thread) |
| | | |
| | +-- return |
| | |
| +-- fuse_fusermount_proceed_mnt(socket) [lib/mount.c] <--- NEW: Bidirectional handshake |
| | |
| +-- send(socket, "proceed", 1) <--- Signal fusermount3 to proceed |
| | |
| +-- recv(socket, &status, 1) <--- BLOCKS until mount result arrives |
| | | |
| | +-- [fusermount3 performs mount and sends status byte] |
| | |
| +-- if (status != 0) return -1 <--- Mount failed |
| | |
| +-- return 0 <--- Mount succeeded |
| |
| |
| Utility (util/fusermount.c): |
| fusermount3 main() with --sync-init |
| | |
| +-- mount_fuse_sync_init() [util/fusermount.c] |
| | |
| +-- mount_fuse_prepare() [util/fusermount.c] |
| | | |
| | +-- open("/dev/fuse") |
| | | |
| | +-- check_perm() [util/fusermount.c] |
| | | |
| | +-- return fd |
| | |
| +-- send_fd(socket, fd) [util/fusermount.c] |
| | | |
| | +-- sendmsg(SCM_RIGHTS) |
| | |
| +-- wait_for_signal(socket) [util/fusermount.c] <--- BLOCKS until library signals |
| | | |
| | +-- recv(socket, buf, 1) |
| | | |
| | +-- return 0 |
| | |
| +-- mount_fuse_finish_fsmount() [util/fusermount.c] |
| | | |
| | +-- fuse_kern_fsmount() [lib/mount_fsmount.c] |
| | | | |
| | | +-- fsopen("fuse", FSOPEN_CLOEXEC) |
| | | | | |
| | | | +-- [kernel creates filesystem context] |
| | | | |
| | | +-- fsconfig(fsfd, SET_STRING, "source", ...) |
| | | +-- fsconfig(fsfd, SET_STRING, "fd", fd_value, ...) |
| | | +-- fsconfig(fsfd, ...) [apply mount options] |
| | | +-- fsconfig(fsfd, CMD_CREATE, ...) |
| | | | |
| | | +-- fsmount(fsfd, FSMOUNT_CLOEXEC, mount_attrs) |
| | | | | |
| | | | +-- [kernel sends FUSE_INIT here] |
| | | | | |
| | | | +-- [worker thread processes INIT] |
| | | | | |
| | | | +-- [fsmount returns mntfd] |
| | | | |
| | | +-- move_mount(mntfd, "", AT_FDCWD, target, ...) |
| | | | | |
| | | | +-- [attach mount to target directory] |
| | | | | |
| | | | +-- [no blocking - INIT already processed] |
| | | | |
| | | +-- add_mount() [lib/mount_fsmount.c - update /etc/mtab] |
| | | | |
| | | +-- return 0 on success, -1 on failure |
| | | |
| | +-- if mount failed: return -1 |
| | +-- if mount succeeded: continue |
| | |
| +-- send_status_byte(socket) [util/fusermount.c] <--- NEW: Send result to library |
| | | |
| | +-- status = (mount_result == 0) ? 0 : 1 |
| | +-- send(socket, &status, 1) |
| | | |
| | +-- return |
| | |
| +-- return 0 |
| |
| |
| Note: The new mount API (fsopen/fsconfig/fsmount/move_mount) is REQUIRED |
| for sync-init because fsmount() triggers FUSE_INIT before the mount |
| is attached. This allows the worker thread to process INIT before |
| move_mount() completes, preventing deadlock. |
| And also so we don't expose the directory tree to the mountns until we |
| know that FUSE_INIT didn't crash the server. |
| |