@@ -8,7 +8,7 @@ use std::sync::Arc;
88use std:: time:: { Duration , SystemTime , UNIX_EPOCH } ;
99
1010use fuser:: {
11- BackingId , FileType , Filesystem , ReplyAttr , ReplyData , ReplyDirectory , ReplyEmpty , ReplyEntry ,
11+ FileType , Filesystem , ReplyAttr , ReplyData , ReplyDirectory , ReplyEmpty , ReplyEntry ,
1212 ReplyIoctl , ReplyOpen , ReplyStatfs , ReplyWrite , Request , TimeOrNow ,
1313} ;
1414use parking_lot:: RwLock ;
@@ -18,17 +18,14 @@ use crate::error::BranchError;
1818use crate :: fs_path:: { classify_path, PathContext } ;
1919use crate :: inode:: { InodeManager , ROOT_INO } ;
2020use crate :: storage;
21+ use crate :: platform:: { FS_IOC_BRANCH_ABORT , FS_IOC_BRANCH_COMMIT , FS_IOC_BRANCH_CREATE } ;
2122
2223// Zero TTL forces the kernel to always revalidate with FUSE, ensuring consistent
2324// behavior after branch switches. This is important for speculative execution
2425// where branches can change at any time.
2526pub ( crate ) const TTL : Duration = Duration :: from_secs ( 0 ) ;
2627pub ( crate ) const BLOCK_SIZE : u32 = 512 ;
2728
28- pub const FS_IOC_BRANCH_CREATE : u32 = 0x8080_6200 ; // _IOR('b', 0, [u8; 128])
29- pub const FS_IOC_BRANCH_COMMIT : u32 = 0x0000_6201 ; // _IO ('b', 1)
30- pub const FS_IOC_BRANCH_ABORT : u32 = 0x0000_6202 ; // _IO ('b', 2)
31-
3229pub ( crate ) const CTL_FILE : & str = ".branchfs_ctl" ;
3330pub ( crate ) const CTL_INO : u64 = u64:: MAX - 1 ;
3431
@@ -132,10 +129,8 @@ pub struct BranchFs {
132129 write_cache : WriteFileCache ,
133130 /// Whether FUSE passthrough mode is enabled (--passthrough flag).
134131 passthrough_enabled : bool ,
135- /// Monotonically increasing file handle counter for passthrough opens.
136- next_fh : AtomicU64 ,
137- /// BackingId objects kept alive until release() — one per passthrough open().
138- backing_ids : HashMap < u64 , BackingId > ,
132+ /// FUSE passthrough state (fh counter, backing_ids)
133+ passthrough_state : crate :: platform:: PassthroughState ,
139134}
140135
141136impl BranchFs {
@@ -155,8 +150,7 @@ impl BranchFs {
155150 open_cache : OpenFileCache :: new ( ) ,
156151 write_cache : WriteFileCache :: new ( ) ,
157152 passthrough_enabled : passthrough,
158- next_fh : AtomicU64 :: new ( 1 ) ,
159- backing_ids : HashMap :: new ( ) ,
153+ passthrough_state : crate :: platform:: PassthroughState :: new ( ) ,
160154 }
161155 }
162156
@@ -228,7 +222,6 @@ impl BranchFs {
228222 /// Attempt to open a file with FUSE passthrough. Falls back to non-passthrough on failure.
229223 fn try_open_passthrough (
230224 & mut self ,
231- _ino : u64 ,
232225 flags : i32 ,
233226 branch : & str ,
234227 rel_path : & str ,
@@ -237,13 +230,10 @@ impl BranchFs {
237230 ) {
238231 let is_writable = ( flags & libc:: O_ACCMODE ) != libc:: O_RDONLY ;
239232
240- // For writable opens, do eager COW — the kernel will write directly to
241- // the backing file, bypassing our write() callback.
242233 let backing_path = if is_writable {
243234 match self . ensure_cow_for_branch ( branch, rel_path) {
244235 Ok ( p) => p,
245236 Err ( _) => {
246- // Fallback to non-passthrough
247237 reply. opened ( 0 , 0 ) ;
248238 return ;
249239 }
@@ -252,7 +242,6 @@ impl BranchFs {
252242 resolved. to_path_buf ( )
253243 } ;
254244
255- // Open the backing file
256245 let open_result = if is_writable {
257246 std:: fs:: OpenOptions :: new ( )
258247 . read ( true )
@@ -261,26 +250,11 @@ impl BranchFs {
261250 } else {
262251 File :: open ( & backing_path)
263252 } ;
264- let file = match open_result {
265- Ok ( f) => f,
266- Err ( _) => {
267- reply. opened ( 0 , 0 ) ;
268- return ;
269- }
270- } ;
271-
272- // Register the fd with the kernel
273- let backing_id = match reply. open_backing ( & file) {
274- Ok ( id) => id,
275- Err ( _) => {
276- reply. opened ( 0 , 0 ) ;
277- return ;
278- }
279- } ;
280-
281- let fh = self . next_fh . fetch_add ( 1 , Ordering :: Relaxed ) ;
282- reply. opened_passthrough ( fh, 0 , & backing_id) ;
283- self . backing_ids . insert ( fh, backing_id) ;
253+
254+ match open_result {
255+ Ok ( f) => crate :: platform:: try_open_passthrough ( & mut self . passthrough_state , f, reply) ,
256+ Err ( _) => reply. opened ( 0 , 0 ) ,
257+ }
284258 }
285259
286260 /// Classify an inode number. Returns None for root and CTL_INO (handled separately).
@@ -311,21 +285,7 @@ impl Filesystem for BranchFs {
311285 }
312286
313287 if self . passthrough_enabled {
314- if let Err ( e) = config. add_capabilities ( fuser:: consts:: FUSE_PASSTHROUGH ) {
315- log:: warn!(
316- "Kernel does not support FUSE_PASSTHROUGH (unsupported bits: {:#x}), disabling" ,
317- e
318- ) ;
319- self . passthrough_enabled = false ;
320- } else if let Err ( e) = config. set_max_stack_depth ( 2 ) {
321- log:: warn!(
322- "Failed to set max_stack_depth (max: {}), disabling passthrough" ,
323- e
324- ) ;
325- self . passthrough_enabled = false ;
326- } else {
327- log:: info!( "FUSE passthrough enabled" ) ;
328- }
288+ crate :: platform:: setup_capabilities ( config, & mut self . passthrough_enabled ) ;
329289 }
330290
331291 Ok ( ( ) )
@@ -1164,11 +1124,11 @@ impl Filesystem for BranchFs {
11641124 name : & OsStr ,
11651125 newparent : u64 ,
11661126 newname : & OsStr ,
1167- flags : u32 ,
1127+ _flags : u32 ,
11681128 reply : ReplyEmpty ,
11691129 ) {
1170- if flags & libc :: RENAME_EXCHANGE != 0 {
1171- reply. error ( libc :: EINVAL ) ;
1130+ if let Err ( e ) = crate :: platform :: check_rename_flags ( _flags ) {
1131+ reply. error ( e ) ;
11721132 return ;
11731133 }
11741134
@@ -1254,7 +1214,7 @@ impl Filesystem for BranchFs {
12541214 }
12551215
12561216 // RENAME_NOREPLACE
1257- if flags & libc :: RENAME_NOREPLACE != 0
1217+ if crate :: platform :: check_rename_noreplace ( _flags )
12581218 && self . resolve_for_branch ( & branch, & dst_rel) . is_some ( )
12591219 {
12601220 reply. error ( libc:: EEXIST ) ;
@@ -1330,7 +1290,7 @@ impl Filesystem for BranchFs {
13301290 reply. ok ( ) ;
13311291 }
13321292
1333- fn open ( & mut self , _req : & Request , ino : u64 , flags : i32 , reply : ReplyOpen ) {
1293+ fn open ( & mut self , _req : & Request , ino : u64 , _flags : i32 , reply : ReplyOpen ) {
13341294 // Control file is always openable (no epoch check)
13351295 if ino == CTL_INO {
13361296 reply. opened ( 0 , 0 ) ;
@@ -1363,7 +1323,7 @@ impl Filesystem for BranchFs {
13631323 reply. error ( libc:: ENOENT ) ;
13641324 return ;
13651325 }
1366- let resolved = match self . resolve_for_branch ( & branch, & rel_path) {
1326+ let _resolved = match self . resolve_for_branch ( & branch, & rel_path) {
13671327 Some ( p) => p,
13681328 None => {
13691329 reply. error ( libc:: ENOENT ) ;
@@ -1373,7 +1333,7 @@ impl Filesystem for BranchFs {
13731333 self . manager . register_opened_inode ( & branch, ino) ;
13741334
13751335 if self . passthrough_enabled {
1376- self . try_open_passthrough ( ino , flags , & branch, & rel_path, & resolved , reply) ;
1336+ self . try_open_passthrough ( _flags , & branch, & rel_path, & _resolved , reply) ;
13771337 } else {
13781338 reply. opened ( 0 , 0 ) ;
13791339 }
@@ -1384,7 +1344,7 @@ impl Filesystem for BranchFs {
13841344 reply. error ( libc:: ESTALE ) ;
13851345 return ;
13861346 }
1387- let resolved = match self . resolve ( & path) {
1347+ let _resolved = match self . resolve ( & path) {
13881348 Some ( p) => p,
13891349 None => {
13901350 reply. error ( libc:: ENOENT ) ;
@@ -1395,7 +1355,7 @@ impl Filesystem for BranchFs {
13951355 self . manager . register_opened_inode ( & branch_name, ino) ;
13961356
13971357 if self . passthrough_enabled {
1398- self . try_open_passthrough ( ino , flags , & branch_name, & path, & resolved , reply) ;
1358+ self . try_open_passthrough ( _flags , & branch_name, & path, & _resolved , reply) ;
13991359 } else {
14001360 reply. opened ( 0 , 0 ) ;
14011361 }
@@ -1414,7 +1374,7 @@ impl Filesystem for BranchFs {
14141374 reply : ReplyEmpty ,
14151375 ) {
14161376 if fh != 0 {
1417- self . backing_ids . remove ( & fh) ;
1377+ crate :: platform :: release_passthrough ( & mut self . passthrough_state , fh) ;
14181378 }
14191379 reply. ok ( ) ;
14201380 }
@@ -1794,6 +1754,10 @@ impl Filesystem for BranchFs {
17941754 }
17951755 }
17961756
1757+ fn access ( & mut self , _req : & Request , _ino : u64 , _mask : i32 , reply : ReplyEmpty ) {
1758+ crate :: platform:: handle_access ( reply) ;
1759+ }
1760+
17971761 fn symlink (
17981762 & mut self ,
17991763 _req : & Request ,
@@ -1909,11 +1873,11 @@ impl Filesystem for BranchFs {
19091873 match nix:: sys:: statvfs:: statvfs ( storage_path) {
19101874 Ok ( stat) => {
19111875 reply. statfs (
1912- stat. blocks ( ) ,
1913- stat. blocks_free ( ) ,
1914- stat. blocks_available ( ) ,
1915- stat. files ( ) ,
1916- stat. files_free ( ) ,
1876+ stat. blocks ( ) . into ( ) ,
1877+ stat. blocks_free ( ) . into ( ) ,
1878+ stat. blocks_available ( ) . into ( ) ,
1879+ stat. files ( ) . into ( ) ,
1880+ stat. files_free ( ) . into ( ) ,
19171881 stat. block_size ( ) as u32 ,
19181882 stat. name_max ( ) as u32 ,
19191883 stat. fragment_size ( ) as u32 ,
0 commit comments