///|
pub async fn Client::getattr(self : Client, handle : Handle) -> Attributes {
let w = @xdr.Writer::new()
put_handle(w, handle)
let r = self.invoke(1U, w)
let attrs = read_attributes(r)
r.finish()
attrs
}
///|
pub async fn Client::lookup(
self : Client,
directory : Handle,
name : String,
) -> Handle {
let (handle, _) = self.lookup_attributes(directory, name)
handle
}
///|
async fn Client::lookup_attributes(
self : Client,
directory : Handle,
name : String,
) -> (Handle, Attributes?) {
let w = @xdr.Writer::new()
put_handle(w, directory)
put_name(w, name)
let r = self.invoke(3U, w)
let handle = get_handle(r)
let attributes = post_attributes(r)
ignore(post_attributes(r))
r.finish()
(handle, attributes)
}
///|
/// Rejects parent traversal. Intermediate symbolic links are not followed.
pub fn path_components(path : String) -> Array[String] raise NfsError {
let result : Array[String] = []
for part in path.split("/") {
let name = part.to_owned()
if name == ".." || name.contains("\u{0000}") {
raise InvalidArgument("parent traversal or NUL in path")
}
if name != "" && name != "." {
result.push(name)
}
}
result
}
///|
pub async fn Client::resolve(self : Client, path : String) -> Handle {
let mut handle = self.root_handle()
for name in path_components(path) {
handle = self.lookup(handle, name)
}
handle
}
///|
pub async fn Client::stat(self : Client, path : String) -> Attributes {
let (_, attributes) = self.resolve_attributes(path)
attributes
}
///|
async fn Client::resolve_attributes(
self : Client,
path : String,
) -> (Handle, Attributes) {
let mut handle = self.root_handle()
let mut attributes : Attributes? = None
for name in path_components(path) {
let (next, current_attributes) = self.lookup_attributes(handle, name)
handle = next
attributes = current_attributes
}
// LOOKUP attributes describe only that reply, and are never cached.
let attributes = match attributes {
Some(value) => value
None => self.getattr(handle)
}
(handle, attributes)
}
///|
/// Sets transfer block sizes using FSINFO; no file data is cached.
pub async fn Client::negotiate(self : Client) -> Unit {
let info = self.fsinfo()
let rtmax = info.read_max
let wtmax = info.write_max
self.read_size = if rtmax < self.options.block_size.reinterpret_as_uint() {
rtmax.reinterpret_as_int()
} else {
self.options.block_size
}
self.write_size = if wtmax < self.options.block_size.reinterpret_as_uint() {
wtmax.reinterpret_as_int()
} else {
self.options.block_size
}
}
///|
pub(all) struct ReadResult {
data : Bytes
eof : Bool
}
///|
pub async fn Client::read_at(
self : Client,
handle : Handle,
offset : UInt64,
count : Int,
) -> ReadResult {
if count < 0 || count > self.read_size {
raise InvalidArgument("read exceeds negotiated block size")
}
ignore(checked_offset(offset, count))
let w = @xdr.Writer::new()
put_handle(w, handle)
w.write_uhyper(offset)
w.write_uint(count.reinterpret_as_uint())
let r = self.invoke(6U, w)
ignore(post_attributes(r))
let reported = r.read_uint()
let eof = r.read_bool()
let data = Bytes::from_array(r.read_opaque(bound=count.reinterpret_as_uint()))
r.finish()
if reported != data.length().reinterpret_as_uint() ||
(count > 0 && data.length() == 0 && !eof) {
raise ProtocolError("invalid READ length or zero progress")
}
{ data, eof, }
}
///|
pub async fn Client::readlink(self : Client, handle : Handle) -> Bytes {
let w = @xdr.Writer::new()
put_handle(w, handle)
let r = self.invoke(5U, w)
ignore(post_attributes(r))
let target = Bytes::from_array(r.read_opaque(bound=4096U))
r.finish()
target
}
///|
pub async fn Client::access(
self : Client,
handle : Handle,
requested : UInt,
) -> UInt {
if requested > 63U {
raise InvalidArgument("invalid access mask")
}
let w = @xdr.Writer::new()
put_handle(w, handle)
w.write_uint(requested)
let r = self.invoke(4U, w)
ignore(post_attributes(r))
let granted = r.read_uint()
r.finish()
if (granted & requested) != granted {
raise ProtocolError("unexpected access bits")
}
granted
}
///|
test "export-relative paths normalize without traversing parents" {
assert_eq(path_components("//a/./b/").join("/"), "a/b")
assert_eq(path_components("/").length(), 0)
try {
ignore(path_components("a/../b"))
fail("expected rejection")
} catch {
InvalidArgument(_) => ()
other => raise other
}
}