Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
a86933b
prepare rootfs for loading zfs kernel module
nohajc Sep 19, 2025
9d82a5b
update download-dedpendencies.sh
nohajc Sep 19, 2025
6208613
vmproxy: zpool import/export
nohajc Sep 20, 2025
2a432c7
fix zpool count parsing
nohajc Sep 20, 2025
3039a7d
need to trim zpool name too
nohajc Sep 20, 2025
ecc1421
need to export all zfs datasets
nohajc Sep 20, 2025
c8070d5
need to debug this
nohajc Sep 20, 2025
76522a2
deduplicate zfs dataset mount points
nohajc Sep 20, 2025
5f8c435
we need to parse `zpool import` output after all
nohajc Sep 20, 2025
44b615b
determine correct order before mounting zpool datasets
nohajc Sep 20, 2025
05b6eb8
disable debug prints, rename zfs_member to zfs
nohajc Sep 20, 2025
361e1e3
only prepare mnt_args when needed
nohajc Sep 20, 2025
fea5a7f
zfs import/mount error handling
nohajc Sep 20, 2025
38c1837
split zpool import and zfs mount operations
nohajc Sep 20, 2025
a735325
custom actions: added before_mount hook
nohajc Sep 20, 2025
de309f7
allow defining environment variables in custom action config
nohajc Sep 20, 2025
8549318
custom actions: fix undefined vars detection
nohajc Sep 20, 2025
8e11011
better error message when statfs fails
nohajc Sep 20, 2025
4325696
fix zfs mountpoint list filtering
nohajc Sep 20, 2025
5cc54e8
`anylinuxfs init`: install kernel modules from squashfs archive
nohajc Sep 21, 2025
607af89
NFSv4 shouldn't be used for everything because of the problems with u…
nohajc Sep 22, 2025
367727b
use NFSv4 when the filesystem is zfs
nohajc Sep 24, 2025
9f54497
error handling
nohajc Sep 24, 2025
f7fe857
if we chown the mountpoint to invoker uid/gid, it does exactly what w…
nohajc Sep 24, 2025
8a0041b
update LINUX_LABELS
nohajc Sep 25, 2025
6524265
error handling must be improved
nohajc Sep 25, 2025
31d775c
always use zfs_root label
nohajc Sep 25, 2025
8cfbaa2
we actually don't want StatusError here
nohajc Sep 25, 2025
b3b31fc
fix read-only zfs mount
nohajc Sep 25, 2025
f3bd742
fix read-only mode export
nohajc Sep 25, 2025
b354aae
make sure each fsid is unique
nohajc Sep 25, 2025
825ba63
disable NFSv4
nohajc Oct 17, 2025
34a0dd6
collect list of all NFS exports from the VM
nohajc Oct 17, 2025
ba5b573
make the exports list sorted
nohajc Oct 17, 2025
1497e40
automatically mount all NFS exports
nohajc Oct 17, 2025
e6a885a
fix order of mount operations
nohajc Oct 17, 2025
a4c5677
fix subdir mount paths
nohajc Oct 17, 2025
058abd6
debug nfs mount commands
nohajc Oct 17, 2025
4b06181
trim leading slash from subdir_relative
nohajc Oct 17, 2025
e1fdc29
construct a trie from NFS subdirs
nohajc Oct 18, 2025
e0592b7
formatting
nohajc Oct 18, 2025
8218f28
store absolute mount point paths in the trie
nohajc Oct 18, 2025
6cff380
implemented parallel mount
nohajc Oct 18, 2025
233d29b
only log the mount point paths
nohajc Oct 18, 2025
7dc18b5
removed TODO comment
nohajc Oct 18, 2025
66df8c7
unmount command (wip)
nohajc Oct 18, 2025
ebc9b3d
added parallel unmount
nohajc Oct 18, 2025
fb268a6
simplified unmount error handling
nohajc Oct 18, 2025
314cc80
validate unmount path if specified
nohajc Oct 18, 2025
bba1e73
helper for properly printing (and logging) child process output
nohajc Oct 18, 2025
ba9c606
make sure the mount error is logged
nohajc Oct 18, 2025
3591410
is this enough?
nohajc Oct 18, 2025
3a94e51
fix sudo flags
nohajc Oct 18, 2025
d85e443
sudo is problematic with stdin redirect to VM, try osascript
nohajc Oct 18, 2025
a9db7d2
try to fix escaping
nohajc Oct 18, 2025
cab532e
back to sudo
nohajc Oct 18, 2025
fdb9222
maybe this could fix it?
nohajc Oct 18, 2025
c2e01c7
maybe we can just do this since we are no longer in raw terminal mode
nohajc Oct 18, 2025
bd45e8d
`anylinuxfs unmount`: accept partial disk_path match
nohajc Oct 18, 2025
f3d86e2
NFSv4 will not be used at all
nohajc Oct 18, 2025
2e51d70
optimization: we don't need to collect additional_exports into a vector
nohajc Oct 18, 2025
6681c87
added global config.toml in etc which will be used for pre-defined cu…
nohajc Oct 18, 2025
ab849b6
fix bug in preferences merging
nohajc Oct 18, 2025
0ce3382
use the real mount point from disk arbitration as mnt_point_base for …
nohajc Oct 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions anylinuxfs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions anylinuxfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ if-addrs = "0.13.4"
crossterm = "0.29.0"
objc2-system-configuration = "0.3.1"
os_socketaddr = "0.2.5"
rayon = "1.11.0"

[patch.crates-io]
libblkid-rs = { git = 'https://github.com/stratis-storage/libblkid-rs.git', rev = "5c08342" }
2 changes: 2 additions & 0 deletions anylinuxfs/src/diskutil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ pub const LINUX_LABELS: Labels = Labels {
"Linux_LVM",
"Linux_RAID",
"Linux",
"ZFS",
]),
fs_types: FsTypes(&[
"btrfs",
Expand All @@ -168,6 +169,7 @@ pub const LINUX_LABELS: Labels = Labels {
"zfs",
"crypto_LUKS",
"LVM2_member",
"zfs_member",
]),
};

Expand Down
157 changes: 156 additions & 1 deletion anylinuxfs/src/fsutil.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use anyhow::anyhow;
use common_utils::host_println;
use rayon::prelude::*;
use std::{
collections::HashSet,
ffi::{CStr, CString, OsStr, OsString},
io, mem,
os::unix::ffi::OsStrExt,
path::{Path, PathBuf},
process::Command,
ptr::null_mut,
};

#[derive(Debug, Clone)]
pub struct MountTable {
disks: HashSet<OsString>,
mount_points: HashSet<OsString>,
}

impl MountTable {
Expand All @@ -32,6 +37,7 @@ impl MountTable {
}

let mut disks = HashSet::new();
let mut mount_points = HashSet::new();
for buf in mounts_raw {
let mntfromname = os_str_from_c_chars(&buf.f_mntfromname).to_owned();
let mntonname = os_str_from_c_chars(&buf.f_mntonname).to_owned();
Expand All @@ -40,15 +46,23 @@ impl MountTable {

if !mntfromname.is_empty() && !mntonname.is_empty() {
disks.insert(mntfromname);
mount_points.insert(mntonname);
}
}
Ok(MountTable { disks })
Ok(MountTable {
disks,
mount_points,
})
}

pub fn is_mounted(&self, path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
self.disks.contains(path.as_os_str())
}

pub fn mount_points(&self) -> impl Iterator<Item = &OsString> {
self.mount_points.iter()
}
}

pub fn mounted_from(path: impl AsRef<Path>) -> io::Result<PathBuf> {
Expand Down Expand Up @@ -81,3 +95,144 @@ fn os_str_from_c_chars(chars: &[i8]) -> &OsStr {
let cstr = unsafe { CStr::from_ptr(chars.as_ptr()) };
OsStr::from_bytes(cstr.to_bytes())
}

mod dirtrie {
use std::{collections::BTreeMap, ffi::OsString, fmt::Display, path::Path};

#[derive(Debug, Default)]
pub struct Node {
pub paths: Option<(OsString, String)>,
pub children: BTreeMap<OsString, Node>,
}

impl Node {
pub fn insert(&mut self, path: &Path, full_path: &str) {
let mut current = self;
for segment in path.components() {
let segment = segment.as_os_str().to_owned();
current = current.children.entry(segment).or_default();
}
current.paths = Some((path.as_os_str().to_owned(), full_path.to_owned()));
}
}

impl Display for Node {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt_node(
node: &Node,
f: &mut std::fmt::Formatter<'_>,
prefix: &str,
) -> std::fmt::Result {
for (segment, child) in &node.children {
write!(
f,
"{}{} ({})\r\n",
prefix,
segment.to_string_lossy(),
child
.paths
.as_ref()
.map(|(_, p)| p.clone())
.unwrap_or("".to_owned())
)?;
fmt_node(child, f, &format!("{}--", prefix))?;
}
Ok(())
}
fmt_node(self, f, "")
}
}
}

fn parallel_mount_recursive(
mnt_point_base: PathBuf,
trie: &dirtrie::Node,
elevate: bool,
) -> anyhow::Result<()> {
if let Some((rel_path, nfs_path)) = &trie.paths {
let shell_script = format!(
"mount -t nfs \"localhost:{}\" \"{}\"",
nfs_path,
mnt_point_base.join(rel_path).display()
);
// host_println!("Running NFS mount command: `{}`", &shell_script);

// elevate if needed (e.g. mounting image under /Volumes)
let cmdline = ["sudo", "-S", "sh", "-c", &shell_script];
let cmdline = if elevate { &cmdline[..] } else { &cmdline[2..] };
let status = Command::new(cmdline[0]).args(&cmdline[1..]).status()?;

if !status.success() {
return Err(anyhow!(
"mount failed with exit code {}",
status
.code()
.map(|c| c.to_string())
.unwrap_or("unknown".to_owned())
));
}
host_println!(
"Mounted subdirectory: {}",
mnt_point_base.join(rel_path).display()
);
}
trie.children.par_iter().try_for_each(|(_, child)| {
parallel_mount_recursive(mnt_point_base.clone(), child, elevate)
})?;

Ok(())
}

pub fn mount_nfs_subdirs<'a>(
share_path_base: &str,
subdirs: impl Iterator<Item = &'a str>,
mnt_point_base: impl AsRef<Path>,
elevate: bool,
) -> anyhow::Result<()> {
let mut trie = dirtrie::Node::default();

for subdir in subdirs {
let subdir_relative = subdir
.trim_start_matches(share_path_base)
.trim_start_matches('/');

trie.insert(Path::new(subdir_relative), subdir);
}

parallel_mount_recursive(mnt_point_base.as_ref().into(), &trie, elevate)?;
// host_println!("Mounted NFS subdirectories:\r\n{}", trie);
Ok(())
}

fn parallel_unmount_recursive(trie: &dirtrie::Node) -> anyhow::Result<()> {
trie.children
.par_iter()
.try_for_each(|(_, child)| parallel_unmount_recursive(child))?;

if let Some((_, mount_path)) = &trie.paths {
let shell_script = format!("diskutil unmount \"{}\"", mount_path);
// host_println!("Running NFS unmount command: `{}`", &shell_script);
// exit status ignored, we don't want to exit early if one unmount fails
let _ = Command::new("sh").arg("-c").arg(&shell_script).status()?;
}
Ok(())
}

pub fn unmount_nfs_subdirs<'a>(
subdirs: impl Iterator<Item = &'a OsStr>,
mnt_point_base: impl AsRef<Path>,
) -> anyhow::Result<()> {
let mut trie = dirtrie::Node::default();

for subdir in subdirs {
let subdir = subdir.to_string_lossy();
let subdir_relative = subdir
.trim_start_matches(&*mnt_point_base.as_ref().to_string_lossy())
.trim_start_matches('/');

trie.insert(Path::new(subdir_relative), &subdir);
}

parallel_unmount_recursive(&trie)?;
Ok(())
}
Loading