///|
pub(all) struct TreeTransfer {
files : Int
directories : Int
symlinks : Int
bytes : UInt64
}
///|
fn require_copy_kinds(entries : Array[TreeEntry]) -> Unit raise NfsError {
for entry in entries {
if entry.attributes.kind != 1U &&
entry.attributes.kind != 2U &&
entry.attributes.kind != 5U {
raise InvalidArgument(
"tree copy does not copy special devices or sockets",
)
}
}
}
///|
fn require_disjoint_destination(
source : String,
destination : String,
) -> Unit raise {
let source = normalize_path(source)
let destination = normalize_path(destination)
if source == "/" ||
destination == source ||
destination.has_prefix(source + "/") {
raise InvalidArgument("copy destination must be outside source subtree")
}
if destination == "/" {
raise InvalidArgument("cannot replace export root")
}
}
///|
pub async fn Client::copy_tree(
self : Client,
source : String,
destination : String,
preserve_mode? : Bool = true,
) -> TreeTransfer {
require_disjoint_destination(source, destination)
self.copy_tree_to(self, source, destination, preserve_mode~)
}
///|
/// Collects and validates the traversal before the first deletion. Never follows links.
pub async fn Client::remove_tree(self : Client, path : String) -> Int {
if path_components(path).length() == 0 {
raise InvalidArgument("cannot remove export root")
}
let entries = self.collect_tree(path)
let mut removed = 0
for i = entries.length() - 1; i >= 0; i = i - 1 {
let entry = entries[i]
let current = self.stat(entry.path)
if current.fsid != entry.attributes.fsid ||
current.fileid != entry.attributes.fileid ||
current.kind != entry.attributes.kind {
raise ProtocolError("tree entry was replaced before removal")
}
self.remove(entry.path, directory=entry.attributes.kind == 2U)
removed += 1
}
removed
}
///|
pub(all) enum TreeDifferenceKind {
MissingLeft
MissingRight
DifferentType
DifferentSize
DifferentContent
DifferentMode
DifferentLinkTarget
}
///|
pub(all) struct TreeDifference {
relative : String
kind : TreeDifferenceKind
}
///|
pub async fn Client::compare_trees(
self : Client,
left : String,
right : String,
compare_mode? : Bool = false,
) -> Array[TreeDifference] {
let first = self.collect_tree(left)
let second = self.collect_tree(right)
let by_path : Map[String, TreeEntry] = Map([])
let visited : Map[String, Bool] = Map([])
let result : Array[TreeDifference] = []
for entry in second {
by_path[entry.relative] = entry
}
for entry in first {
visited[entry.relative] = true
match by_path.get(entry.relative) {
None => result.push({ relative: entry.relative, kind: MissingRight, })
Some(other) => {
let relative = entry.relative
let a = entry.attributes
let b = other.attributes
if a.kind != b.kind {
result.push({ relative, kind: DifferentType, })
continue
}
if compare_mode && (a.mode & 4095U) != (b.mode & 4095U) {
result.push({ relative, kind: DifferentMode, })
}
match a.kind {
1U =>
if a.size != b.size {
result.push({ relative, kind: DifferentSize, })
} else if !self.compare_files(entry.path, other.path).equal {
result.push({ relative, kind: DifferentContent, })
}
5U =>
if self.readlink(entry.handle) != self.readlink(other.handle) {
result.push({ relative, kind: DifferentLinkTarget, })
}
2U => ()
_ =>
raise InvalidArgument("comparison of special nodes is unsupported")
}
}
}
}
for entry in second {
if !visited.contains(entry.relative) {
result.push({ relative: entry.relative, kind: MissingLeft, })
}
}
result
}
///|
pub fn TreeDifferenceKind::name(self : TreeDifferenceKind) -> String {
match self {
MissingLeft => "missing-left"
MissingRight => "missing-right"
DifferentType => "type"
DifferentSize => "size"
DifferentContent => "content"
DifferentMode => "mode"
DifferentLinkTarget => "link-target"
}
}