1- use fuser:: { Filesystem , Session } ;
1+ use fuser:: { Filesystem , Session , SessionACL } ;
22use std:: rc:: Rc ;
3+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
4+ use std:: sync:: Arc ;
35use std:: thread;
46use std:: time:: Duration ;
57use tempfile:: TempDir ;
@@ -22,3 +24,164 @@ fn unmount_no_send() {
2224 } ) ;
2325 session. run ( ) . unwrap ( ) ;
2426}
27+
28+ /// Test that clone_fd creates a working file descriptor for multi-reader setups.
29+ #[ cfg( target_os = "linux" ) ]
30+ #[ test]
31+ fn clone_fd_multi_reader ( ) {
32+ use std:: os:: fd:: AsRawFd ;
33+
34+ // Simple filesystem that tracks how many times getattr is called
35+ struct CountingFS {
36+ count : Arc < AtomicUsize > ,
37+ }
38+
39+ impl Filesystem for CountingFS {
40+ fn getattr (
41+ & mut self ,
42+ _req : & fuser:: Request < ' _ > ,
43+ ino : u64 ,
44+ _fh : Option < u64 > ,
45+ reply : fuser:: ReplyAttr ,
46+ ) {
47+ self . count . fetch_add ( 1 , Ordering :: SeqCst ) ;
48+ if ino == 1 {
49+ // Root directory
50+ reply. attr (
51+ & Duration :: from_secs ( 1 ) ,
52+ & fuser:: FileAttr {
53+ ino : 1 ,
54+ size : 0 ,
55+ blocks : 0 ,
56+ atime : std:: time:: UNIX_EPOCH ,
57+ mtime : std:: time:: UNIX_EPOCH ,
58+ ctime : std:: time:: UNIX_EPOCH ,
59+ crtime : std:: time:: UNIX_EPOCH ,
60+ kind : fuser:: FileType :: Directory ,
61+ perm : 0o755 ,
62+ nlink : 2 ,
63+ uid : 0 ,
64+ gid : 0 ,
65+ rdev : 0 ,
66+ blksize : 4096 ,
67+ flags : 0 ,
68+ } ,
69+ ) ;
70+ } else {
71+ reply. error ( libc:: ENOENT ) ;
72+ }
73+ }
74+ }
75+
76+ let tmpdir: TempDir = tempfile:: tempdir ( ) . unwrap ( ) ;
77+ let count = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
78+
79+ let session = Session :: new (
80+ CountingFS {
81+ count : count. clone ( ) ,
82+ } ,
83+ tmpdir. path ( ) ,
84+ & [ ] ,
85+ )
86+ . unwrap ( ) ;
87+
88+ // Clone the fd - this should succeed
89+ let cloned_fd = session. clone_fd ( ) . expect ( "clone_fd should succeed" ) ;
90+
91+ // Verify it's a valid fd (different from the original)
92+ assert ! ( cloned_fd. as_raw_fd( ) >= 0 ) ;
93+
94+ // Clean up
95+ drop ( cloned_fd) ;
96+ drop ( session) ;
97+ }
98+
99+ /// Test that from_fd_initialized creates a session that can process requests.
100+ #[ cfg( target_os = "linux" ) ]
101+ #[ test]
102+ fn from_fd_initialized_works ( ) {
103+ use std:: sync:: Barrier ;
104+
105+ // Simple filesystem that responds to getattr
106+ #[ derive( Clone ) ]
107+ struct SimpleFS ;
108+
109+ impl Filesystem for SimpleFS {
110+ fn getattr (
111+ & mut self ,
112+ _req : & fuser:: Request < ' _ > ,
113+ ino : u64 ,
114+ _fh : Option < u64 > ,
115+ reply : fuser:: ReplyAttr ,
116+ ) {
117+ if ino == 1 {
118+ reply. attr (
119+ & Duration :: from_secs ( 1 ) ,
120+ & fuser:: FileAttr {
121+ ino : 1 ,
122+ size : 0 ,
123+ blocks : 0 ,
124+ atime : std:: time:: UNIX_EPOCH ,
125+ mtime : std:: time:: UNIX_EPOCH ,
126+ ctime : std:: time:: UNIX_EPOCH ,
127+ crtime : std:: time:: UNIX_EPOCH ,
128+ kind : fuser:: FileType :: Directory ,
129+ perm : 0o755 ,
130+ nlink : 2 ,
131+ uid : 0 ,
132+ gid : 0 ,
133+ rdev : 0 ,
134+ blksize : 4096 ,
135+ flags : 0 ,
136+ } ,
137+ ) ;
138+ } else {
139+ reply. error ( libc:: ENOENT ) ;
140+ }
141+ }
142+ }
143+
144+ let tmpdir: TempDir = tempfile:: tempdir ( ) . unwrap ( ) ;
145+
146+ let mut session = Session :: new ( SimpleFS , tmpdir. path ( ) , & [ ] ) . unwrap ( ) ;
147+ let mut unmounter = session. unmount_callable ( ) ;
148+
149+ // Clone fd for second reader
150+ let cloned_fd = session. clone_fd ( ) . expect ( "clone_fd should succeed" ) ;
151+
152+ // Barrier to synchronize reader threads
153+ let barrier = Arc :: new ( Barrier :: new ( 3 ) ) ; // 2 readers + 1 main thread
154+
155+ // Start second reader in a thread
156+ let barrier_clone = barrier. clone ( ) ;
157+ let reader_handle = thread:: spawn ( move || {
158+ let mut reader_session = Session :: from_fd_initialized ( SimpleFS , cloned_fd, SessionACL :: All ) ;
159+ barrier_clone. wait ( ) ; // Signal ready
160+ // Run until unmount
161+ let _ = reader_session. run ( ) ;
162+ } ) ;
163+
164+ // Start primary session in a thread
165+ let barrier_clone = barrier. clone ( ) ;
166+ let session_handle = thread:: spawn ( move || {
167+ barrier_clone. wait ( ) ; // Signal ready
168+ let _ = session. run ( ) ;
169+ } ) ;
170+
171+ // Wait for both readers to be ready
172+ barrier. wait ( ) ;
173+
174+ // Give readers time to start processing
175+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
176+
177+ // Access the mountpoint - this triggers FUSE requests
178+ let _ = std:: fs:: metadata ( tmpdir. path ( ) ) ;
179+
180+ // Unmount to stop the sessions
181+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
182+ unmounter. unmount ( ) . unwrap ( ) ;
183+
184+ // Wait for threads to finish
185+ let _ = session_handle. join ( ) ;
186+ let _ = reader_handle. join ( ) ;
187+ }
0 commit comments