#
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}