mirror of
https://github.com/uutils/coreutils
synced 2024-11-16 17:58:06 +00:00
Merge pull request #3259 from jfinkels/df-loops
df: separate functions for two main modes of df
This commit is contained in:
commit
161db4cdd7
2 changed files with 195 additions and 45 deletions
|
@ -230,39 +230,49 @@ fn filter_mount_list(vmi: Vec<MountInfo>, opt: &Options) -> Vec<MountInfo> {
|
|||
result
|
||||
}
|
||||
|
||||
/// Assign 1 `MountInfo` entry to each path
|
||||
/// `lofs` entries are skipped and dummy mount points are skipped
|
||||
/// Only the longest matching prefix for that path is considered
|
||||
/// `lofs` is for Solaris style loopback filesystem and is present in Solaris and FreeBSD.
|
||||
/// It works similar to symlinks
|
||||
fn get_point_list(vmi: &[MountInfo], paths: &[String]) -> Vec<MountInfo> {
|
||||
/// Get all currently mounted filesystems.
|
||||
///
|
||||
/// `opt` excludes certain filesystems from consideration; see
|
||||
/// [`Options`] for more information.
|
||||
fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
|
||||
// The list of all mounted filesystems.
|
||||
//
|
||||
// Filesystems excluded by the command-line options are
|
||||
// not considered.
|
||||
let mounts: Vec<MountInfo> = filter_mount_list(read_fs_list(), opt);
|
||||
|
||||
// Convert each `MountInfo` into a `Filesystem`, which contains
|
||||
// both the mount information and usage information.
|
||||
mounts.into_iter().filter_map(Filesystem::new).collect()
|
||||
}
|
||||
|
||||
/// For each path, get the filesystem that contains that path.
|
||||
fn get_named_filesystems<P>(paths: &[P]) -> Vec<Filesystem>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// The list of all mounted filesystems.
|
||||
//
|
||||
// Filesystems marked as `dummy` or of type "lofs" are not
|
||||
// considered. The "lofs" filesystem is a loopback
|
||||
// filesystem present on Solaris and FreeBSD systems. It
|
||||
// is similar to a symbolic link.
|
||||
let mounts: Vec<MountInfo> = read_fs_list()
|
||||
.into_iter()
|
||||
.filter(|mi| mi.fs_type != "lofs" && !mi.dummy)
|
||||
.collect();
|
||||
|
||||
// Convert each path into a `Filesystem`, which contains
|
||||
// both the mount information and usage information.
|
||||
paths
|
||||
.iter()
|
||||
.map(|p| {
|
||||
vmi.iter()
|
||||
.filter(|mi| mi.fs_type.ne("lofs"))
|
||||
.filter(|mi| !mi.dummy)
|
||||
.filter(|mi| p.starts_with(&mi.mount_dir))
|
||||
.max_by_key(|mi| mi.mount_dir.len())
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<MountInfo>>()
|
||||
.filter_map(|p| Filesystem::from_path(&mounts, p))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().get_matches_from(args);
|
||||
|
||||
// Canonicalize the input_paths and then convert to string
|
||||
let paths = matches
|
||||
.values_of(OPT_PATHS)
|
||||
.unwrap_or_default()
|
||||
.map(Path::new)
|
||||
.filter_map(|v| v.canonicalize().ok())
|
||||
.filter_map(|v| v.into_os_string().into_string().ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if matches.is_present(OPT_INODES) {
|
||||
|
@ -273,27 +283,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
let opt = Options::from(&matches).map_err(|e| USimpleError::new(1, format!("{}", e)))?;
|
||||
|
||||
let mounts = read_fs_list();
|
||||
|
||||
let op_mount_points: Vec<MountInfo> = if paths.is_empty() {
|
||||
// Get all entries
|
||||
filter_mount_list(mounts, &opt)
|
||||
} else {
|
||||
// Get Point for each input_path
|
||||
get_point_list(&mounts, &paths)
|
||||
// Get the list of filesystems to display in the output table.
|
||||
let filesystems: Vec<Filesystem> = match matches.values_of(OPT_PATHS) {
|
||||
None => get_all_filesystems(&opt),
|
||||
Some(paths) => {
|
||||
let paths: Vec<&str> = paths.collect();
|
||||
get_named_filesystems(&paths)
|
||||
}
|
||||
};
|
||||
let data: Vec<Row> = op_mount_points
|
||||
.into_iter()
|
||||
.filter_map(Filesystem::new)
|
||||
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
// The running total of filesystem sizes and usage.
|
||||
//
|
||||
// This accumulator is computed in case we need to display the
|
||||
// total counts in the last row of the table.
|
||||
let mut total = Row::new("total");
|
||||
|
||||
println!("{}", Header::new(&opt));
|
||||
let mut total = Row::new("total");
|
||||
for row in data {
|
||||
println!("{}", DisplayRow::new(&row, &opt));
|
||||
total += row;
|
||||
for filesystem in filesystems {
|
||||
// If the filesystem is not empty, or if the options require
|
||||
// showing all filesystems, then print the data as a row in
|
||||
// the output table.
|
||||
if opt.show_all_fs || opt.show_listed_fs || filesystem.usage.blocks > 0 {
|
||||
let row = Row::from(filesystem);
|
||||
println!("{}", DisplayRow::new(&row, &opt));
|
||||
total += row;
|
||||
}
|
||||
}
|
||||
if opt.show_total {
|
||||
println!("{}", DisplayRow::new(&total, &opt));
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//! A [`Filesystem`] struct represents a device containing a
|
||||
//! filesystem mounted at a particular directory. It also includes
|
||||
//! information on amount of space available and amount of space used.
|
||||
#[cfg(windows)]
|
||||
// spell-checker:ignore canonicalized
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -30,6 +30,40 @@ pub(crate) struct Filesystem {
|
|||
pub usage: FsUsage,
|
||||
}
|
||||
|
||||
/// Find the mount info that best matches a given filesystem path.
|
||||
///
|
||||
/// This function returns the element of `mounts` on which `path` is
|
||||
/// mounted. If there are no matches, this function returns
|
||||
/// [`None`]. If there are two or more matches, then the single
|
||||
/// [`MountInfo`] with the longest mount directory is returned.
|
||||
///
|
||||
/// If `canonicalize` is `true`, then the `path` is canonicalized
|
||||
/// before checking whether it matches any mount directories.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Path::canonicalize`]
|
||||
/// * [`MountInfo::mount_dir`]
|
||||
fn mount_info_from_path<P>(
|
||||
mounts: &[MountInfo],
|
||||
path: P,
|
||||
// This is really only used for testing purposes.
|
||||
canonicalize: bool,
|
||||
) -> Option<&MountInfo>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
// TODO Refactor this function with `Stater::find_mount_point()`
|
||||
// in the `stat` crate.
|
||||
let path = if canonicalize {
|
||||
path.as_ref().canonicalize().ok()?
|
||||
} else {
|
||||
path.as_ref().to_path_buf()
|
||||
};
|
||||
let matches = mounts.iter().filter(|mi| path.starts_with(&mi.mount_dir));
|
||||
matches.max_by_key(|mi| mi.mount_dir.len())
|
||||
}
|
||||
|
||||
impl Filesystem {
|
||||
// TODO: resolve uuid in `mount_info.dev_name` if exists
|
||||
pub(crate) fn new(mount_info: MountInfo) -> Option<Self> {
|
||||
|
@ -52,4 +86,106 @@ impl Filesystem {
|
|||
let usage = FsUsage::new(Path::new(&_stat_path));
|
||||
Some(Self { mount_info, usage })
|
||||
}
|
||||
|
||||
/// Find and create the filesystem that best matches a given path.
|
||||
///
|
||||
/// This function returns a new `Filesystem` derived from the
|
||||
/// element of `mounts` on which `path` is mounted. If there are
|
||||
/// no matches, this function returns [`None`]. If there are two
|
||||
/// or more matches, then the single [`Filesystem`] with the
|
||||
/// longest mount directory is returned.
|
||||
///
|
||||
/// The `path` is canonicalized before checking whether it matches
|
||||
/// any mount directories.
|
||||
///
|
||||
/// # See also
|
||||
///
|
||||
/// * [`Path::canonicalize`]
|
||||
/// * [`MountInfo::mount_dir`]
|
||||
///
|
||||
pub(crate) fn from_path<P>(mounts: &[MountInfo], path: P) -> Option<Self>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let canonicalize = true;
|
||||
let mount_info = mount_info_from_path(mounts, path, canonicalize)?;
|
||||
// TODO Make it so that we do not need to clone the `mount_info`.
|
||||
let mount_info = (*mount_info).clone();
|
||||
Self::new(mount_info)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
mod mount_info_from_path {
|
||||
|
||||
use uucore::fsext::MountInfo;
|
||||
|
||||
use crate::filesystem::mount_info_from_path;
|
||||
|
||||
// Create a fake `MountInfo` with the given directory name.
|
||||
fn mount_info(mount_dir: &str) -> MountInfo {
|
||||
MountInfo {
|
||||
dev_id: Default::default(),
|
||||
dev_name: Default::default(),
|
||||
fs_type: Default::default(),
|
||||
mount_dir: String::from(mount_dir),
|
||||
mount_option: Default::default(),
|
||||
mount_root: Default::default(),
|
||||
remote: Default::default(),
|
||||
dummy: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether two `MountInfo` instances are equal.
|
||||
fn mount_info_eq(m1: &MountInfo, m2: &MountInfo) -> bool {
|
||||
m1.dev_id == m2.dev_id
|
||||
&& m1.dev_name == m2.dev_name
|
||||
&& m1.fs_type == m2.fs_type
|
||||
&& m1.mount_dir == m2.mount_dir
|
||||
&& m1.mount_option == m2.mount_option
|
||||
&& m1.mount_root == m2.mount_root
|
||||
&& m1.remote == m2.remote
|
||||
&& m1.dummy == m2.dummy
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_mounts() {
|
||||
assert!(mount_info_from_path(&[], "/", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_exact_match() {
|
||||
let mounts = [mount_info("/foo")];
|
||||
let actual = mount_info_from_path(&mounts, "/foo", false).unwrap();
|
||||
assert!(mount_info_eq(actual, &mounts[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prefix_match() {
|
||||
let mounts = [mount_info("/foo")];
|
||||
let actual = mount_info_from_path(&mounts, "/foo/bar", false).unwrap();
|
||||
assert!(mount_info_eq(actual, &mounts[0]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_matches() {
|
||||
let mounts = [mount_info("/foo"), mount_info("/foo/bar")];
|
||||
let actual = mount_info_from_path(&mounts, "/foo/bar", false).unwrap();
|
||||
assert!(mount_info_eq(actual, &mounts[1]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_match() {
|
||||
let mounts = [mount_info("/foo")];
|
||||
assert!(mount_info_from_path(&mounts, "/bar", false).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_match() {
|
||||
let mounts = [mount_info("/foo/bar")];
|
||||
assert!(mount_info_from_path(&mounts, "/foo/baz", false).is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue