pub const EXPORT_ROOTFS_PREFIX: &str = "/run/arcbox/nfs-export/docker/rootfs/overlayfs/";
#[must_use]
pub fn rootfs_mountpoints(mountinfo: &str) -> Vec<String> {
let mut found: Vec<String> = mountinfo
.lines()
.filter_map(parse_overlay_mountpoint)
.filter(|mountpoint| mountpoint.starts_with(EXPORT_ROOTFS_PREFIX))
.map(str::to_owned)
.collect();
found.sort();
found.dedup();
found
}
fn parse_overlay_mountpoint(line: &str) -> Option<&str> {
let (before, after) = line.split_once(" - ")?;
if after.split_whitespace().next()? != "overlay" {
return None;
}
before.split_whitespace().nth(4)
}
#[must_use]
pub fn fsid_for(mountpoint: &str) -> String {
use std::fmt::Write as _;
use sha2::{Digest, Sha256};
let digest = Sha256::digest(mountpoint.as_bytes());
let mut hex = String::with_capacity(32);
for byte in digest.iter().take(16) {
let _ = write!(hex, "{byte:02x}");
}
format!(
"{}-{}-{}-{}-{}",
&hex[0..8],
&hex[8..12],
&hex[12..16],
&hex[16..20],
&hex[20..32]
)
}
#[cfg(test)]
mod tests {
use super::*;
const MOUNTINFO: &str = "\
25 24 0:23 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
71 30 0:52 / /var/lib/docker/rootfs/overlayfs/9aa873fd rw,relatime - overlay overlay rw,lowerdir=/a:/b,upperdir=/c,workdir=/d,index=on,nfs_export=on
72 31 0:52 / /run/arcbox/nfs-export/docker/rootfs/overlayfs/9aa873fd rw,relatime - overlay overlay rw,lowerdir=/a:/b,upperdir=/c,workdir=/d,index=on,nfs_export=on
73 31 0:11 / /run/arcbox/nfs-export/docker/containerd ro,noatime - btrfs /dev/vdb ro,subvol=/@containerd
";
#[test]
fn only_overlays_inside_the_export_root_are_returned() {
assert_eq!(
rootfs_mountpoints(MOUNTINFO),
vec!["/run/arcbox/nfs-export/docker/rootfs/overlayfs/9aa873fd".to_owned()]
);
}
#[test]
fn optional_fields_do_not_shift_the_fstype() {
let with_optionals = "\
72 31 0:52 / /run/arcbox/nfs-export/docker/rootfs/overlayfs/abc rw,relatime shared:9 master:2 - overlay overlay rw,index=on
";
assert_eq!(
rootfs_mountpoints(with_optionals),
vec!["/run/arcbox/nfs-export/docker/rootfs/overlayfs/abc".to_owned()]
);
}
#[test]
fn the_fstype_decides_not_the_source_string() {
let misleading = "\
74 31 0:11 / /run/arcbox/nfs-export/docker/rootfs/overlayfs/abc ro,noatime - btrfs /dev/overlay ro
";
assert!(rootfs_mountpoints(misleading).is_empty());
}
#[test]
fn fsid_is_uuid_shaped_stable_and_distinct() {
let one = fsid_for("/run/arcbox/nfs-export/docker/rootfs/overlayfs/aaa");
let two = fsid_for("/run/arcbox/nfs-export/docker/rootfs/overlayfs/bbb");
assert_eq!(
one,
fsid_for("/run/arcbox/nfs-export/docker/rootfs/overlayfs/aaa")
);
assert_ne!(one, two);
assert_eq!(one.len(), 36);
assert_eq!(
one.split('-').map(str::len).collect::<Vec<_>>(),
vec![8, 4, 4, 4, 12]
);
assert!(one.chars().all(|c| c.is_ascii_hexdigit() || c == '-'));
}
#[test]
fn a_derived_fsid_never_collides_with_the_static_exports() {
for id in ["/x", "/y", EXPORT_ROOTFS_PREFIX] {
let fsid = fsid_for(id);
assert_ne!(fsid, "0");
assert_ne!(fsid, "1");
}
}
}