Skip to content

Commit 161db4c

Browse files
authored
Merge pull request #3259 from jfinkels/df-loops
df: separate functions for two main modes of df
2 parents 45cf7d5 + b84ff35 commit 161db4c

File tree

2 files changed

+195
-45
lines changed

2 files changed

+195
-45
lines changed

src/uu/df/src/df.rs

Lines changed: 58 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -230,39 +230,49 @@ fn filter_mount_list(vmi: Vec<MountInfo>, opt: &Options) -> Vec<MountInfo> {
230230
result
231231
}
232232

233-
/// Assign 1 `MountInfo` entry to each path
234-
/// `lofs` entries are skipped and dummy mount points are skipped
235-
/// Only the longest matching prefix for that path is considered
236-
/// `lofs` is for Solaris style loopback filesystem and is present in Solaris and FreeBSD.
237-
/// It works similar to symlinks
238-
fn get_point_list(vmi: &[MountInfo], paths: &[String]) -> Vec<MountInfo> {
233+
/// Get all currently mounted filesystems.
234+
///
235+
/// `opt` excludes certain filesystems from consideration; see
236+
/// [`Options`] for more information.
237+
fn get_all_filesystems(opt: &Options) -> Vec<Filesystem> {
238+
// The list of all mounted filesystems.
239+
//
240+
// Filesystems excluded by the command-line options are
241+
// not considered.
242+
let mounts: Vec<MountInfo> = filter_mount_list(read_fs_list(), opt);
243+
244+
// Convert each `MountInfo` into a `Filesystem`, which contains
245+
// both the mount information and usage information.
246+
mounts.into_iter().filter_map(Filesystem::new).collect()
247+
}
248+
249+
/// For each path, get the filesystem that contains that path.
250+
fn get_named_filesystems<P>(paths: &[P]) -> Vec<Filesystem>
251+
where
252+
P: AsRef<Path>,
253+
{
254+
// The list of all mounted filesystems.
255+
//
256+
// Filesystems marked as `dummy` or of type "lofs" are not
257+
// considered. The "lofs" filesystem is a loopback
258+
// filesystem present on Solaris and FreeBSD systems. It
259+
// is similar to a symbolic link.
260+
let mounts: Vec<MountInfo> = read_fs_list()
261+
.into_iter()
262+
.filter(|mi| mi.fs_type != "lofs" && !mi.dummy)
263+
.collect();
264+
265+
// Convert each path into a `Filesystem`, which contains
266+
// both the mount information and usage information.
239267
paths
240268
.iter()
241-
.map(|p| {
242-
vmi.iter()
243-
.filter(|mi| mi.fs_type.ne("lofs"))
244-
.filter(|mi| !mi.dummy)
245-
.filter(|mi| p.starts_with(&mi.mount_dir))
246-
.max_by_key(|mi| mi.mount_dir.len())
247-
.unwrap()
248-
.clone()
249-
})
250-
.collect::<Vec<MountInfo>>()
269+
.filter_map(|p| Filesystem::from_path(&mounts, p))
270+
.collect()
251271
}
252272

253273
#[uucore::main]
254274
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
255275
let matches = uu_app().get_matches_from(args);
256-
257-
// Canonicalize the input_paths and then convert to string
258-
let paths = matches
259-
.values_of(OPT_PATHS)
260-
.unwrap_or_default()
261-
.map(Path::new)
262-
.filter_map(|v| v.canonicalize().ok())
263-
.filter_map(|v| v.into_os_string().into_string().ok())
264-
.collect::<Vec<_>>();
265-
266276
#[cfg(windows)]
267277
{
268278
if matches.is_present(OPT_INODES) {
@@ -273,27 +283,31 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
273283

274284
let opt = Options::from(&matches).map_err(|e| USimpleError::new(1, format!("{}", e)))?;
275285

276-
let mounts = read_fs_list();
277-
278-
let op_mount_points: Vec<MountInfo> = if paths.is_empty() {
279-
// Get all entries
280-
filter_mount_list(mounts, &opt)
281-
} else {
282-
// Get Point for each input_path
283-
get_point_list(&mounts, &paths)
286+
// Get the list of filesystems to display in the output table.
287+
let filesystems: Vec<Filesystem> = match matches.values_of(OPT_PATHS) {
288+
None => get_all_filesystems(&opt),
289+
Some(paths) => {
290+
let paths: Vec<&str> = paths.collect();
291+
get_named_filesystems(&paths)
292+
}
284293
};
285-
let data: Vec<Row> = op_mount_points
286-
.into_iter()
287-
.filter_map(Filesystem::new)
288-
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
289-
.map(Into::into)
290-
.collect();
291294

292-
println!("{}", Header::new(&opt));
295+
// The running total of filesystem sizes and usage.
296+
//
297+
// This accumulator is computed in case we need to display the
298+
// total counts in the last row of the table.
293299
let mut total = Row::new("total");
294-
for row in data {
295-
println!("{}", DisplayRow::new(&row, &opt));
296-
total += row;
300+
301+
println!("{}", Header::new(&opt));
302+
for filesystem in filesystems {
303+
// If the filesystem is not empty, or if the options require
304+
// showing all filesystems, then print the data as a row in
305+
// the output table.
306+
if opt.show_all_fs || opt.show_listed_fs || filesystem.usage.blocks > 0 {
307+
let row = Row::from(filesystem);
308+
println!("{}", DisplayRow::new(&row, &opt));
309+
total += row;
310+
}
297311
}
298312
if opt.show_total {
299313
println!("{}", DisplayRow::new(&total, &opt));

src/uu/df/src/filesystem.rs

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! A [`Filesystem`] struct represents a device containing a
88
//! filesystem mounted at a particular directory. It also includes
99
//! information on amount of space available and amount of space used.
10-
#[cfg(windows)]
10+
// spell-checker:ignore canonicalized
1111
use std::path::Path;
1212

1313
#[cfg(unix)]
@@ -30,6 +30,40 @@ pub(crate) struct Filesystem {
3030
pub usage: FsUsage,
3131
}
3232

33+
/// Find the mount info that best matches a given filesystem path.
34+
///
35+
/// This function returns the element of `mounts` on which `path` is
36+
/// mounted. If there are no matches, this function returns
37+
/// [`None`]. If there are two or more matches, then the single
38+
/// [`MountInfo`] with the longest mount directory is returned.
39+
///
40+
/// If `canonicalize` is `true`, then the `path` is canonicalized
41+
/// before checking whether it matches any mount directories.
42+
///
43+
/// # See also
44+
///
45+
/// * [`Path::canonicalize`]
46+
/// * [`MountInfo::mount_dir`]
47+
fn mount_info_from_path<P>(
48+
mounts: &[MountInfo],
49+
path: P,
50+
// This is really only used for testing purposes.
51+
canonicalize: bool,
52+
) -> Option<&MountInfo>
53+
where
54+
P: AsRef<Path>,
55+
{
56+
// TODO Refactor this function with `Stater::find_mount_point()`
57+
// in the `stat` crate.
58+
let path = if canonicalize {
59+
path.as_ref().canonicalize().ok()?
60+
} else {
61+
path.as_ref().to_path_buf()
62+
};
63+
let matches = mounts.iter().filter(|mi| path.starts_with(&mi.mount_dir));
64+
matches.max_by_key(|mi| mi.mount_dir.len())
65+
}
66+
3367
impl Filesystem {
3468
// TODO: resolve uuid in `mount_info.dev_name` if exists
3569
pub(crate) fn new(mount_info: MountInfo) -> Option<Self> {
@@ -52,4 +86,106 @@ impl Filesystem {
5286
let usage = FsUsage::new(Path::new(&_stat_path));
5387
Some(Self { mount_info, usage })
5488
}
89+
90+
/// Find and create the filesystem that best matches a given path.
91+
///
92+
/// This function returns a new `Filesystem` derived from the
93+
/// element of `mounts` on which `path` is mounted. If there are
94+
/// no matches, this function returns [`None`]. If there are two
95+
/// or more matches, then the single [`Filesystem`] with the
96+
/// longest mount directory is returned.
97+
///
98+
/// The `path` is canonicalized before checking whether it matches
99+
/// any mount directories.
100+
///
101+
/// # See also
102+
///
103+
/// * [`Path::canonicalize`]
104+
/// * [`MountInfo::mount_dir`]
105+
///
106+
pub(crate) fn from_path<P>(mounts: &[MountInfo], path: P) -> Option<Self>
107+
where
108+
P: AsRef<Path>,
109+
{
110+
let canonicalize = true;
111+
let mount_info = mount_info_from_path(mounts, path, canonicalize)?;
112+
// TODO Make it so that we do not need to clone the `mount_info`.
113+
let mount_info = (*mount_info).clone();
114+
Self::new(mount_info)
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod tests {
120+
121+
mod mount_info_from_path {
122+
123+
use uucore::fsext::MountInfo;
124+
125+
use crate::filesystem::mount_info_from_path;
126+
127+
// Create a fake `MountInfo` with the given directory name.
128+
fn mount_info(mount_dir: &str) -> MountInfo {
129+
MountInfo {
130+
dev_id: Default::default(),
131+
dev_name: Default::default(),
132+
fs_type: Default::default(),
133+
mount_dir: String::from(mount_dir),
134+
mount_option: Default::default(),
135+
mount_root: Default::default(),
136+
remote: Default::default(),
137+
dummy: Default::default(),
138+
}
139+
}
140+
141+
// Check whether two `MountInfo` instances are equal.
142+
fn mount_info_eq(m1: &MountInfo, m2: &MountInfo) -> bool {
143+
m1.dev_id == m2.dev_id
144+
&& m1.dev_name == m2.dev_name
145+
&& m1.fs_type == m2.fs_type
146+
&& m1.mount_dir == m2.mount_dir
147+
&& m1.mount_option == m2.mount_option
148+
&& m1.mount_root == m2.mount_root
149+
&& m1.remote == m2.remote
150+
&& m1.dummy == m2.dummy
151+
}
152+
153+
#[test]
154+
fn test_empty_mounts() {
155+
assert!(mount_info_from_path(&[], "/", false).is_none());
156+
}
157+
158+
#[test]
159+
fn test_exact_match() {
160+
let mounts = [mount_info("/foo")];
161+
let actual = mount_info_from_path(&mounts, "/foo", false).unwrap();
162+
assert!(mount_info_eq(actual, &mounts[0]));
163+
}
164+
165+
#[test]
166+
fn test_prefix_match() {
167+
let mounts = [mount_info("/foo")];
168+
let actual = mount_info_from_path(&mounts, "/foo/bar", false).unwrap();
169+
assert!(mount_info_eq(actual, &mounts[0]));
170+
}
171+
172+
#[test]
173+
fn test_multiple_matches() {
174+
let mounts = [mount_info("/foo"), mount_info("/foo/bar")];
175+
let actual = mount_info_from_path(&mounts, "/foo/bar", false).unwrap();
176+
assert!(mount_info_eq(actual, &mounts[1]));
177+
}
178+
179+
#[test]
180+
fn test_no_match() {
181+
let mounts = [mount_info("/foo")];
182+
assert!(mount_info_from_path(&mounts, "/bar", false).is_none());
183+
}
184+
185+
#[test]
186+
fn test_partial_match() {
187+
let mounts = [mount_info("/foo/bar")];
188+
assert!(mount_info_from_path(&mounts, "/foo/baz", false).is_none());
189+
}
190+
}
55191
}

0 commit comments

Comments
 (0)