///|
fn path_prefix(prefix : String, path : String) -> Bool {
prefix == "/" || prefix == path || path.has_prefix(prefix + "/")
}
///|
pub fn Snapshot::namespace_id(self : Snapshot) -> String {
self.token
}
///|
/// Returns a copy: modifying the returned array cannot change the snapshot.
pub fn Snapshot::mounts(self : Snapshot) -> Array[Mount] {
self.entries.copy()
}
///|
pub fn Snapshot::get(self : Snapshot, id : Int32) -> Mount? {
self.index.get(id)
}
///|
pub fn Snapshot::children(self : Snapshot, id : Int32) -> Array[Mount] {
match self.child_index.get(id) {
Some(v) => v.copy()
None => []
}
}
///|
/// Self followed by recorded parents, including covered mounts. A missing outer parent stops traversal.
pub fn Snapshot::ancestors(self : Snapshot, id : Int32) -> Array[Mount] {
let out : Array[Mount] = []
let mut next = self.get(id)
while next is Some(m) {
out.push(m)
if m.id == m.parent_id {
break
}
next = self.get(m.parent_id)
}
out
}
///|
pub fn Snapshot::peers(self : Snapshot, group : Int32) -> Array[Mount] {
self.entries.filter(fn(m) { m.shared == Some(group) })
}
///|
/// Direct slave mounts whose master peer group is `group`.
pub fn Snapshot::slaves(self : Snapshot, group : Int32) -> Array[Mount] {
self.entries.filter(fn(m) { m.master == Some(group) })
}
///|
/// Resolve lexically through the *visible parent tree*, not global longest prefix.
/// Rejects ambiguous siblings and snapshots lacking a unique recorded root.
pub fn Snapshot::resolve(
self : Snapshot,
path : String,
) -> Resolution raise MountError {
if !valid_path(path) {
raise InvalidPath(path)
}
let roots = self.entries.filter(fn(m) {
m.point == "/" && (m.id == m.parent_id || !self.index.contains(m.parent_id))
})
if roots.length() != 1 {
raise Topology(reason="no unique namespace_id root")
}
let mut current = roots[0]
let chain = [current.id]
for ;; {
let mut next : Mount? = None
let mut ambiguous = false
for m in self.children(current.id) {
if !path_prefix(m.point, path) {
continue
}
match next {
None => {
next = Some(m)
ambiguous = false
}
Some(previous) =>
if m.point.length() < previous.point.length() {
next = Some(m)
ambiguous = false
} else if m.point.length() == previous.point.length() {
ambiguous = true
}
}
}
if ambiguous {
raise Topology(reason="ambiguous sibling mounts")
}
match next {
None => break
Some(m) => {
current = m
chain.push(m.id)
}
}
}
let suffix = if current.point == "/" {
path.strip_prefix("/").unwrap().to_owned()
} else if path == current.point {
""
} else {
path.strip_prefix(current.point + "/").unwrap().to_owned()
}
let filesystem_path = if !valid_path(current.root) {
None
} else if suffix == "" {
Some(current.root)
} else if current.root == "/" {
Some("/" + suffix)
} else {
Some(current.root + "/" + suffix)
}
{ mount: current, filesystem_path, chain, }
}
///|
/// Compare observations of the same namespace_id lifetime. IDs may be reused by Linux:
/// Replaced only means the observed (device, filesystem root/type/source) changed.
pub fn Snapshot::diff(
self : Snapshot,
newer : Snapshot,
) -> Array[Change] raise MountError {
if self.token != newer.token {
raise NamespaceMismatch
}
let changes : Array[Change] = []
for before in self.entries {
match newer.get(before.id) {
None =>
changes.push({ kind: Removed, before: Some(before), after: None, })
Some(after) => {
if before == after {
continue
}
let replaced = before.major != after.major ||
before.minor != after.minor ||
before.root != after.root ||
before.fs_type != after.fs_type ||
before.source != after.source
changes.push({
kind: if replaced {
Replaced
} else {
Changed
},
before: Some(before),
after: Some(after),
})
}
}
}
for after in newer.entries {
if !self.index.contains(after.id) {
changes.push({ kind: Added, before: None, after: Some(after), })
}
}
changes
}