|
|
@@ -69,12 +69,18 @@ pub struct File<'dir> {
|
|
|
/// means that they should be skipped when recursing.
|
|
|
pub is_all_all: bool,
|
|
|
|
|
|
+ /// Whether to dereference symbolic links when querying for information.
|
|
|
+ ///
|
|
|
+ /// For instance, when querying the size of a symbolic link, if
|
|
|
+ /// dereferencing is enabled, the size of the target will be displayed
|
|
|
+ /// instead.
|
|
|
+ pub deref_links: bool,
|
|
|
/// The extended attributes of this file.
|
|
|
pub extended_attributes: Vec<Attribute>,
|
|
|
}
|
|
|
|
|
|
impl<'dir> File<'dir> {
|
|
|
- pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN) -> io::Result<File<'dir>>
|
|
|
+ pub fn from_args<PD, FN>(path: PathBuf, parent_dir: PD, filename: FN, deref_links: bool) -> io::Result<File<'dir>>
|
|
|
where PD: Into<Option<&'dir Dir>>,
|
|
|
FN: Into<Option<String>>
|
|
|
{
|
|
|
@@ -87,7 +93,7 @@ impl<'dir> File<'dir> {
|
|
|
let is_all_all = false;
|
|
|
let extended_attributes = File::gather_extended_attributes(&path);
|
|
|
|
|
|
- Ok(File { name, ext, path, metadata, parent_dir, is_all_all, extended_attributes })
|
|
|
+ Ok(File { name, ext, path, metadata, parent_dir, is_all_all, deref_links, extended_attributes })
|
|
|
}
|
|
|
|
|
|
pub fn new_aa_current(parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
|
|
@@ -100,7 +106,7 @@ impl<'dir> File<'dir> {
|
|
|
let parent_dir = Some(parent_dir);
|
|
|
let extended_attributes = File::gather_extended_attributes(&path);
|
|
|
|
|
|
- Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, extended_attributes })
|
|
|
+ Ok(File { path, parent_dir, metadata, ext, name: ".".into(), is_all_all, deref_links: false, extended_attributes })
|
|
|
}
|
|
|
|
|
|
pub fn new_aa_parent(path: PathBuf, parent_dir: &'dir Dir) -> io::Result<File<'dir>> {
|
|
|
@@ -112,7 +118,7 @@ impl<'dir> File<'dir> {
|
|
|
let parent_dir = Some(parent_dir);
|
|
|
let extended_attributes = File::gather_extended_attributes(&path);
|
|
|
|
|
|
- Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, extended_attributes })
|
|
|
+ Ok(File { path, parent_dir, metadata, ext, name: "..".into(), is_all_all, deref_links: false, extended_attributes })
|
|
|
}
|
|
|
|
|
|
/// A file’s name is derived from its string. This needs to handle directories
|
|
|
@@ -285,7 +291,7 @@ impl<'dir> File<'dir> {
|
|
|
let ext = File::ext(&path);
|
|
|
let name = File::filename(&path);
|
|
|
let extended_attributes = File::gather_extended_attributes(&absolute_path);
|
|
|
- let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, extended_attributes };
|
|
|
+ let file = File { parent_dir: None, path, ext, metadata, name, is_all_all: false, deref_links: self.deref_links, extended_attributes };
|
|
|
FileTarget::Ok(Box::new(file))
|
|
|
}
|
|
|
Err(e) => {
|
|
|
@@ -295,6 +301,28 @@ impl<'dir> File<'dir> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Assuming this file is a symlink, follows that link and any further
|
|
|
+ /// links recursively, returning the result from following the trail.
|
|
|
+ ///
|
|
|
+ /// For a working symlink that the user is allowed to follow,
|
|
|
+ /// this will be the `File` object at the other end, which can then have
|
|
|
+ /// its name, colour, and other details read.
|
|
|
+ ///
|
|
|
+ /// For a broken symlink, returns where the file *would* be, if it
|
|
|
+ /// existed. If this file cannot be read at all, returns the error that
|
|
|
+ /// we got when we tried to read it.
|
|
|
+ pub fn link_target_recurse(&self) -> FileTarget<'dir> {
|
|
|
+ let target = self.link_target();
|
|
|
+ if let FileTarget::Ok(f) = target {
|
|
|
+ if f.is_link() {
|
|
|
+ return f.link_target_recurse();
|
|
|
+ } else {
|
|
|
+ return FileTarget::Ok(f);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ target
|
|
|
+ }
|
|
|
+
|
|
|
/// This file’s number of hard links.
|
|
|
///
|
|
|
/// It also reports whether this is both a regular file, and a file with
|
|
|
@@ -331,16 +359,27 @@ impl<'dir> File<'dir> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// The ID of the user that own this file.
|
|
|
- #[cfg(unix)]
|
|
|
- pub fn user(&self) -> f::User {
|
|
|
- f::User(self.metadata.uid())
|
|
|
+ /// The ID of the user that own this file. If dereferencing links, the links
|
|
|
+ /// may be broken, in which case `None` will be returned.
|
|
|
+ pub fn user(&self) -> Option<f::User> {
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => return f.user(),
|
|
|
+ _ => return None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Some(f::User(self.metadata.uid()))
|
|
|
}
|
|
|
|
|
|
/// The ID of the group that owns this file.
|
|
|
- #[cfg(unix)]
|
|
|
- pub fn group(&self) -> f::Group {
|
|
|
- f::Group(self.metadata.gid())
|
|
|
+ pub fn group(&self) -> Option<f::Group> {
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => return f.group(),
|
|
|
+ _ => return None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Some(f::Group(self.metadata.gid()))
|
|
|
}
|
|
|
|
|
|
/// This file’s size, if it’s a regular file.
|
|
|
@@ -351,6 +390,10 @@ impl<'dir> File<'dir> {
|
|
|
///
|
|
|
/// Block and character devices return their device IDs, because they
|
|
|
/// usually just have a file size of zero.
|
|
|
+ ///
|
|
|
+ /// Links will return the size of their target (recursively through other
|
|
|
+ /// links) if dereferencing is enabled, otherwise the size of the link
|
|
|
+ /// itself.
|
|
|
#[cfg(unix)]
|
|
|
pub fn size(&self) -> f::Size {
|
|
|
if self.is_link() {
|
|
|
@@ -374,7 +417,12 @@ impl<'dir> File<'dir> {
|
|
|
minor: device_ids[7],
|
|
|
})
|
|
|
}
|
|
|
- else {
|
|
|
+ else if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target() {
|
|
|
+ FileTarget::Ok(f) => f.size(),
|
|
|
+ _ => f::Size::None
|
|
|
+ }
|
|
|
+ } else {
|
|
|
f::Size::Some(self.metadata.len())
|
|
|
}
|
|
|
}
|
|
|
@@ -391,12 +439,26 @@ impl<'dir> File<'dir> {
|
|
|
|
|
|
/// This file’s last modified timestamp, if available on this platform.
|
|
|
pub fn modified_time(&self) -> Option<SystemTime> {
|
|
|
- self.metadata.modified().ok()
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => f.metadata.modified().ok(),
|
|
|
+ _ => None,
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ self.metadata.modified().ok()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// This file’s last changed timestamp, if available on this platform.
|
|
|
#[cfg(unix)]
|
|
|
pub fn changed_time(&self) -> Option<SystemTime> {
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => return f.changed_time(),
|
|
|
+ _ => return None,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
let (mut sec, mut nanosec) = (self.metadata.ctime(), self.metadata.ctime_nsec());
|
|
|
|
|
|
if sec < 0 {
|
|
|
@@ -421,12 +483,26 @@ impl<'dir> File<'dir> {
|
|
|
|
|
|
/// This file’s last accessed timestamp, if available on this platform.
|
|
|
pub fn accessed_time(&self) -> Option<SystemTime> {
|
|
|
- self.metadata.accessed().ok()
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => f.metadata.accessed().ok(),
|
|
|
+ _ => None,
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ self.metadata.accessed().ok()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// This file’s created timestamp, if available on this platform.
|
|
|
pub fn created_time(&self) -> Option<SystemTime> {
|
|
|
- self.metadata.created().ok()
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => f.metadata.created().ok(),
|
|
|
+ _ => None,
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ self.metadata.created().ok()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/// This file’s ‘type’.
|
|
|
@@ -477,11 +553,20 @@ impl<'dir> File<'dir> {
|
|
|
|
|
|
/// This file’s permissions, with flags for each bit.
|
|
|
#[cfg(unix)]
|
|
|
- pub fn permissions(&self) -> f::Permissions {
|
|
|
+ pub fn permissions(&self) -> Option<f::Permissions> {
|
|
|
+ if self.is_link() && self.deref_links {
|
|
|
+ // If the chain of links is broken, we instead fall through and
|
|
|
+ // return the permissions of the original link, as would have been
|
|
|
+ // done if we were not dereferencing.
|
|
|
+ match self.link_target_recurse() {
|
|
|
+ FileTarget::Ok(f) => return f.permissions(),
|
|
|
+ _ => return None,
|
|
|
+ }
|
|
|
+ }
|
|
|
let bits = self.metadata.mode();
|
|
|
let has_bit = |bit| bits & bit == bit;
|
|
|
|
|
|
- f::Permissions {
|
|
|
+ Some(f::Permissions {
|
|
|
user_read: has_bit(modes::USER_READ),
|
|
|
user_write: has_bit(modes::USER_WRITE),
|
|
|
user_execute: has_bit(modes::USER_EXECUTE),
|
|
|
@@ -497,7 +582,7 @@ impl<'dir> File<'dir> {
|
|
|
sticky: has_bit(modes::STICKY),
|
|
|
setgid: has_bit(modes::SETGID),
|
|
|
setuid: has_bit(modes::SETUID),
|
|
|
- }
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
#[cfg(windows)]
|