@@ -428,28 +428,56 @@ impl PassthroughFs {
428428 pub fn save_fs_state ( & self ) -> Vec < u8 > {
429429 use std:: os:: unix:: ffi:: OsStrExt ;
430430 let inodes = self . inodes . read ( ) . unwrap ( ) ;
431+
432+ // Paths are stored RELATIVE to the fs root: the template and each fork mount
433+ // their rootfs at DIFFERENT host paths (per-box overlay dir), and the
434+ // template's dir is gone by restore time. The canonical root is the root
435+ // inode's own readlink (handles symlinks/trailing slashes in cfg.root_dir).
436+ let root_path = inodes. get ( & fuse:: ROOT_ID ) . and_then ( |d| {
437+ std:: fs:: read_link ( format ! ( "/proc/self/fd/{}" , d. file. as_raw_fd( ) ) ) . ok ( )
438+ } ) ;
439+
431440 let mut out = Vec :: new ( ) ;
432441 out. extend_from_slice ( & self . next_inode . load ( Ordering :: Relaxed ) . to_le_bytes ( ) ) ;
433442 out. extend_from_slice ( & self . next_handle . load ( Ordering :: Relaxed ) . to_le_bytes ( ) ) ;
434443 let count_pos = out. len ( ) ;
435444 out. extend_from_slice ( & 0u64 . to_le_bytes ( ) ) ;
436445 let mut count: u64 = 0 ;
437446 for ( nodeid, data) in inodes. iter ( ) {
438- let link = format ! ( "/proc/self/fd/{}" , data. file. as_raw_fd( ) ) ;
439- let path = match std:: fs:: read_link ( & link) {
447+ let path = match std:: fs:: read_link ( format ! (
448+ "/proc/self/fd/{}" ,
449+ data. file. as_raw_fd( )
450+ ) ) {
440451 Ok ( p) => p,
441452 Err ( _) => continue ,
442453 } ;
443- let path_bytes = path. as_os_str ( ) . as_bytes ( ) ;
454+ let full = path. as_os_str ( ) . as_bytes ( ) ;
444455 // Unlinked-but-open files read back as "<path> (deleted)" and cannot be
445456 // reopened by path; skip them — the guest will re-LOOKUP if needed.
446- if path_bytes . ends_with ( b" (deleted)" ) || path_bytes . contains ( & 0 ) {
457+ if full . ends_with ( b" (deleted)" ) || full . contains ( & 0 ) {
447458 continue ;
448459 }
460+ // Relativize against the root path. The root inode itself stores "".
461+ let rel: & [ u8 ] = match & root_path {
462+ Some ( root) => {
463+ let root_b = root. as_os_str ( ) . as_bytes ( ) ;
464+ if full == root_b {
465+ b""
466+ } else if full. len ( ) > root_b. len ( )
467+ && full. starts_with ( root_b)
468+ && full[ root_b. len ( ) ] == b'/'
469+ {
470+ & full[ root_b. len ( ) + 1 ..]
471+ } else {
472+ continue ; // outside the fs root — not reconstructible
473+ }
474+ }
475+ None => continue ,
476+ } ;
449477 out. extend_from_slice ( & ( * nodeid) . to_le_bytes ( ) ) ;
450478 out. extend_from_slice ( & data. refcount . load ( Ordering :: Relaxed ) . to_le_bytes ( ) ) ;
451- out. extend_from_slice ( & ( path_bytes . len ( ) as u32 ) . to_le_bytes ( ) ) ;
452- out. extend_from_slice ( path_bytes ) ;
479+ out. extend_from_slice ( & ( rel . len ( ) as u32 ) . to_le_bytes ( ) ) ;
480+ out. extend_from_slice ( rel ) ;
453481 count += 1 ;
454482 }
455483 out[ count_pos..count_pos + 8 ] . copy_from_slice ( & count. to_le_bytes ( ) ) ;
@@ -482,9 +510,16 @@ impl PassthroughFs {
482510 if off + plen > blob. len ( ) {
483511 break ;
484512 }
485- let path = & blob[ off..off + plen] ;
513+ let rel = & blob[ off..off + plen] ;
486514 off += plen;
487- let cpath = match CString :: new ( path) {
515+ // Rejoin the relative path against THIS fork's rootfs mount (a different
516+ // host dir than the template's). Empty rel == the root inode itself.
517+ let mut full = self . cfg . root_dir . clone ( ) . into_bytes ( ) ;
518+ if !rel. is_empty ( ) {
519+ full. push ( b'/' ) ;
520+ full. extend_from_slice ( rel) ;
521+ }
522+ let cpath = match CString :: new ( full) {
488523 Ok ( c) => c,
489524 Err ( _) => continue ,
490525 } ;
0 commit comments