@@ -13,17 +13,22 @@ use std::fs::{File, OpenOptions};
13
13
use std:: ops:: Deref ;
14
14
use std:: os:: unix:: fs:: PermissionsExt ;
15
15
use std:: os:: unix:: io:: AsRawFd ;
16
+ use std:: os:: unix:: net:: UnixStream ;
16
17
use std:: path:: { Path , PathBuf } ;
17
18
use std:: sync:: { Arc , Mutex } ;
18
19
19
20
use nix:: errno:: Errno ;
20
- use nix:: fcntl:: { fcntl, FcntlArg , OFlag } ;
21
+ use nix:: fcntl:: { fcntl, FcntlArg , FdFlag , OFlag } ;
21
22
use nix:: mount:: { mount, umount2, MntFlags , MsFlags } ;
22
23
use nix:: poll:: { poll, PollFd , PollFlags } ;
23
24
use nix:: sys:: epoll:: { epoll_ctl, EpollEvent , EpollFlags , EpollOp } ;
24
25
use nix:: unistd:: { getgid, getuid, read} ;
25
26
26
- use super :: { super :: pagesize, Error :: SessionFailure , FuseBuf , FuseDevWriter , Reader , Result } ;
27
+ use super :: {
28
+ super :: pagesize,
29
+ Error :: { IoError , SessionFailure } ,
30
+ FuseBuf , FuseDevWriter , Reader , Result ,
31
+ } ;
27
32
28
33
// These follows definition from libfuse.
29
34
const FUSE_KERN_BUF_SIZE : usize = 256 ;
@@ -42,9 +47,12 @@ pub struct FuseSession {
42
47
fsname : String ,
43
48
subtype : String ,
44
49
file : Option < File > ,
50
+ // Socket to keep alive / drop for fusermount's auto_unmount.
51
+ keep_alive : Option < UnixStream > ,
45
52
bufsize : usize ,
46
53
readonly : bool ,
47
54
wakers : Mutex < Vec < Arc < Waker > > > ,
55
+ auto_unmount : bool ,
48
56
}
49
57
50
58
impl FuseSession {
@@ -54,6 +62,17 @@ impl FuseSession {
54
62
fsname : & str ,
55
63
subtype : & str ,
56
64
readonly : bool ,
65
+ ) -> Result < FuseSession > {
66
+ FuseSession :: new_with_autounmount ( mountpoint, fsname, subtype, readonly, false )
67
+ }
68
+
69
+ /// Create a new fuse session, without mounting/connecting to the in kernel fuse driver.
70
+ pub fn new_with_autounmount (
71
+ mountpoint : & Path ,
72
+ fsname : & str ,
73
+ subtype : & str ,
74
+ readonly : bool ,
75
+ auto_unmount : bool ,
57
76
) -> Result < FuseSession > {
58
77
let dest = mountpoint
59
78
. canonicalize ( )
@@ -67,9 +86,11 @@ impl FuseSession {
67
86
fsname : fsname. to_owned ( ) ,
68
87
subtype : subtype. to_owned ( ) ,
69
88
file : None ,
89
+ keep_alive : None ,
70
90
bufsize : FUSE_KERN_BUF_SIZE * pagesize ( ) + FUSE_HEADER_SIZE ,
71
91
readonly,
72
92
wakers : Mutex :: new ( Vec :: new ( ) ) ,
93
+ auto_unmount,
73
94
} )
74
95
}
75
96
@@ -79,11 +100,18 @@ impl FuseSession {
79
100
if self . readonly {
80
101
flags |= MsFlags :: MS_RDONLY ;
81
102
}
82
- let file = fuse_kern_mount ( & self . mountpoint , & self . fsname , & self . subtype , flags) ?;
103
+ let ( file, socket) = fuse_kern_mount (
104
+ & self . mountpoint ,
105
+ & self . fsname ,
106
+ & self . subtype ,
107
+ flags,
108
+ self . auto_unmount ,
109
+ ) ?;
83
110
84
111
fcntl ( file. as_raw_fd ( ) , FcntlArg :: F_SETFL ( OFlag :: O_NONBLOCK ) )
85
112
. map_err ( |e| SessionFailure ( format ! ( "set fd nonblocking: {e}" ) ) ) ?;
86
113
self . file = Some ( file) ;
114
+ self . keep_alive = socket;
87
115
88
116
Ok ( ( ) )
89
117
}
@@ -100,7 +128,9 @@ impl FuseSession {
100
128
101
129
/// Destroy a fuse session.
102
130
pub fn umount ( & mut self ) -> Result < ( ) > {
103
- if let Some ( file) = self . file . take ( ) {
131
+ // If we have a keep_alive socket, just drop it,
132
+ // and let fusermount3 do the unmount.
133
+ if let ( None , Some ( file) ) = ( self . keep_alive . take ( ) , self . file . take ( ) ) {
104
134
if let Some ( mountpoint) = self . mountpoint . to_str ( ) {
105
135
fuse_kern_umount ( mountpoint, file)
106
136
} else {
@@ -310,7 +340,13 @@ impl FuseChannel {
310
340
}
311
341
312
342
/// Mount a fuse file system
313
- fn fuse_kern_mount ( mountpoint : & Path , fsname : & str , subtype : & str , flags : MsFlags ) -> Result < File > {
343
+ fn fuse_kern_mount (
344
+ mountpoint : & Path ,
345
+ fsname : & str ,
346
+ subtype : & str ,
347
+ flags : MsFlags ,
348
+ auto_unmount : bool ,
349
+ ) -> Result < ( File , Option < UnixStream > ) > {
314
350
let file = OpenOptions :: new ( )
315
351
. create ( false )
316
352
. read ( true )
@@ -343,16 +379,117 @@ fn fuse_kern_mount(mountpoint: &Path, fsname: &str, subtype: &str, flags: MsFlag
343
379
file. as_raw_fd( ) ,
344
380
) ;
345
381
}
346
- mount (
347
- Some ( fsname) ,
348
- mountpoint,
349
- Some ( fstype. deref ( ) ) ,
350
- flags,
351
- Some ( opts. deref ( ) ) ,
382
+ if auto_unmount {
383
+ fuse_fusermount_mount ( mountpoint, fsname, subtype, opts, flags, auto_unmount)
384
+ } else {
385
+ match mount (
386
+ Some ( fsname) ,
387
+ mountpoint,
388
+ Some ( fstype. deref ( ) ) ,
389
+ flags,
390
+ Some ( opts. deref ( ) ) ,
391
+ ) {
392
+ Ok ( ( ) ) => Ok ( ( file, None ) ) ,
393
+ Err ( nix:: errno:: Errno :: EPERM ) => {
394
+ fuse_fusermount_mount ( mountpoint, fsname, subtype, opts, flags, auto_unmount)
395
+ }
396
+ Err ( e) => Err ( SessionFailure ( format ! (
397
+ "failed to mount {mountpoint:?}: {e}"
398
+ ) ) ) ,
399
+ }
400
+ }
401
+ }
402
+
403
+ fn msflags_to_string ( flags : MsFlags ) -> String {
404
+ [
405
+ ( MsFlags :: MS_RDONLY , ( "rw" , "ro" ) ) ,
406
+ ( MsFlags :: MS_NOSUID , ( "suid" , "nosuid" ) ) ,
407
+ ( MsFlags :: MS_NODEV , ( "dev" , "nodev" ) ) ,
408
+ ( MsFlags :: MS_NOEXEC , ( "exec" , "noexec" ) ) ,
409
+ ( MsFlags :: MS_SYNCHRONOUS , ( "async" , "sync" ) ) ,
410
+ ( MsFlags :: MS_NOATIME , ( "atime" , "noatime" ) ) ,
411
+ ( MsFlags :: MS_NODIRATIME , ( "diratime" , "nodiratime" ) ) ,
412
+ ( MsFlags :: MS_LAZYTIME , ( "nolazytime" , "lazytime" ) ) ,
413
+ ( MsFlags :: MS_RELATIME , ( "norelatime" , "relatime" ) ) ,
414
+ ( MsFlags :: MS_STRICTATIME , ( "nostrictatime" , "strictatime" ) ) ,
415
+ ]
416
+ . map (
417
+ |( flag, ( neg, pos) ) | {
418
+ if flags. contains ( flag) {
419
+ pos
420
+ } else {
421
+ neg
422
+ }
423
+ } ,
352
424
)
353
- . map_err ( |e| SessionFailure ( format ! ( "failed to mount {mountpoint:?}: {e}" ) ) ) ?;
425
+ . join ( "," )
426
+ }
354
427
355
- Ok ( file)
428
+ /// Mount a fuse file system with fusermount
429
+ fn fuse_fusermount_mount (
430
+ mountpoint : & Path ,
431
+ fsname : & str ,
432
+ subtype : & str ,
433
+ opts : String ,
434
+ flags : MsFlags ,
435
+ auto_unmount : bool ,
436
+ ) -> Result < ( File , Option < UnixStream > ) > {
437
+ let mut opts = vec ! [ format!( "fsname={fsname}" ) , opts, msflags_to_string( flags) ] ;
438
+ if !subtype. is_empty ( ) {
439
+ opts. push ( format ! ( "subtype={subtype}" ) ) ;
440
+ }
441
+ if auto_unmount {
442
+ opts. push ( "auto_unmount" . to_owned ( ) ) ;
443
+ }
444
+ let opts = opts. join ( "," ) ;
445
+
446
+ let ( send, recv) = UnixStream :: pair ( ) . unwrap ( ) ;
447
+
448
+ // Keep the sending socket around after exec to pass to fusermount3.
449
+ // When its partner recv closes, fusermount3 will unmount.
450
+ // Remove the close-on-exec flag from the socket, so we can pass it to
451
+ // fusermount3.
452
+ nix:: fcntl:: fcntl ( send. as_raw_fd ( ) , FcntlArg :: F_SETFD ( FdFlag :: empty ( ) ) )
453
+ . map_err ( |e| SessionFailure ( format ! ( "Failed to remove close-on-exec flag: {e}" ) ) ) ?;
454
+
455
+ let mut proc = std:: process:: Command :: new ( "fusermount3" )
456
+ . env ( "_FUSE_COMMFD" , format ! ( "{}" , send. as_raw_fd( ) ) )
457
+ // Old version of fusermount doesn't support long --options, yet.
458
+ . arg ( "-o" )
459
+ . arg ( opts)
460
+ . arg ( "--" )
461
+ . arg ( mountpoint)
462
+ . spawn ( )
463
+ . map_err ( IoError ) ?;
464
+ if auto_unmount {
465
+ std:: thread:: spawn ( move || {
466
+ let _ = proc. wait ( ) ;
467
+ } ) ;
468
+ } else {
469
+ match proc. wait ( ) . map_err ( IoError ) ?. code ( ) {
470
+ Some ( 0 ) => { }
471
+ exit_code => {
472
+ return Err ( SessionFailure ( format ! (
473
+ "Unexpected exit code when running fusermount3: {exit_code:?}"
474
+ ) ) )
475
+ }
476
+ }
477
+ }
478
+ drop ( send) ;
479
+
480
+ match vmm_sys_util:: sock_ctrl_msg:: ScmSocket :: recv_with_fd ( & recv, & mut [ 0u8 ; 8 ] ) . map_err (
481
+ |e| {
482
+ SessionFailure ( format ! (
483
+ "Unexpected error when receiving fuse file descriptor from fusermount3: {}" ,
484
+ e
485
+ ) )
486
+ } ,
487
+ ) ? {
488
+ ( _recv_bytes, Some ( file) ) => Ok ( ( file, if auto_unmount { Some ( recv) } else { None } ) ) ,
489
+ ( recv_bytes, None ) => Err ( SessionFailure ( format ! (
490
+ "fusermount3 did not send a file descriptor. We received {recv_bytes} bytes."
491
+ ) ) ) ,
492
+ }
356
493
}
357
494
358
495
/// Umount a fuse file system
@@ -372,8 +509,32 @@ fn fuse_kern_umount(mountpoint: &str, file: File) -> Result<()> {
372
509
// Drop to close fuse session fd, otherwise synchronous umount can recurse into filesystem and
373
510
// cause deadlock.
374
511
drop ( file) ;
375
- umount2 ( mountpoint, MntFlags :: MNT_DETACH )
376
- . map_err ( |e| SessionFailure ( format ! ( "failed to umount {mountpoint}: {e}" ) ) )
512
+ match umount2 ( mountpoint, MntFlags :: MNT_DETACH ) {
513
+ Ok ( ( ) ) => Ok ( ( ) ) ,
514
+ Err ( nix:: errno:: Errno :: EPERM ) => fuse_fusermount3_umount ( mountpoint) ,
515
+ Err ( e) => Err ( SessionFailure ( format ! (
516
+ "failed to umount {mountpoint}: {e}"
517
+ ) ) ) ,
518
+ }
519
+ }
520
+
521
+ /// Umount a fuse file system
522
+ fn fuse_fusermount3_umount ( mountpoint : & str ) -> Result < ( ) > {
523
+ match std:: process:: Command :: new ( "fusermount3" )
524
+ . arg ( "--unmount" )
525
+ . arg ( "--quiet" )
526
+ . arg ( "--lazy" )
527
+ . arg ( "--" )
528
+ . arg ( mountpoint)
529
+ . status ( )
530
+ . map_err ( IoError ) ?
531
+ . code ( )
532
+ {
533
+ Some ( 0 ) => Ok ( ( ) ) ,
534
+ exit_code => Err ( SessionFailure ( format ! (
535
+ "Unexpected exit code when unmounting via running fusermount3: {exit_code:?}"
536
+ ) ) ) ,
537
+ }
377
538
}
378
539
379
540
#[ cfg( test) ]
0 commit comments