Merge pull request #8371 from drinkcat/df-osstr

df: Move to using `OsString`
This commit is contained in:
Daniel Hofstetter
2025-07-24 15:09:32 +02:00
committed by GitHub
11 changed files with 373 additions and 200 deletions
Generated
+1
View File
@@ -3977,6 +3977,7 @@ dependencies = [
"bigdecimal", "bigdecimal",
"blake2b_simd", "blake2b_simd",
"blake3", "blake3",
"bstr",
"chrono", "chrono",
"clap", "clap",
"crc32fast", "crc32fast",
+1
View File
@@ -1584,6 +1584,7 @@ dependencies = [
"bigdecimal", "bigdecimal",
"blake2b_simd", "blake2b_simd",
"blake3", "blake3",
"bstr",
"clap", "clap",
"crc32fast", "crc32fast",
"data-encoding", "data-encoding",
+1 -1
View File
@@ -101,7 +101,7 @@ impl OrderChecker {
return true; return true;
} }
let is_ordered = current_line >= &self.last_line; let is_ordered = *current_line >= *self.last_line;
if !is_ordered && !self.has_error { if !is_ordered && !self.has_error {
eprintln!( eprintln!(
"{}", "{}",
+10 -8
View File
@@ -20,6 +20,7 @@ use uucore::{format_usage, show};
use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource}; use clap::{Arg, ArgAction, ArgMatches, Command, parser::ValueSource};
use std::ffi::OsString; use std::ffi::OsString;
use std::io::stdout;
use std::path::Path; use std::path::Path;
use thiserror::Error; use thiserror::Error;
@@ -431,7 +432,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let opt = Options::from(&matches).map_err(DfError::OptionsError)?; let opt = Options::from(&matches).map_err(DfError::OptionsError)?;
// Get the list of filesystems to display in the output table. // Get the list of filesystems to display in the output table.
let filesystems: Vec<Filesystem> = match matches.get_many::<String>(OPT_PATHS) { let filesystems: Vec<Filesystem> = match matches.get_many::<OsString>(OPT_PATHS) {
None => { None => {
let filesystems = get_all_filesystems(&opt).map_err(|e| { let filesystems = get_all_filesystems(&opt).map_err(|e| {
let context = get_message("df-error-cannot-read-table-of-mounted-filesystems"); let context = get_message("df-error-cannot-read-table-of-mounted-filesystems");
@@ -464,7 +465,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
} }
}; };
println!("{}", Table::new(&opt, filesystems)); Table::new(&opt, filesystems).write_to(&mut stdout())?;
Ok(()) Ok(())
} }
@@ -611,6 +612,7 @@ pub fn uu_app() -> Command {
.arg( .arg(
Arg::new(OPT_PATHS) Arg::new(OPT_PATHS)
.action(ArgAction::Append) .action(ArgAction::Append)
.value_parser(ValueParser::os_string())
.value_hint(clap::ValueHint::AnyPath), .value_hint(clap::ValueHint::AnyPath),
) )
} }
@@ -629,9 +631,9 @@ mod tests {
dev_id: String::new(), dev_id: String::new(),
dev_name: String::from(dev_name), dev_name: String::from(dev_name),
fs_type: String::new(), fs_type: String::new(),
mount_dir: String::from(mount_dir), mount_dir: mount_dir.into(),
mount_option: String::new(), mount_option: String::new(),
mount_root: String::from(mount_root), mount_root: mount_root.into(),
remote: false, remote: false,
dummy: false, dummy: false,
} }
@@ -679,9 +681,9 @@ mod tests {
dev_id: String::from(dev_id), dev_id: String::from(dev_id),
dev_name: String::new(), dev_name: String::new(),
fs_type: String::new(), fs_type: String::new(),
mount_dir: String::from(mount_dir), mount_dir: mount_dir.into(),
mount_option: String::new(), mount_option: String::new(),
mount_root: String::new(), mount_root: "/".into(),
remote: false, remote: false,
dummy: false, dummy: false,
} }
@@ -724,9 +726,9 @@ mod tests {
dev_id: String::new(), dev_id: String::new(),
dev_name: String::new(), dev_name: String::new(),
fs_type: String::from(fs_type), fs_type: String::from(fs_type),
mount_dir: String::from(mount_dir), mount_dir: mount_dir.into(),
mount_option: String::new(), mount_option: String::new(),
mount_root: String::new(), mount_root: "/".into(),
remote, remote,
dummy, dummy,
} }
+17 -13
View File
@@ -8,7 +8,7 @@
//! filesystem mounted at a particular directory. It also includes //! filesystem mounted at a particular directory. It also includes
//! information on amount of space available and amount of space used. //! information on amount of space available and amount of space used.
// spell-checker:ignore canonicalized // spell-checker:ignore canonicalized
use std::path::Path; use std::{ffi::OsString, path::Path};
#[cfg(unix)] #[cfg(unix)]
use uucore::fsext::statfs; use uucore::fsext::statfs;
@@ -28,7 +28,7 @@ pub(crate) struct Filesystem {
/// When invoking `df` with a positional argument, it displays /// When invoking `df` with a positional argument, it displays
/// usage information for the filesystem that contains the given /// usage information for the filesystem that contains the given
/// file. If given, this field contains that filename. /// file. If given, this field contains that filename.
pub file: Option<String>, pub file: Option<OsString>,
/// Information about the mounted device, mount directory, and related options. /// Information about the mounted device, mount directory, and related options.
pub mount_info: MountInfo, pub mount_info: MountInfo,
@@ -123,22 +123,22 @@ where
impl Filesystem { impl Filesystem {
// TODO: resolve uuid in `mount_info.dev_name` if exists // TODO: resolve uuid in `mount_info.dev_name` if exists
pub(crate) fn new(mount_info: MountInfo, file: Option<String>) -> Option<Self> { pub(crate) fn new(mount_info: MountInfo, file: Option<OsString>) -> Option<Self> {
let _stat_path = if mount_info.mount_dir.is_empty() { let _stat_path = if mount_info.mount_dir.is_empty() {
#[cfg(unix)] #[cfg(unix)]
{ {
mount_info.dev_name.clone() mount_info.dev_name.clone().into()
} }
#[cfg(windows)] #[cfg(windows)]
{ {
// On windows, we expect the volume id // On windows, we expect the volume id
mount_info.dev_id.clone() mount_info.dev_id.clone().into()
} }
} else { } else {
mount_info.mount_dir.clone() mount_info.mount_dir.clone()
}; };
#[cfg(unix)] #[cfg(unix)]
let usage = FsUsage::new(statfs(_stat_path).ok()?); let usage = FsUsage::new(statfs(&_stat_path).ok()?);
#[cfg(windows)] #[cfg(windows)]
let usage = FsUsage::new(Path::new(&_stat_path)).ok()?; let usage = FsUsage::new(Path::new(&_stat_path)).ok()?;
Some(Self { Some(Self {
@@ -154,7 +154,7 @@ impl Filesystem {
pub(crate) fn from_mount( pub(crate) fn from_mount(
mounts: &[MountInfo], mounts: &[MountInfo],
mount: &MountInfo, mount: &MountInfo,
file: Option<String>, file: Option<OsString>,
) -> Result<Self, FsError> { ) -> Result<Self, FsError> {
if is_over_mounted(mounts, mount) { if is_over_mounted(mounts, mount) {
Err(FsError::OverMounted) Err(FsError::OverMounted)
@@ -165,7 +165,7 @@ impl Filesystem {
/// Find and create the filesystem from the given mount. /// Find and create the filesystem from the given mount.
#[cfg(windows)] #[cfg(windows)]
pub(crate) fn from_mount(mount: &MountInfo, file: Option<String>) -> Result<Self, FsError> { pub(crate) fn from_mount(mount: &MountInfo, file: Option<OsString>) -> Result<Self, FsError> {
Self::new(mount.clone(), file).ok_or(FsError::MountMissing) Self::new(mount.clone(), file).ok_or(FsError::MountMissing)
} }
@@ -189,7 +189,7 @@ impl Filesystem {
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
let file = path.as_ref().display().to_string(); let file = path.as_ref().as_os_str().to_owned();
let canonicalize = true; let canonicalize = true;
let result = mount_info_from_path(mounts, path, canonicalize); let result = mount_info_from_path(mounts, path, canonicalize);
@@ -205,6 +205,8 @@ mod tests {
mod mount_info_from_path { mod mount_info_from_path {
use std::ffi::OsString;
use uucore::fsext::MountInfo; use uucore::fsext::MountInfo;
use crate::filesystem::{FsError, mount_info_from_path}; use crate::filesystem::{FsError, mount_info_from_path};
@@ -215,9 +217,9 @@ mod tests {
dev_id: String::default(), dev_id: String::default(),
dev_name: String::default(), dev_name: String::default(),
fs_type: String::default(), fs_type: String::default(),
mount_dir: String::from(mount_dir), mount_dir: OsString::from(mount_dir),
mount_option: String::default(), mount_option: String::default(),
mount_root: String::default(), mount_root: OsString::default(),
remote: Default::default(), remote: Default::default(),
dummy: Default::default(), dummy: Default::default(),
} }
@@ -312,6 +314,8 @@ mod tests {
#[cfg(not(windows))] #[cfg(not(windows))]
mod over_mount { mod over_mount {
use std::ffi::OsString;
use crate::filesystem::{Filesystem, FsError, is_over_mounted}; use crate::filesystem::{Filesystem, FsError, is_over_mounted};
use uucore::fsext::MountInfo; use uucore::fsext::MountInfo;
@@ -320,9 +324,9 @@ mod tests {
dev_id: String::default(), dev_id: String::default(),
dev_name: dev_name.map(String::from).unwrap_or_default(), dev_name: dev_name.map(String::from).unwrap_or_default(),
fs_type: String::default(), fs_type: String::default(),
mount_dir: String::from(mount_dir), mount_dir: OsString::from(mount_dir),
mount_option: String::default(), mount_option: String::default(),
mount_root: String::default(), mount_root: OsString::default(),
remote: Default::default(), remote: Default::default(),
dummy: Default::default(), dummy: Default::default(),
} }
+202 -83
View File
File diff suppressed because it is too large Load Diff
+6 -10
View File
@@ -22,7 +22,6 @@ use std::ffi::{OsStr, OsString};
use std::fs::{FileType, Metadata}; use std::fs::{FileType, Metadata};
use std::io::Write; use std::io::Write;
use std::os::unix::fs::{FileTypeExt, MetadataExt}; use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::os::unix::prelude::OsStrExt;
use std::path::Path; use std::path::Path;
use std::{env, fs}; use std::{env, fs};
@@ -258,7 +257,7 @@ struct Stater {
show_fs: bool, show_fs: bool,
from_user: bool, from_user: bool,
files: Vec<OsString>, files: Vec<OsString>,
mount_list: Option<Vec<String>>, mount_list: Option<Vec<OsString>>,
default_tokens: Vec<Token>, default_tokens: Vec<Token>,
default_dev_tokens: Vec<Token>, default_dev_tokens: Vec<Token>,
} }
@@ -876,7 +875,7 @@ impl Stater {
})? })?
.iter() .iter()
.map(|mi| mi.mount_dir.clone()) .map(|mi| mi.mount_dir.clone())
.collect::<Vec<String>>(); .collect::<Vec<_>>();
// Reverse sort. The longer comes first. // Reverse sort. The longer comes first.
mount_list.sort(); mount_list.sort();
mount_list.reverse(); mount_list.reverse();
@@ -899,7 +898,8 @@ impl Stater {
for root in self.mount_list.as_ref()? { for root in self.mount_list.as_ref()? {
if path.starts_with(root) { if path.starts_with(root) {
return Some(root.clone()); // TODO: This is probably wrong, we should pass the OsString
return Some(root.to_string_lossy().into_owned());
} }
} }
None None
@@ -992,7 +992,7 @@ impl Stater {
'h' => OutputType::Unsigned(meta.nlink()), 'h' => OutputType::Unsigned(meta.nlink()),
// inode number // inode number
'i' => OutputType::Unsigned(meta.ino()), 'i' => OutputType::Unsigned(meta.ino()),
// mount point // mount point: TODO: This should be an OsStr
'm' => OutputType::Str(self.find_mount_point(file).unwrap()), 'm' => OutputType::Str(self.find_mount_point(file).unwrap()),
// file name // file name
'n' => OutputType::Str(display_name.to_string()), 'n' => OutputType::Str(display_name.to_string()),
@@ -1092,11 +1092,7 @@ impl Stater {
OsString::from(file) OsString::from(file)
}; };
if self.show_fs { if self.show_fs {
#[cfg(unix)] match statfs(&file) {
let p = file.as_bytes();
#[cfg(not(unix))]
let p = file.into_string().unwrap();
match statfs(p) {
Ok(meta) => { Ok(meta) => {
let tokens = &self.default_tokens; let tokens = &self.default_tokens;
+1
View File
@@ -19,6 +19,7 @@ all-features = true
path = "src/lib/lib.rs" path = "src/lib/lib.rs"
[dependencies] [dependencies]
bstr = { workspace = true }
chrono = { workspace = true, optional = true } chrono = { workspace = true, optional = true }
clap = { workspace = true } clap = { workspace = true }
uucore_procs = { workspace = true } uucore_procs = { workspace = true }
+90 -46
View File
@@ -19,17 +19,18 @@ const MAX_PATH: usize = 266;
static EXIT_ERR: i32 = 1; static EXIT_ERR: i32 = 1;
#[cfg(any( #[cfg(any(
windows,
target_os = "freebsd", target_os = "freebsd",
target_vendor = "apple", target_vendor = "apple",
target_os = "netbsd", target_os = "netbsd",
target_os = "openbsd" target_os = "openbsd"
))] ))]
use crate::os_str_from_bytes;
#[cfg(windows)] #[cfg(windows)]
use crate::show_warning; use crate::show_warning;
#[cfg(windows)]
use std::ffi::OsStr; use std::ffi::OsStr;
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::ffi::OsStrExt; use std::os::windows::ffi::OsStrExt;
#[cfg(windows)] #[cfg(windows)]
@@ -61,17 +62,15 @@ fn to_nul_terminated_wide_string(s: impl AsRef<OsStr>) -> Vec<u16> {
use libc::{ use libc::{
S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK, mode_t, strerror,
}; };
use std::borrow::Cow;
#[cfg(unix)] #[cfg(unix)]
use std::ffi::CStr; use std::ffi::{CStr, CString};
#[cfg(unix)]
use std::ffi::CString;
use std::io::Error as IOError; use std::io::Error as IOError;
#[cfg(unix)] #[cfg(unix)]
use std::mem; use std::mem;
#[cfg(windows)] #[cfg(windows)]
use std::path::Path; use std::path::Path;
use std::time::UNIX_EPOCH; use std::time::UNIX_EPOCH;
use std::{borrow::Cow, ffi::OsString};
#[cfg(any( #[cfg(any(
target_os = "linux", target_os = "linux",
@@ -123,14 +122,16 @@ impl BirthTime for Metadata {
} }
} }
// TODO: Types for this struct are probably mostly wrong. Possibly, most of them
// should be OsString.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MountInfo { pub struct MountInfo {
/// Stores `volume_name` in windows platform and `dev_id` in unix platform /// Stores `volume_name` in windows platform and `dev_id` in unix platform
pub dev_id: String, pub dev_id: String,
pub dev_name: String, pub dev_name: String,
pub fs_type: String, pub fs_type: String,
pub mount_root: String, pub mount_root: OsString,
pub mount_dir: String, pub mount_dir: OsString,
/// We only care whether this field contains "bind" /// We only care whether this field contains "bind"
pub mount_option: String, pub mount_option: String,
pub remote: bool, pub remote: bool,
@@ -138,7 +139,9 @@ pub struct MountInfo {
} }
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
fn replace_special_chars(s: String) -> String { fn replace_special_chars(s: &[u8]) -> Vec<u8> {
use bstr::ByteSlice;
// Replace // Replace
// //
// * ASCII space with a regular space character, // * ASCII space with a regular space character,
@@ -152,7 +155,11 @@ fn replace_special_chars(s: String) -> String {
impl MountInfo { impl MountInfo {
#[cfg(any(target_os = "linux", target_os = "android"))] #[cfg(any(target_os = "linux", target_os = "android"))]
fn new(file_name: &str, raw: &[&str]) -> Option<Self> { fn new(file_name: &str, raw: &[&[u8]]) -> Option<Self> {
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::ffi::OsStringExt;
let dev_name; let dev_name;
let fs_type; let fs_type;
let mount_root; let mount_root;
@@ -165,21 +172,24 @@ impl MountInfo {
// "man proc" for more details // "man proc" for more details
LINUX_MOUNTINFO => { LINUX_MOUNTINFO => {
const FIELDS_OFFSET: usize = 6; const FIELDS_OFFSET: usize = 6;
let after_fields = raw[FIELDS_OFFSET..].iter().position(|c| *c == "-").unwrap() let after_fields = raw[FIELDS_OFFSET..]
.iter()
.position(|c| *c == b"-")
.unwrap()
+ FIELDS_OFFSET + FIELDS_OFFSET
+ 1; + 1;
dev_name = raw[after_fields + 1].to_string(); dev_name = String::from_utf8_lossy(raw[after_fields + 1]).to_string();
fs_type = raw[after_fields].to_string(); fs_type = String::from_utf8_lossy(raw[after_fields]).to_string();
mount_root = raw[3].to_string(); mount_root = OsStr::from_bytes(raw[3]).to_owned();
mount_dir = replace_special_chars(raw[4].to_string()); mount_dir = OsString::from_vec(replace_special_chars(raw[4]));
mount_option = raw[5].to_string(); mount_option = String::from_utf8_lossy(raw[5]).to_string();
} }
LINUX_MTAB => { LINUX_MTAB => {
dev_name = raw[0].to_string(); dev_name = String::from_utf8_lossy(raw[0]).to_string();
fs_type = raw[2].to_string(); fs_type = String::from_utf8_lossy(raw[2]).to_string();
mount_root = String::new(); mount_root = OsString::new();
mount_dir = replace_special_chars(raw[1].to_string()); mount_dir = OsString::from_vec(replace_special_chars(raw[1]));
mount_option = raw[3].to_string(); mount_option = String::from_utf8_lossy(raw[3]).to_string();
} }
_ => return None, _ => return None,
}; };
@@ -233,6 +243,8 @@ impl MountInfo {
// TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA` // TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
return None; return None;
} }
// TODO: This should probably call `OsString::from_wide`, but unclear if
// terminating zeros need to be striped first.
let mount_root = LPWSTR2String(&mount_root_buf); let mount_root = LPWSTR2String(&mount_root_buf);
let mut fs_type_buf = [0u16; MAX_PATH]; let mut fs_type_buf = [0u16; MAX_PATH];
@@ -263,8 +275,8 @@ impl MountInfo {
dev_id: volume_name, dev_id: volume_name,
dev_name, dev_name,
fs_type: fs_type.unwrap_or_default(), fs_type: fs_type.unwrap_or_default(),
mount_root, mount_root: mount_root.into(), // TODO: We should figure out how to keep an OsString here.
mount_dir: String::new(), mount_dir: OsString::new(),
mount_option: String::new(), mount_option: String::new(),
remote, remote,
dummy: false, dummy: false,
@@ -292,12 +304,11 @@ impl From<StatFs> for MountInfo {
.to_string_lossy() .to_string_lossy()
.into_owned() .into_owned()
}; };
let mount_dir = unsafe { let mount_dir_bytes = unsafe {
// spell-checker:disable-next-line // spell-checker:disable-next-line
CStr::from_ptr(&statfs.f_mntonname[0]) CStr::from_ptr(&statfs.f_mntonname[0]).to_bytes()
.to_string_lossy()
.into_owned()
}; };
let mount_dir = os_str_from_bytes(mount_dir_bytes).unwrap().into_owned();
let dev_id = mount_dev_id(&mount_dir); let dev_id = mount_dev_id(&mount_dir);
let dummy = is_dummy_filesystem(&fs_type, ""); let dummy = is_dummy_filesystem(&fs_type, "");
@@ -308,7 +319,7 @@ impl From<StatFs> for MountInfo {
dev_name, dev_name,
fs_type, fs_type,
mount_dir, mount_dir,
mount_root: String::new(), mount_root: OsString::new(),
mount_option: String::new(), mount_option: String::new(),
remote, remote,
dummy, dummy,
@@ -343,7 +354,7 @@ fn is_remote_filesystem(dev_name: &str, fs_type: &str) -> bool {
} }
#[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))] #[cfg(all(unix, not(any(target_os = "aix", target_os = "redox"))))]
fn mount_dev_id(mount_dir: &str) -> String { fn mount_dev_id(mount_dir: &OsStr) -> String {
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
if let Ok(stat) = std::fs::metadata(mount_dir) { if let Ok(stat) = std::fs::metadata(mount_dir) {
@@ -426,10 +437,10 @@ pub fn read_fs_list() -> UResult<Vec<MountInfo>> {
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))?; .or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))?;
let reader = BufReader::new(f); let reader = BufReader::new(f);
Ok(reader Ok(reader
.lines() .split(b'\n')
.map_while(Result::ok) .map_while(Result::ok)
.filter_map(|line| { .filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>(); let raw_data = line.split(|c| *c == b' ').collect::<Vec<&[u8]>>();
MountInfo::new(file_name, &raw_data) MountInfo::new(file_name, &raw_data)
}) })
.collect::<Vec<_>>()) .collect::<Vec<_>>())
@@ -855,11 +866,13 @@ impl FsMeta for StatFs {
} }
#[cfg(unix)] #[cfg(unix)]
pub fn statfs<P>(path: P) -> Result<StatFs, String> pub fn statfs(path: &OsStr) -> Result<StatFs, String> {
where #[cfg(unix)]
P: Into<Vec<u8>>, let p = path.as_bytes();
{ #[cfg(not(unix))]
match CString::new(path) { let p = path.into_string().unwrap();
match CString::new(p) {
Ok(p) => { Ok(p) => {
let mut buffer: StatFs = unsafe { mem::zeroed() }; let mut buffer: StatFs = unsafe { mem::zeroed() };
unsafe { unsafe {
@@ -1060,8 +1073,8 @@ mod tests {
// spell-checker:ignore (word) relatime // spell-checker:ignore (word) relatime
let info = MountInfo::new( let info = MountInfo::new(
LINUX_MOUNTINFO, LINUX_MOUNTINFO,
&"106 109 253:6 / /mnt rw,relatime - xfs /dev/fs0 rw" &b"106 109 253:6 / /mnt rw,relatime - xfs /dev/fs0 rw"
.split_ascii_whitespace() .split(|c| *c == b' ')
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .unwrap();
@@ -1075,8 +1088,8 @@ mod tests {
// Test parsing with different amounts of optional fields. // Test parsing with different amounts of optional fields.
let info = MountInfo::new( let info = MountInfo::new(
LINUX_MOUNTINFO, LINUX_MOUNTINFO,
&"106 109 253:6 / /mnt rw,relatime master:1 - xfs /dev/fs0 rw" &b"106 109 253:6 / /mnt rw,relatime master:1 - xfs /dev/fs0 rw"
.split_ascii_whitespace() .split(|c| *c == b' ')
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .unwrap();
@@ -1086,8 +1099,8 @@ mod tests {
let info = MountInfo::new( let info = MountInfo::new(
LINUX_MOUNTINFO, LINUX_MOUNTINFO,
&"106 109 253:6 / /mnt rw,relatime master:1 shared:2 - xfs /dev/fs0 rw" &b"106 109 253:6 / /mnt rw,relatime master:1 shared:2 - xfs /dev/fs0 rw"
.split_ascii_whitespace() .split(|c| *c == b' ')
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .unwrap();
@@ -1101,8 +1114,8 @@ mod tests {
fn test_mountinfo_dir_special_chars() { fn test_mountinfo_dir_special_chars() {
let info = MountInfo::new( let info = MountInfo::new(
LINUX_MOUNTINFO, LINUX_MOUNTINFO,
&r#"317 61 7:0 / /mnt/f\134\040\011oo rw,relatime shared:641 - ext4 /dev/loop0 rw"# &br#"317 61 7:0 / /mnt/f\134\040\011oo rw,relatime shared:641 - ext4 /dev/loop0 rw"#
.split_ascii_whitespace() .split(|c| *c == b' ')
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .unwrap();
@@ -1111,12 +1124,43 @@ mod tests {
let info = MountInfo::new( let info = MountInfo::new(
LINUX_MTAB, LINUX_MTAB,
&r#"/dev/loop0 /mnt/f\134\040\011oo ext4 rw,relatime 0 0"# &br#"/dev/loop0 /mnt/f\134\040\011oo ext4 rw,relatime 0 0"#
.split_ascii_whitespace() .split(|c| *c == b' ')
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.unwrap(); .unwrap();
assert_eq!(info.mount_dir, r#"/mnt/f\ oo"#); assert_eq!(info.mount_dir, r#"/mnt/f\ oo"#);
} }
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_mountinfo_dir_non_unicode() {
let info = MountInfo::new(
LINUX_MOUNTINFO,
&b"317 61 7:0 / /mnt/some-\xc0-dir-\xf3 rw,relatime shared:641 - ext4 /dev/loop0 rw"
.split(|c| *c == b' ')
.collect::<Vec<_>>(),
)
.unwrap();
assert_eq!(
info.mount_dir,
crate::os_str_from_bytes(b"/mnt/some-\xc0-dir-\xf3").unwrap()
);
let info = MountInfo::new(
LINUX_MOUNTINFO,
&b"317 61 7:0 / /mnt/some-\\040-dir-\xf3 rw,relatime shared:641 - ext4 /dev/loop0 rw"
.split(|c| *c == b' ')
.collect::<Vec<_>>(),
)
.unwrap();
// Note that the \040 above will have been substituted by a space.
assert_eq!(
info.mount_dir,
crate::os_str_from_bytes(b"/mnt/some- -dir-\xf3").unwrap()
);
}
} }
+39 -39
View File
@@ -117,7 +117,7 @@ fn test_df_output() {
.arg("-H") .arg("-H")
.arg("--total") .arg("--total")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let actual = output.lines().take(1).collect::<Vec<&str>>()[0]; let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
let actual = actual.split_whitespace().collect::<Vec<_>>(); let actual = actual.split_whitespace().collect::<Vec<_>>();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@@ -151,7 +151,7 @@ fn test_df_output_overridden() {
.arg("-hH") .arg("-hH")
.arg("--total") .arg("--total")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let actual = output.lines().take(1).collect::<Vec<&str>>()[0]; let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
let actual = actual.split_whitespace().collect::<Vec<_>>(); let actual = actual.split_whitespace().collect::<Vec<_>>();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@@ -181,7 +181,7 @@ fn test_default_headers() {
"on", "on",
] ]
}; };
let output = new_ucmd!().succeeds().stdout_move_str(); let output = new_ucmd!().succeeds().stdout_str_lossy();
let actual = output.lines().take(1).collect::<Vec<&str>>()[0]; let actual = output.lines().take(1).collect::<Vec<&str>>()[0];
let actual = actual.split_whitespace().collect::<Vec<_>>(); let actual = actual.split_whitespace().collect::<Vec<_>>();
assert_eq!(actual, expected); assert_eq!(actual, expected);
@@ -195,7 +195,7 @@ fn test_precedence_of_human_readable_and_si_header_over_output_header() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&[arg, "--output=size"]) .args(&[arg, "--output=size"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap(); let header = output.lines().next().unwrap();
assert_eq!(header, " Size"); assert_eq!(header, " Size");
} }
@@ -207,7 +207,7 @@ fn test_used_header_starts_with_space() {
// using -h here to ensure the width of the column's content is <= 4 // using -h here to ensure the width of the column's content is <= 4
.args(&["-h", "--output=used"]) .args(&["-h", "--output=used"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap(); let header = output.lines().next().unwrap();
assert_eq!(header, " Used"); assert_eq!(header, " Used");
} }
@@ -226,11 +226,11 @@ fn test_order_same() {
let output1 = new_ucmd!() let output1 = new_ucmd!()
.arg("--output=source") .arg("--output=source")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let output2 = new_ucmd!() let output2 = new_ucmd!()
.arg("--output=source") .arg("--output=source")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!(output1, output2); assert_eq!(output1, output2);
} }
@@ -238,7 +238,7 @@ fn test_order_same() {
#[cfg(all(unix, not(target_os = "freebsd")))] // FIXME: fix this test for FreeBSD #[cfg(all(unix, not(target_os = "freebsd")))] // FIXME: fix this test for FreeBSD
#[test] #[test]
fn test_output_mp_repeat() { fn test_output_mp_repeat() {
let output1 = new_ucmd!().arg("/").arg("/").succeeds().stdout_move_str(); let output1 = new_ucmd!().arg("/").arg("/").succeeds().stdout_str_lossy();
let output1: Vec<String> = output1 let output1: Vec<String> = output1
.lines() .lines()
.map(|l| String::from(l.split_once(' ').unwrap().0)) .map(|l| String::from(l.split_once(' ').unwrap().0))
@@ -272,7 +272,7 @@ fn test_type_option() {
let fs_types = new_ucmd!() let fs_types = new_ucmd!()
.arg("--output=fstype") .arg("--output=fstype")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let fs_type = fs_types.lines().nth(1).unwrap().trim(); let fs_type = fs_types.lines().nth(1).unwrap().trim();
new_ucmd!().args(&["-t", fs_type]).succeeds(); new_ucmd!().args(&["-t", fs_type]).succeeds();
@@ -292,7 +292,7 @@ fn test_type_option_with_file() {
let fs_type = new_ucmd!() let fs_type = new_ucmd!()
.args(&["--output=fstype", "."]) .args(&["--output=fstype", "."])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let fs_type = fs_type.lines().nth(1).unwrap().trim(); let fs_type = fs_type.lines().nth(1).unwrap().trim();
new_ucmd!().args(&["-t", fs_type, "."]).succeeds(); new_ucmd!().args(&["-t", fs_type, "."]).succeeds();
@@ -310,7 +310,7 @@ fn test_type_option_with_file() {
let fs_types = new_ucmd!() let fs_types = new_ucmd!()
.arg("--output=fstype") .arg("--output=fstype")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let fs_types: Vec<_> = fs_types let fs_types: Vec<_> = fs_types
.lines() .lines()
.skip(1) .skip(1)
@@ -335,7 +335,7 @@ fn test_exclude_all_types() {
let fs_types = new_ucmd!() let fs_types = new_ucmd!()
.arg("--output=fstype") .arg("--output=fstype")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let fs_types: HashSet<_> = fs_types.lines().skip(1).collect(); let fs_types: HashSet<_> = fs_types.lines().skip(1).collect();
let mut args = Vec::new(); let mut args = Vec::new();
@@ -379,7 +379,7 @@ fn test_total() {
// ... // ...
// /dev/loop14 63488 63488 0 100% /snap/core20/1361 // /dev/loop14 63488 63488 0 100% /snap/core20/1361
// total 258775268 98099712 148220200 40% - // total 258775268 98099712 148220200 40% -
let output = new_ucmd!().arg("--total").succeeds().stdout_move_str(); let output = new_ucmd!().arg("--total").succeeds().stdout_str_lossy();
// Skip the header line. // Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect(); let lines: Vec<&str> = output.lines().skip(1).collect();
@@ -422,21 +422,21 @@ fn test_total_label_in_correct_column() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source", "--total", "."]) .args(&["--output=source", "--total", "."])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let last_line = output.lines().last().unwrap(); let last_line = output.lines().last().unwrap();
assert_eq!(last_line.trim(), "total"); assert_eq!(last_line.trim(), "total");
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=target", "--total", "."]) .args(&["--output=target", "--total", "."])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let last_line = output.lines().last().unwrap(); let last_line = output.lines().last().unwrap();
assert_eq!(last_line.trim(), "total"); assert_eq!(last_line.trim(), "total");
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source,target", "--total", "."]) .args(&["--output=source,target", "--total", "."])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let last_line = output.lines().last().unwrap(); let last_line = output.lines().last().unwrap();
assert_eq!( assert_eq!(
last_line.split_whitespace().collect::<Vec<&str>>(), last_line.split_whitespace().collect::<Vec<&str>>(),
@@ -446,7 +446,7 @@ fn test_total_label_in_correct_column() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=target,source", "--total", "."]) .args(&["--output=target,source", "--total", "."])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let last_line = output.lines().last().unwrap(); let last_line = output.lines().last().unwrap();
assert_eq!( assert_eq!(
last_line.split_whitespace().collect::<Vec<&str>>(), last_line.split_whitespace().collect::<Vec<&str>>(),
@@ -463,7 +463,7 @@ fn test_use_percentage() {
// "percentage" values. // "percentage" values.
.args(&["--total", "--output=used,avail,pcent", "--block-size=1"]) .args(&["--total", "--output=used,avail,pcent", "--block-size=1"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
// Skip the header line. // Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect(); let lines: Vec<&str> = output.lines().skip(1).collect();
@@ -488,7 +488,7 @@ fn test_iuse_percentage() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--total", "--output=itotal,iused,ipcent"]) .args(&["--total", "--output=itotal,iused,ipcent"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
// Skip the header line. // Skip the header line.
let lines: Vec<&str> = output.lines().skip(1).collect(); let lines: Vec<&str> = output.lines().skip(1).collect();
@@ -518,7 +518,7 @@ fn test_default_block_size() {
let output = new_ucmd!() let output = new_ucmd!()
.arg("--output=size") .arg("--output=size")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string(); let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, "1K-blocks"); assert_eq!(header, "1K-blocks");
@@ -527,7 +527,7 @@ fn test_default_block_size() {
.arg("--output=size") .arg("--output=size")
.env("POSIXLY_CORRECT", "1") .env("POSIXLY_CORRECT", "1")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string(); let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, "512B-blocks"); assert_eq!(header, "512B-blocks");
@@ -547,14 +547,14 @@ fn test_default_block_size_in_posix_portability_mode() {
.to_string() .to_string()
} }
let output = new_ucmd!().arg("-P").succeeds().stdout_move_str(); let output = new_ucmd!().arg("-P").succeeds().stdout_str_lossy();
assert_eq!(get_header(&output), "1024-blocks"); assert_eq!(get_header(&output), "1024-blocks");
let output = new_ucmd!() let output = new_ucmd!()
.arg("-P") .arg("-P")
.env("POSIXLY_CORRECT", "1") .env("POSIXLY_CORRECT", "1")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!(get_header(&output), "512-blocks"); assert_eq!(get_header(&output), "512-blocks");
} }
@@ -564,7 +564,7 @@ fn test_block_size_1024() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["-B", &format!("{block_size}"), "--output=size"]) .args(&["-B", &format!("{block_size}"), "--output=size"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
output.lines().next().unwrap().trim().to_string() output.lines().next().unwrap().trim().to_string()
} }
@@ -588,7 +588,7 @@ fn test_block_size_with_suffix() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["-B", block_size, "--output=size"]) .args(&["-B", block_size, "--output=size"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
output.lines().next().unwrap().trim().to_string() output.lines().next().unwrap().trim().to_string()
} }
@@ -612,7 +612,7 @@ fn test_block_size_in_posix_portability_mode() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["-P", "-B", block_size]) .args(&["-P", "-B", block_size])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
output output
.lines() .lines()
.next() .next()
@@ -639,7 +639,7 @@ fn test_block_size_from_env() {
.arg("--output=size") .arg("--output=size")
.env(env_var, env_value) .env(env_var, env_value)
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
output.lines().next().unwrap().trim().to_string() output.lines().next().unwrap().trim().to_string()
} }
@@ -658,7 +658,7 @@ fn test_block_size_from_env_precedences() {
.env(k1, v1) .env(k1, v1)
.env(k2, v2) .env(k2, v2)
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
output.lines().next().unwrap().trim().to_string() output.lines().next().unwrap().trim().to_string()
} }
@@ -677,7 +677,7 @@ fn test_precedence_of_block_size_arg_over_env() {
.args(&["-B", "999", "--output=size"]) .args(&["-B", "999", "--output=size"])
.env("DF_BLOCK_SIZE", "111") .env("DF_BLOCK_SIZE", "111")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string(); let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, "999B-blocks"); assert_eq!(header, "999B-blocks");
@@ -691,7 +691,7 @@ fn test_invalid_block_size_from_env() {
.arg("--output=size") .arg("--output=size")
.env("DF_BLOCK_SIZE", "invalid") .env("DF_BLOCK_SIZE", "invalid")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string(); let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, default_block_size_header); assert_eq!(header, default_block_size_header);
@@ -701,7 +701,7 @@ fn test_invalid_block_size_from_env() {
.env("DF_BLOCK_SIZE", "invalid") .env("DF_BLOCK_SIZE", "invalid")
.env("BLOCK_SIZE", "222") .env("BLOCK_SIZE", "222")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output.lines().next().unwrap().trim().to_string(); let header = output.lines().next().unwrap().trim().to_string();
assert_eq!(header, default_block_size_header); assert_eq!(header, default_block_size_header);
@@ -717,7 +717,7 @@ fn test_ignore_block_size_from_env_in_posix_portability_mode() {
.env("BLOCK_SIZE", "222") .env("BLOCK_SIZE", "222")
.env("BLOCKSIZE", "333") .env("BLOCKSIZE", "333")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let header = output let header = output
.lines() .lines()
.next() .next()
@@ -784,13 +784,13 @@ fn test_output_selects_columns() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source"]) .args(&["--output=source"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!(output.lines().next().unwrap(), "Filesystem"); assert_eq!(output.lines().next().unwrap(), "Filesystem");
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source,target"]) .args(&["--output=source,target"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!( assert_eq!(
output output
.lines() .lines()
@@ -804,7 +804,7 @@ fn test_output_selects_columns() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source,target,used"]) .args(&["--output=source,target,used"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!( assert_eq!(
output output
.lines() .lines()
@@ -821,7 +821,7 @@ fn test_output_multiple_occurrences() {
let output = new_ucmd!() let output = new_ucmd!()
.args(&["--output=source", "--output=target"]) .args(&["--output=source", "--output=target"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
assert_eq!( assert_eq!(
output output
.lines() .lines()
@@ -840,7 +840,7 @@ fn test_output_file_all_filesystems() {
let output = new_ucmd!() let output = new_ucmd!()
.arg("--output=file") .arg("--output=file")
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let mut lines = output.lines(); let mut lines = output.lines();
assert_eq!(lines.next().unwrap(), "File"); assert_eq!(lines.next().unwrap(), "File");
for line in lines { for line in lines {
@@ -862,7 +862,7 @@ fn test_output_file_specific_files() {
let output = ucmd let output = ucmd
.args(&["--output=file", "a", "b", "c"]) .args(&["--output=file", "a", "b", "c"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let actual: Vec<&str> = output.lines().collect(); let actual: Vec<&str> = output.lines().collect();
assert_eq!(actual, vec!["File", "a", "b", "c"]); assert_eq!(actual, vec!["File", "a", "b", "c"]);
} }
@@ -876,7 +876,7 @@ fn test_file_column_width_if_filename_contains_unicode_chars() {
let output = ucmd let output = ucmd
.args(&["--output=file,target", "äöü.txt"]) .args(&["--output=file,target", "äöü.txt"])
.succeeds() .succeeds()
.stdout_move_str(); .stdout_str_lossy();
let actual = output.lines().next().unwrap(); let actual = output.lines().next().unwrap();
// expected width: 7 chars (length of äöü.txt) + 1 char (column separator) // expected width: 7 chars (length of äöü.txt) + 1 char (column separator)
assert_eq!(actual, "File Mounted on"); assert_eq!(actual, "File Mounted on");
+5
View File
@@ -357,6 +357,11 @@ impl CmdResult {
std::str::from_utf8(&self.stdout).unwrap() std::str::from_utf8(&self.stdout).unwrap()
} }
/// Returns the program's standard output as a string, automatically handling invalid utf8
pub fn stdout_str_lossy(self) -> String {
String::from_utf8_lossy(&self.stdout).to_string()
}
/// Returns the program's standard output as a string /// Returns the program's standard output as a string
/// consumes self /// consumes self
pub fn stdout_move_str(self) -> String { pub fn stdout_move_str(self) -> String {