summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Thiel <sthiel@thoughtworks.com>2020-05-04 13:07:58 +0800
committerSebastian Thiel <sthiel@thoughtworks.com>2020-05-04 13:07:58 +0800
commit9156cf7cac8f91a496f7383940f3ce6140ffe54c (patch)
tree8965c3a980646caacc87af2ceb7ac44de96ee724
parenta5988d091b437315a91accd21f6f1b61d21e2e9a (diff)
Add '-x' flag to not cross filesystems
Fixes #3
-rw-r--r--CHANGELOG.md1
-rw-r--r--src/aggregate.rs20
-rw-r--r--src/common.rs1
-rw-r--r--src/crossdev.rs25
-rw-r--r--src/interactive/app_test/utils.rs1
-rw-r--r--src/lib.rs1
-rw-r--r--src/main.rs1
-rw-r--r--src/options.rs4
-rw-r--r--src/traverse.rs7
9 files changed, 51 insertions, 10 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0033c3d..8043cb5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
#### v2.6.0
* Use `x` to only mark entries for deletion, instead of toggling them.
+* Add `-x` | `--stay-on-filesystem` flag to force staying on the file system the root is on, similar to `-x` in the venerable `du` tool.
#### v2.5.0 Much more nuanced percentage bars for a more precise visualization of space consumption
diff --git a/src/aggregate.rs b/src/aggregate.rs
index b04fa83..b7ef995 100644
--- a/src/aggregate.rs
+++ b/src/aggregate.rs
@@ -1,4 +1,4 @@
-use crate::{InodeFilter, WalkOptions, WalkResult};
+use crate::{crossdev, InodeFilter, WalkOptions, WalkResult};
use failure::Error;
use filesize::PathExt;
use std::borrow::Cow;
@@ -10,7 +10,7 @@ use termion::color;
/// If `sort_by_size_in_bytes` is set, we will sort all sizes (ascending) before outputting them.
pub fn aggregate(
mut out: impl io::Write,
- options: WalkOptions,
+ walk_options: WalkOptions,
compute_total: bool,
sort_by_size_in_bytes: bool,
paths: impl IntoIterator<Item = impl AsRef<Path>>,
@@ -27,15 +27,19 @@ pub fn aggregate(
num_roots += 1;
let mut num_bytes = 0u64;
let mut num_errors = 0u64;
- for entry in options.iter_from_path(path.as_ref()) {
+ let device_id = crossdev::init(path.as_ref())?;
+ for entry in walk_options.iter_from_path(path.as_ref()) {
stats.entries_traversed += 1;
match entry {
Ok(entry) => {
let file_size = match entry.client_state {
Some(Ok(ref m))
- if !m.is_dir() && (options.count_hard_links || inodes.add(m)) =>
+ if !m.is_dir()
+ && (walk_options.count_hard_links || inodes.add(m))
+ && (walk_options.cross_filesystems
+ || crossdev::is_same_device(device_id, m)) =>
{
- if options.apparent_size {
+ if walk_options.apparent_size {
m.len()
} else {
entry.path().size_on_disk_fast(m).unwrap_or_else(|_| {
@@ -64,7 +68,7 @@ pub fn aggregate(
} else {
write_path(
&mut out,
- &options,
+ &walk_options,
&path,
num_bytes,
num_errors,
@@ -84,7 +88,7 @@ pub fn aggregate(
for (path, num_bytes, num_errors) in aggregates.into_iter() {
write_path(
&mut out,
- &options,
+ &walk_options,
&path,
num_bytes,
num_errors,
@@ -96,7 +100,7 @@ pub fn aggregate(
if num_roots > 1 && compute_total {
write_path(
&mut out,
- &options,
+ &walk_options,
Path::new("total"),
total,
res.num_errors,
diff --git a/src/common.rs b/src/common.rs
index f88c491..e5cf697 100644
--- a/src/common.rs
+++ b/src/common.rs
@@ -155,6 +155,7 @@ pub struct WalkOptions {
pub apparent_size: bool,
pub color: Color,
pub sorting: TraversalSorting,
+ pub cross_filesystems: bool,
}
type WalkDir = jwalk::WalkDirGeneric<((), Option<Result<std::fs::Metadata, jwalk::Error>>)>;
diff --git a/src/crossdev.rs b/src/crossdev.rs
new file mode 100644
index 0000000..94c5c67
--- /dev/null
+++ b/src/crossdev.rs
@@ -0,0 +1,25 @@
+use std::{io, path::Path};
+
+#[cfg(unix)]
+pub fn init(path: &Path) -> io::Result<u64> {
+ use std::os::unix::fs::MetadataExt;
+
+ path.metadata().map(|m| m.dev())
+}
+
+#[cfg(unix)]
+pub fn is_same_device(device_id: u64, meta: &std::fs::Metadata) -> bool {
+ use std::os::unix::fs::MetadataExt;
+
+ meta.dev() == device_id
+}
+
+#[cfg(not(any(unix, windows)))]
+pub fn is_same_device(device_id: u64, meta: &std::fs::Metadata) -> bool {
+ true
+}
+
+#[cfg(not(any(unix, windows)))]
+pub fn init(path: &Path) -> io::Result<u64> {
+ Ok(0)
+}
diff --git a/src/interactive/app_test/utils.rs b/src/interactive/app_test/utils.rs
index 8fde29f..57854d9 100644
--- a/src/interactive/app_test/utils.rs
+++ b/src/interactive/app_test/utils.rs
@@ -175,6 +175,7 @@ pub fn initialized_app_and_terminal_with_closure<P: AsRef<Path>>(
count_hard_links: false,
color: Color::None,
sorting: TraversalSorting::AlphabeticalByFileName,
+ cross_filesystems: false,
},
input,
Interaction::None,
diff --git a/src/lib.rs b/src/lib.rs
index ae6f953..f1f4f5c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ extern crate jwalk;
mod aggregate;
mod common;
+mod crossdev;
mod inodefilter;
pub mod traverse;
diff --git a/src/main.rs b/src/main.rs
index e9ea37e..7f505d9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -32,6 +32,7 @@ fn run() -> Result<(), Error> {
apparent_size: opt.apparent_size,
count_hard_links: opt.count_hard_links,
sorting: TraversalSorting::None,
+ cross_filesystems: !opt.stay_on_filesystem,
};
let res = match opt.command {
Some(Interactive { input }) => {
diff --git a/src/options.rs b/src/options.rs
index 1a06e45..5b9d604 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -60,6 +60,10 @@ pub struct Args {
#[structopt(short = "l", long)]
pub count_hard_links: bool,
+ /// If set, we will not cross filesystems or traverse mount points
+ #[structopt(short = "x", long)]
+ pub stay_on_filesystem: bool,
+
/// One or more input files or directories. If unset, we will use all entries in the current working directory.
#[structopt(parse(from_os_str))]
pub input: Vec<PathBuf>,
diff --git a/src/traverse.rs b/src/traverse.rs
index d78041f..5027b69 100644
--- a/src/traverse.rs
+++ b/src/traverse.rs
@@ -1,4 +1,4 @@
-use crate::{get_size_or_panic, InodeFilter, WalkOptions};
+use crate::{crossdev, get_size_or_panic, InodeFilter, WalkOptions};
use failure::Error;
use filesize::PathExt;
use petgraph::{graph::NodeIndex, stable_graph::StableGraph, Directed, Direction};
@@ -80,6 +80,7 @@ impl Traversal {
}
for path in input.into_iter() {
let mut last_seen_eid = 0;
+ let device_id = crossdev::init(path.as_ref())?;
for (eid, entry) in walk_options
.iter_from_path(path.as_ref())
.into_iter()
@@ -97,7 +98,9 @@ impl Traversal {
let file_size = match entry.client_state {
Some(Ok(ref m))
if !m.is_dir()
- && (walk_options.count_hard_links || inodes.add(m)) =>
+ && (walk_options.count_hard_links || inodes.add(m))
+ && (walk_options.cross_filesystems
+ || crossdev::is_same_device(device_id, m)) =>
{
if walk_options.apparent_size {
m.len()