cargo_util/du.rs
1//! A simple disk usage estimator.
2
3use anyhow::{Context, Result};
4use ignore::overrides::OverrideBuilder;
5use ignore::{WalkBuilder, WalkState};
6use std::path::Path;
7use std::sync::{Arc, Mutex};
8
9/// Determines the disk usage of all files in the given directory.
10///
11/// The given patterns are gitignore style patterns relative to the given
12/// path. If there are patterns, it will only count things matching that
13/// pattern. `!` can be used to exclude things. See [`OverrideBuilder::add`]
14/// for more info.
15///
16/// This is a primitive implementation that doesn't handle hard links, and
17/// isn't particularly fast (for example, not using `getattrlistbulk` on
18/// macOS). It also only uses actual byte sizes instead of block counts (and
19/// thus vastly undercounts directories with lots of small files). It would be
20/// nice to improve this or replace it with something better.
21pub fn du(path: &Path, patterns: &[&str]) -> Result<u64> {
22 du_inner(path, patterns).with_context(|| format!("failed to walk `{}`", path.display()))
23}
24
25fn du_inner(path: &Path, patterns: &[&str]) -> Result<u64> {
26 let mut builder = OverrideBuilder::new(path);
27 for pattern in patterns {
28 builder.add(pattern)?;
29 }
30 let overrides = builder.build()?;
31
32 let mut builder = WalkBuilder::new(path);
33 builder
34 .overrides(overrides)
35 .hidden(false)
36 .parents(false)
37 .ignore(false)
38 .git_global(false)
39 .git_ignore(false)
40 .git_exclude(false);
41 let walker = builder.build_parallel();
42
43 // Platforms like PowerPC don't support AtomicU64, so we use a Mutex instead.
44 //
45 // See:
46 // - https://github.com/rust-lang/cargo/pull/12981
47 // - https://github.com/rust-lang/rust/pull/117916#issuecomment-1812635848
48 let total = Arc::new(Mutex::new(0u64));
49
50 // A slot used to indicate there was an error while walking.
51 //
52 // It is possible that more than one error happens (such as in different
53 // threads). The error returned is arbitrary in that case.
54 let err = Arc::new(Mutex::new(None));
55 walker.run(|| {
56 Box::new(|entry| {
57 match entry {
58 Ok(entry) => match entry.metadata() {
59 Ok(meta) => {
60 if meta.is_file() {
61 let mut lock = total.lock().unwrap();
62 *lock += meta.len();
63 }
64 }
65 Err(e) => {
66 *err.lock().unwrap() = Some(e.into());
67 return WalkState::Quit;
68 }
69 },
70 Err(e) => {
71 *err.lock().unwrap() = Some(e.into());
72 return WalkState::Quit;
73 }
74 }
75 WalkState::Continue
76 })
77 });
78
79 if let Some(e) = err.lock().unwrap().take() {
80 return Err(e);
81 }
82
83 let total = *total.lock().unwrap();
84 Ok(total)
85}