Skip to content

Commit

Permalink
df: Adds support for mount path prefix matching and input path (#3161)
Browse files Browse the repository at this point in the history
* Adds support for mount path prefix matching and input path
canonicalization

- Sorts mount paths in reverse lexicographical order
- Canonicalize all paths and clear invalid paths
- Checking of mount path prefix matches input path
  • Loading branch information
crazystylus authored Mar 11, 2022
1 parent 1795272 commit 5c5f4ca
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 18 deletions.
59 changes: 41 additions & 18 deletions src/uu/df/src/df.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs
// spell-checker:ignore itotal iused iavail ipcent pcent tmpfs squashfs lofs
mod blocks;
mod columns;
mod table;
Expand All @@ -22,7 +22,6 @@ use std::collections::HashSet;
use std::fmt;
use std::iter::FromIterator;

#[cfg(windows)]
use std::path::Path;

use crate::blocks::{block_size_from_matches, BlockSize};
Expand Down Expand Up @@ -188,7 +187,7 @@ impl Filesystem {
}

/// Whether to display the mount info given the inclusion settings.
fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
fn is_included(mi: &MountInfo, opt: &Options) -> bool {
// Don't show remote filesystems if `--local` has been given.
if mi.remote && opt.show_local_fs {
return false;
Expand All @@ -204,12 +203,6 @@ fn is_included(mi: &MountInfo, paths: &[String], opt: &Options) -> bool {
return false;
}

// Don't show filesystems other than the ones specified on the
// command line, if any.
if !paths.is_empty() && !paths.contains(&mi.mount_dir) {
return false;
}

true
}

Expand Down Expand Up @@ -259,37 +252,59 @@ fn is_best(previous: &[MountInfo], mi: &MountInfo) -> bool {

/// Keep only the specified subset of [`MountInfo`] instances.
///
/// If `paths` is non-empty, this function excludes any [`MountInfo`]
/// that is not mounted at the specified path.
///
/// The `opt` argument specifies a variety of ways of excluding
/// [`MountInfo`] instances; see [`Options`] for more information.
///
/// Finally, if there are duplicate entries, the one with the shorter
/// path is kept.
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
fn filter_mount_list(vmi: Vec<MountInfo>, opt: &Options) -> Vec<MountInfo> {
let mut result = vec![];
for mi in vmi {
// TODO The running time of the `is_best()` function is linear
// in the length of `result`. That makes the running time of
// this loop quadratic in the length of `vmi`. This could be
// improved by a more efficient implementation of `is_best()`,
// but `vmi` is probably not very long in practice.
if is_included(&mi, paths, opt) && is_best(&result, &mi) {
if is_included(&mi, opt) && is_best(&result, &mi) {
result.push(mi);
}
}
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> {
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>>()
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().get_matches_from(args);

let paths: Vec<String> = matches
// Canonicalize the input_paths and then convert to string
let paths = matches
.values_of(OPT_PATHS)
.map(|v| v.map(ToString::to_string).collect())
.unwrap_or_default();
.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)]
{
Expand All @@ -302,7 +317,15 @@ 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 data: Vec<Row> = filter_mount_list(mounts, &paths, &opt)

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)
};
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)
Expand Down
12 changes: 12 additions & 0 deletions tests/by-util/test_df.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ fn test_order_same() {
assert_eq!(output1, output2);
}

/// Test of mount point begin repeated
#[cfg(unix)]
#[test]
fn test_output_mp_repeat() {
let output1 = new_ucmd!().arg("/").arg("/").succeeds().stdout_move_str();
let output1: Vec<String> = output1
.lines()
.map(|l| String::from(l.split_once(' ').unwrap().0))
.collect();
assert_eq!(3, output1.len());
assert_eq!(output1[1], output1[2]);
}
#[test]
fn test_output_conflict_options() {
for option in ["-i", "-T", "-P"] {
Expand Down

0 comments on commit 5c5f4ca

Please sign in to comment.