Skip to content

Commit

Permalink
Merge pull request #40 from solidiquis/handle-hard-link
Browse files Browse the repository at this point in the history
Handle hardlinks
  • Loading branch information
solidiquis authored Mar 6, 2023
2 parents 277f70a + 412d490 commit dc79370
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 2 deletions.
21 changes: 20 additions & 1 deletion src/fs/erdtree/node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use super::{disk_usage::DiskUsage, get_ls_colors};
use super::{
super::inode::Inode,
disk_usage::DiskUsage,
get_ls_colors,
};
use crate::{
fs::file_size::FileSize,
icons::{self, icon_from_ext, icon_from_file_name, icon_from_file_type},
Expand Down Expand Up @@ -31,6 +35,7 @@ pub struct Node {
children: Option<Vec<Node>>,
file_name: OsString,
file_type: Option<FileType>,
inode: Option<Inode>,
path: PathBuf,
show_icon: bool,
style: Style,
Expand All @@ -45,6 +50,7 @@ impl Node {
children: Option<Vec<Node>>,
file_name: OsString,
file_type: Option<FileType>,
inode: Option<Inode>,
path: PathBuf,
show_icon: bool,
style: Style,
Expand All @@ -56,6 +62,7 @@ impl Node {
file_name,
file_size,
file_type,
inode,
path,
show_icon,
style,
Expand Down Expand Up @@ -146,6 +153,11 @@ impl Node {
&self.style
}

/// Returns reference to underlying [Inode] if any.
pub fn inode(&self) -> Option<&Inode> {
self.inode.as_ref()
}

/// Gets stylized icon for node if enabled. Icons without extensions are styled based on the
/// [`LS_COLORS`] foreground configuration of the associated file name.
///
Expand Down Expand Up @@ -263,12 +275,19 @@ impl From<NodePrecursor<'_>> for Node {
}
};

let inode = metadata
.map(Inode::try_from)
.transpose()
.ok()
.flatten();

Self::new(
depth,
file_size,
children,
file_name,
file_type,
inode,
path.into(),
show_icon,
style,
Expand Down
10 changes: 9 additions & 1 deletion src/fs/erdtree/tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::cli::Clargs;
use crossbeam::channel::{self, Sender};
use ignore::{WalkParallel, WalkState};
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
convert::TryFrom,
fmt::{self, Display, Formatter},
path::PathBuf,
Expand Down Expand Up @@ -79,6 +79,7 @@ impl Tree {
// components needed to assemble a `Tree`.
let tree_components = thread::spawn(move || -> TreeResult<TreeComponents> {
let mut branches: Branches = HashMap::new();
let mut inodes = HashSet::new();
let mut root = None;

while let Ok(node) = rx.recv() {
Expand All @@ -93,6 +94,13 @@ impl Tree {
root = Some(node);
continue;
}
} else {
// If a hard-link is already accounted for skip the subsequent one.
if let Some(inode) = node.inode() {
if !inodes.insert(inode.properties()) {
continue
}
}
}

let parent = node.parent_path_buf().ok_or(Error::ExpectedParent)?;
Expand Down
68 changes: 68 additions & 0 deletions src/fs/inode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::{
convert::TryFrom,
error::Error as StdError,
fmt::{self, Display},
fs::Metadata,
};

/// Represents a file's underlying inode.
#[derive(Debug)]
pub struct Inode {
ino: u64,
dev: u64,
nlink: u64
}

impl Inode {
/// Initializer for an inode given all the properties that make it unique.
pub fn new(ino: u64, dev: u64, nlink: u64) -> Self {
Self { ino, dev, nlink }
}

/// Returns a tuple of all the fields of the [Inode].
pub fn properties(&self) -> (u64, u64, u64) {
(self.ino, self.dev, self.nlink)
}
}

#[derive(Debug)]
pub struct Error;

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Insufficient information to compute inode")
}
}

impl StdError for Error {}

impl TryFrom<Metadata> for Inode {
type Error = Error;

#[cfg(unix)]
fn try_from(md: Metadata) -> Result<Self, Self::Error> {
use std::os::unix::fs::MetadataExt;

Ok(Self::new(md.ino(), md.dev(), md.nlink()))
}

#[cfg(windows)]
fn try_from(md: Metadata) -> Result<Self, Self::Error> {
use std::os::windows::fs::MetadataExt;

if let (Some(ino), Some(dev), Some(nlinks)) = (
metadata.file_index(),
metadata.volume_serial_number(),
metadata.number_of_links(),
) {
return Ok(Self::new(md, dev, nlink));
}

Err(Error {})
}

#[cfg(not(any(unix, windows)))]
fn try_from(md: Metadata) -> Result<Self, Self::Error> {
Err(Error {})
}
}
3 changes: 3 additions & 0 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/// Errors related to filesystem traversal.
pub mod error;

/// Operations pertaining to Inodes.
pub mod inode;

/// Operations to present disk usage in human-readable format.
pub mod file_size;

Expand Down

0 comments on commit dc79370

Please sign in to comment.