Skip to content

Commit 8bb409b

Browse files
RoyLinRoyLin
authored andcommitted
fix(snapshot): store virtio-fs inode paths relative to fs root, rejoin on restore
The template and each fork mount their rootfs at different host overlay dirs, and the template's dir is removed before restore — so absolute saved paths failed to reopen (0 inodes restored). Store each path relative to the root inode's canonical path; on restore rejoin against this fork's root_dir.
1 parent d288842 commit 8bb409b

1 file changed

Lines changed: 43 additions & 8 deletions

File tree

src/devices/src/virtio/fs/linux/passthrough.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)