///|
priv enum FindMetadata[T] {
Unloaded
Available(T)
Unavailable(@os_error.OSError)
}
///|
async fn find_lookup_path(path : String) -> String {
if !(@async.platform is Windows) && path.has_suffix("/") {
if @fs.kind(path) != Directory {
raise @os_error.OSError(@os_error.errno_ENOTDIR, context=path)
}
@fs.realpath(path)
} else {
path
}
}
///|
fn is_link_loop(error : @os_error.OSError) -> Bool {
let @os_error.OSError(code, ..) = error
match @async.platform {
Linux => code == 40
MacOS => code == 62
Windows => code == 1921
}
}
///|
fn find_os_message(error : @os_error.OSError) -> String {
if error.is_ENOENT() {
"No such file or directory"
} else if error.is_ENOTDIR() {
"Not a directory"
} else if error.is_EACCES() {
"Permission denied"
} else if is_link_loop(error) {
"Too many levels of symbolic links"
} else {
error.to_string()
}
}
///|
async fn FindEntry::metadata_error(
self : FindEntry,
error : @os_error.OSError,
dimension : String,
) -> Unit {
self.failed = true
let @os_error.OSError(code, ..) = error
let message = if code == @os_error.errno_ENOTSUP {
"unsupported capability: " + dimension + " metadata"
} else {
find_os_message(error)
}
@stdio.stderr.write("find: '\{self.path}': \{message}\n")
}
///|
async fn FindEntry::load_size(self : FindEntry) -> Unit {
if self.size is Unloaded {
self.size = try {
let size = @fs.file_size(find_lookup_path(self.path))
if size < 0L {
raise FindError("negative entry size: '\{self.path}'")
}
Available(size)
} catch {
@os_error.OSError(_) as error => Unavailable(error)
error => raise error
}
}
}
///|
async fn FindEntry::byte_size(self : FindEntry) -> Int64? {
self.load_size()
match self.size {
Available(value) => Some(value)
Unavailable(error) => {
self.metadata_error(error, "entry size")
None
}
Unloaded => abort("size load did not finish")
}
}
///|
async fn FindEntry::load_timestamp(
self : FindEntry,
kind : @fsops.TimestampKind,
) -> FindMetadata[@fsops.FileTimestamp] {
let previous = match kind {
AccessTime => self.atime
ModificationTime => self.mtime
StatusChangeTime => self.ctime
}
if !(previous is Unloaded) {
return previous
}
let value = Available(
@fsops.read_file_timestamp(
find_lookup_path(self.path),
kind,
follow_symlink=false,
),
) catch {
@os_error.OSError(_) as error => Unavailable(error)
error => raise error
}
match kind {
AccessTime => self.atime = value
ModificationTime => self.mtime = value
StatusChangeTime => self.ctime = value
}
value
}
///|
async fn FindEntry::timestamp(
self : FindEntry,
kind : @fsops.TimestampKind,
) -> @fsops.FileTimestamp? {
match self.load_timestamp(kind) {
Available(value) => Some(value)
Unavailable(error) => {
self.metadata_error(error, "timestamp")
None
}
Unloaded => abort("timestamp load did not finish")
}
}
///|
// GNU stat fields precede directory reads and survive descendant processing
// and deletion. Collect only fields named by the expression, and report a
// capability failure only when its predicate is evaluated. Access and names
// remain separate live observations.
async fn FindEntry::load_stat_fields(
self : FindEntry,
needs : MetadataNeeds,
) -> Unit {
if needs.size || (needs.regular_size && self.kind == Regular) {
self.load_size()
}
if needs.atime {
ignore(self.load_timestamp(AccessTime))
}
if needs.mtime {
ignore(self.load_timestamp(ModificationTime))
}
if needs.ctime {
ignore(self.load_timestamp(StatusChangeTime))
}
}
///|
async fn find_access(path : String, mode : Char) -> Bool {
try {
match mode {
'r' => @fs.can_read(path)
'w' => @fs.can_write(path)
_ => @fs.can_execute(path)
}
} catch {
@os_error.OSError(_) => false
error => raise error
}
}
///|
async fn FindEntry::followed_kind_matches(
self : FindEntry,
wanted : Char,
) -> Bool {
if self.kind != SymLink {
return kind_matches(self.kind, wanted)
}
kind_matches(@fs.kind(self.path), wanted) catch {
// GNU optionl_stat first falls back to lstat for ENOENT/ENOTDIR;
// pred_xtype additionally falls back for ELOOP.
@os_error.OSError(_) as error if error.is_ENOENT() ||
error.is_ENOTDIR() ||
is_link_loop(error) => kind_matches(SymLink, wanted)
@os_error.OSError(_) as error => {
self.metadata_error(error, "target type")
false
}
error => raise error
}
}