Merge pull request #2199 from jhscheer/refactor_fsext

df/stat: refactor - reduce duplicate code
This commit is contained in:
Sylvestre Ledru 2021-05-12 08:41:16 +02:00 committed by GitHub
commit a5f8ca60b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 555 additions and 764 deletions

View file

@ -16,14 +16,10 @@ path = "src/df.rs"
[dependencies]
clap = "2.33"
libc = "0.2"
number_prefix = "0.4"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore" }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["libc", "fsext"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
[[bin]]
name = "df"
path = "src/main.rs"

View file

@ -6,22 +6,17 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) mountinfo mtab BLOCKSIZE getmntinfo fobj mptr noatime Iused overmounted
// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statfs statvfs subfs syncreads syncwrites sysfs wcslen
// spell-checker:ignore (ToDO) mountinfo BLOCKSIZE fobj mptr noatime Iused overmounted
// spell-checker:ignore (libc/fs) asyncreads asyncwrites autofs bavail bfree bsize charspare cifs debugfs devfs devpts ffree frsize fsid fstypename fusectl inode inodes iosize kernfs mntbufp mntfromname mntonname mqueue namemax pipefs smbfs statvfs subfs syncreads syncwrites sysfs wcslen
#[macro_use]
extern crate uucore;
#[cfg(unix)]
use uucore::fsext::statfs_fn;
use uucore::fsext::{read_fs_list, FsUsage, MountInfo};
use clap::{App, Arg};
#[cfg(windows)]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(windows)]
use winapi::um::fileapi::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW,
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
};
use number_prefix::NumberPrefix;
use std::cell::Cell;
use std::collections::HashMap;
@ -32,41 +27,11 @@ use std::ffi::CString;
#[cfg(unix)]
use std::mem;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use libc::c_int;
#[cfg(target_vendor = "apple")]
use libc::statfs;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
#[cfg(target_os = "freebsd")]
use libc::{c_char, fsid_t, uid_t};
use uucore::libc::{c_char, fsid_t, uid_t};
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use winapi::shared::minwindef::DWORD;
#[cfg(windows)]
use winapi::um::fileapi::GetDiskFreeSpaceW;
#[cfg(windows)]
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
#[cfg(windows)]
use winapi::um::winbase::DRIVE_REMOTE;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Show information about the file system on which each FILE resides,\n\
@ -75,14 +40,6 @@ static ABOUT: &str = "Show information about the file system on which each FILE
static EXIT_OK: i32 = 0;
static EXIT_ERR: i32 = 1;
#[cfg(windows)]
const MAX_PATH: usize = 266;
#[cfg(target_os = "linux")]
static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
#[cfg(target_os = "linux")]
static LINUX_MTAB: &str = "/etc/mtab";
static OPT_ALL: &str = "all";
static OPT_BLOCKSIZE: &str = "blocksize";
static OPT_DIRECT: &str = "direct";
@ -101,8 +58,6 @@ static OPT_TYPE: &str = "type";
static OPT_PRINT_TYPE: &str = "print-type";
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
static MOUNT_OPT_BIND: &str = "bind";
/// Store names of file systems as a selector.
/// Note: `exclude` takes priority over `include`.
struct FsSelector {
@ -121,136 +76,16 @@ struct Options {
fs_selector: FsSelector,
}
#[derive(Debug, Clone)]
struct MountInfo {
// it stores `volume_name` in windows platform and `dev_id` in unix platform
dev_id: String,
dev_name: String,
fs_type: String,
mount_dir: String,
mount_option: String, // we only care "bind" option
mount_root: String,
remote: bool,
dummy: bool,
}
#[cfg(all(
target_os = "freebsd",
not(all(target_vendor = "apple", target_arch = "x86_64"))
))]
#[repr(C)]
#[derive(Copy, Clone)]
#[allow(non_camel_case_types)]
struct statfs {
f_version: u32,
f_type: u32,
f_flags: u64,
f_bsize: u64,
f_iosize: u64,
f_blocks: u64,
f_bfree: u64,
f_bavail: i64,
f_files: u64,
f_ffree: i64,
f_syncwrites: u64,
f_asyncwrites: u64,
f_syncreads: u64,
f_asyncreads: u64,
f_spare: [u64; 10usize],
f_namemax: u32,
f_owner: uid_t,
f_fsid: fsid_t,
f_charspare: [c_char; 80usize],
f_fstypename: [c_char; 16usize],
f_mntfromname: [c_char; 88usize],
f_mntonname: [c_char; 88usize],
}
#[derive(Debug, Clone)]
struct FsUsage {
blocksize: u64,
blocks: u64,
bfree: u64,
bavail: u64,
bavail_top_bit_set: bool,
files: u64,
ffree: u64,
}
#[derive(Debug, Clone)]
struct Filesystem {
mountinfo: MountInfo,
usage: FsUsage,
}
#[cfg(windows)]
macro_rules! String2LPWSTR {
($str: expr) => {
OsString::from($str.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect::<Vec<u16>>()
.as_ptr()
};
}
#[cfg(windows)]
#[allow(non_snake_case)]
fn LPWSTR2String(buf: &[u16]) -> String {
let len = unsafe { libc::wcslen(buf.as_ptr()) };
OsString::from_wide(&buf[..len as usize])
.into_string()
.unwrap()
}
fn get_usage() -> String {
format!("{0} [OPTION]... [FILE]...", executable!())
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
#[cfg(any(
all(target_os = "freebsd"),
all(target_vendor = "apple", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<statfs> for MountInfo {
fn from(statfs: statfs) -> Self {
let mut info = MountInfo {
dev_id: "".to_string(),
dev_name: unsafe {
CStr::from_ptr(&statfs.f_mntfromname[0])
.to_string_lossy()
.into_owned()
},
fs_type: unsafe {
CStr::from_ptr(&statfs.f_fstypename[0])
.to_string_lossy()
.into_owned()
},
mount_dir: unsafe {
CStr::from_ptr(&statfs.f_mntonname[0])
.to_string_lossy()
.into_owned()
},
mount_root: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
info.set_missing_fields();
info
}
}
impl FsSelector {
fn new() -> FsSelector {
FsSelector {
@ -295,239 +130,6 @@ impl Options {
}
}
impl MountInfo {
fn set_missing_fields(&mut self) {
#[cfg(unix)]
{
// We want to keep the dev_id on Windows
// but set dev_id
let path = CString::new(self.mount_dir.clone()).unwrap();
unsafe {
let mut stat = mem::zeroed();
if libc::stat(path.as_ptr(), &mut stat) == 0 {
self.dev_id = (stat.st_dev as i32).to_string();
} else {
self.dev_id = "".to_string();
}
}
}
// set MountInfo::dummy
match self.fs_type.as_ref() {
"autofs" | "proc" | "subfs"
/* for Linux 2.6/3.x */
| "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs"
/* FreeBSD, Linux 2.4 */
| "devfs"
/* for NetBSD 3.0 */
| "kernfs"
/* for Irix 6.5 */
| "ignore" => self.dummy = true,
_ => self.dummy = self.fs_type == "none"
&& self.mount_option.find(MOUNT_OPT_BIND).is_none(),
}
// set MountInfo::remote
#[cfg(windows)]
{
self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) };
}
#[cfg(unix)]
{
if self.dev_name.find(':').is_some()
|| (self.dev_name.starts_with("//") && self.fs_type == "smbfs"
|| self.fs_type == "cifs")
|| self.dev_name == "-hosts"
{
self.remote = true;
} else {
self.remote = false;
}
}
}
#[cfg(target_os = "linux")]
fn new(file_name: &str, raw: Vec<&str>) -> Option<MountInfo> {
match file_name {
// Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// "man proc" for more details
"/proc/self/mountinfo" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[9].to_string(),
fs_type: raw[8].to_string(),
mount_root: raw[3].to_string(),
mount_dir: raw[4].to_string(),
mount_option: raw[5].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
"/etc/mtab" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[0].to_string(),
fs_type: raw[2].to_string(),
mount_root: "".to_string(),
mount_dir: raw[1].to_string(),
mount_option: raw[3].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
_ => None,
}
}
#[cfg(windows)]
fn new(mut volume_name: String) -> Option<MountInfo> {
let mut dev_name_buf = [0u16; MAX_PATH];
volume_name.pop();
unsafe {
QueryDosDeviceW(
OsString::from(volume_name.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.skip(4)
.collect::<Vec<u16>>()
.as_ptr(),
dev_name_buf.as_mut_ptr(),
dev_name_buf.len() as DWORD,
)
};
volume_name.push('\\');
let dev_name = LPWSTR2String(&dev_name_buf);
let mut mount_root_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
String2LPWSTR!(volume_name),
mount_root_buf.as_mut_ptr(),
mount_root_buf.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
// TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
return None;
}
let mount_root = LPWSTR2String(&mount_root_buf);
let mut fs_type_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumeInformationW(
String2LPWSTR!(mount_root),
ptr::null_mut(),
0 as DWORD,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
fs_type_buf.as_mut_ptr(),
fs_type_buf.len() as DWORD,
)
};
let fs_type = if 0 != success {
Some(LPWSTR2String(&fs_type_buf))
} else {
None
};
let mut mn_info = MountInfo {
dev_id: volume_name,
dev_name,
fs_type: fs_type.unwrap_or_else(|| "".to_string()),
mount_root,
mount_dir: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
mn_info.set_missing_fields();
Some(mn_info)
}
}
impl FsUsage {
#[cfg(unix)]
fn new(statvfs: libc::statvfs) -> FsUsage {
{
FsUsage {
blocksize: if statvfs.f_frsize != 0 {
statvfs.f_frsize as u64
} else {
statvfs.f_bsize as u64
},
blocks: statvfs.f_blocks as u64,
bfree: statvfs.f_bfree as u64,
bavail: statvfs.f_bavail as u64,
bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0,
files: statvfs.f_files as u64,
ffree: statvfs.f_ffree as u64,
}
}
}
#[cfg(not(unix))]
fn new(path: &Path) -> FsUsage {
let mut root_path = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
//path_utf8.as_ptr(),
String2LPWSTR!(path.as_os_str()),
root_path.as_mut_ptr(),
root_path.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
crash!(
EXIT_ERR,
"GetVolumePathNamesForVolumeNameW failed: {}",
unsafe { GetLastError() }
);
}
let mut sectors_per_cluster = 0;
let mut bytes_per_sector = 0;
let mut number_of_free_clusters = 0;
let mut total_number_of_clusters = 0;
let success = unsafe {
GetDiskFreeSpaceW(
String2LPWSTR!(path.as_os_str()),
&mut sectors_per_cluster,
&mut bytes_per_sector,
&mut number_of_free_clusters,
&mut total_number_of_clusters,
)
};
if 0 == success {
// Fails in case of CD for example
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
//GetLastError()
//});
}
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
FsUsage {
// f_bsize File system block size.
blocksize: bytes_per_cluster as u64,
// f_blocks - Total number of blocks on the file system, in units of f_frsize.
// frsize = Fundamental file system block size (fragment size).
blocks: total_number_of_clusters as u64,
// Total number of free blocks.
bfree: number_of_free_clusters as u64,
// Total number of free blocks available to non-privileged processes.
bavail: 0 as u64,
bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0,
// Total number of file nodes (inodes) on the file system.
files: 0 as u64, // Not available on windows
// Total number of free file nodes (inodes).
ffree: 4096 as u64, // Meaningless on Windows
}
}
}
impl Filesystem {
// TODO: resolve uuid in `mountinfo.dev_name` if exists
fn new(mountinfo: MountInfo) -> Option<Filesystem> {
@ -548,7 +150,7 @@ impl Filesystem {
unsafe {
let path = CString::new(_stat_path).unwrap();
let mut statvfs = mem::zeroed();
if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 {
if statfs_fn(path.as_ptr(), &mut statvfs) < 0 {
None
} else {
Some(Filesystem {
@ -565,80 +167,6 @@ impl Filesystem {
}
}
/// Read file system list.
fn read_fs_list() -> Vec<MountInfo> {
#[cfg(target_os = "linux")]
{
let (file_name, fobj) = File::open(LINUX_MOUNTINFO)
.map(|f| (LINUX_MOUNTINFO, f))
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))
.expect("failed to find mount list files");
let reader = BufReader::new(fobj);
reader
.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>();
MountInfo::new(file_name, raw_data)
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
if len < 0 {
crash!(EXIT_ERR, "getmntinfo failed");
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
mounts
.iter()
.map(|m| MountInfo::from(*m))
.collect::<Vec<_>>()
}
#[cfg(windows)]
{
let mut volume_name_buf = [0u16; MAX_PATH];
// As recommended in the MS documentation, retrieve the first volume before the others
let find_handle = unsafe {
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
};
if INVALID_HANDLE_VALUE == find_handle {
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
GetLastError()
});
}
let mut mounts = Vec::<MountInfo>::new();
loop {
let volume_name = LPWSTR2String(&volume_name_buf);
if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') {
show_warning!("A bad path was skipped: {}", volume_name);
continue;
}
if let Some(m) = MountInfo::new(volume_name) {
mounts.push(m);
}
if 0 == unsafe {
FindNextVolumeW(
find_handle,
volume_name_buf.as_mut_ptr(),
volume_name_buf.len() as DWORD,
)
} {
let err = unsafe { GetLastError() };
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
}
break;
}
}
unsafe {
FindVolumeClose(find_handle);
}
mounts
}
}
fn filter_mount_list(vmi: Vec<MountInfo>, paths: &[String], opt: &Options) -> Vec<MountInfo> {
vmi.into_iter()
.filter_map(|mi| {

View file

@ -16,9 +16,7 @@ path = "src/stat.rs"
[dependencies]
clap = "2.33"
time = "0.1.40"
libc = "0.2"
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs"] }
uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "libc", "fs", "fsext"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }
[[bin]]

View file

@ -5,15 +5,16 @@
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
// spell-checker:ignore (ToDO) mtab fsext showfs otype fmtstr prec ftype blocksize nlink rdev fnodes fsid namelen blksize inodes fstype iosize statfs gnulib NBLOCKSIZE
mod fsext;
pub use crate::fsext::*;
// spell-checker:ignore (ToDO) showfs otype fmtstr prec ftype blocksize nlink rdev fnodes fsid namelen blksize inodes fstype iosize statfs gnulib NBLOCKSIZE
#[macro_use]
extern crate uucore;
use uucore::entries;
use uucore::fs::display_permissions;
use uucore::fsext::{
pretty_filetype, pretty_fstype, pretty_time, read_fs_list, statfs, BirthTime, FsMeta,
};
use uucore::libc::mode_t;
use clap::{App, Arg, ArgMatches};
use std::borrow::Cow;

View file

@ -29,6 +29,9 @@ time = { version="<= 0.1.43", optional=true }
data-encoding = { version="~2.1", optional=true } ## data-encoding: require v2.1; but v2.2.0 breaks the build for MinSRV v1.31.0
libc = { version="0.2.15, <= 0.2.85", optional=true } ## libc: initial utmp support added in v0.2.15; but v0.2.68 breaks the build for MinSRV v1.31.0
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["errhandlingapi", "fileapi", "handleapi", "winerror"] }
[target.'cfg(target_os = "redox")'.dependencies]
termion = "1.5"
@ -38,6 +41,7 @@ default = []
encoding = ["data-encoding", "thiserror"]
entries = ["libc"]
fs = ["libc"]
fsext = ["libc", "time"]
mode = ["libc"]
parse_time = []
perms = ["libc"]

View file

@ -4,6 +4,8 @@
pub mod encoding;
#[cfg(feature = "fs")]
pub mod fs;
#[cfg(feature = "fsext")]
pub mod fsext;
#[cfg(feature = "parse_time")]
pub mod parse_time;
#[cfg(feature = "zero-copy")]

View file

@ -1,6 +1,8 @@
// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
// (c) Fangxu Hu <framlog@gmail.com>
// (c) Sylvestre Ledru <sylvestre@debian.org>
//
// For the full copyright and license information, please view the LICENSE file
// that was distributed with this source code.
@ -9,19 +11,108 @@
extern crate time;
pub use crate::*; // import macros from `../../macros.rs`
#[cfg(target_os = "linux")]
static LINUX_MTAB: &str = "/etc/mtab";
const LINUX_MTAB: &str = "/etc/mtab";
#[cfg(target_os = "linux")]
static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
const LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
static MOUNT_OPT_BIND: &str = "bind";
#[cfg(windows)]
const MAX_PATH: usize = 266;
#[cfg(not(unix))]
static EXIT_ERR: i32 = 1;
#[cfg(windows)]
use std::ffi::OsString;
#[cfg(windows)]
use std::os::windows::ffi::OsStrExt;
#[cfg(windows)]
use std::os::windows::ffi::OsStringExt;
#[cfg(windows)]
use winapi::shared::minwindef::DWORD;
#[cfg(windows)]
use winapi::um::errhandlingapi::GetLastError;
#[cfg(windows)]
use winapi::um::fileapi::GetDiskFreeSpaceW;
#[cfg(windows)]
use winapi::um::fileapi::{
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetVolumeInformationW,
GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
};
#[cfg(windows)]
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
#[cfg(windows)]
use winapi::um::winbase::DRIVE_REMOTE;
#[cfg(windows)]
macro_rules! String2LPWSTR {
($str: expr) => {
OsString::from($str.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.collect::<Vec<u16>>()
.as_ptr()
};
}
#[cfg(windows)]
#[allow(non_snake_case)]
fn LPWSTR2String(buf: &[u16]) -> String {
let len = unsafe { libc::wcslen(buf.as_ptr()) };
OsString::from_wide(&buf[..len as usize])
.into_string()
.unwrap()
}
use self::time::Timespec;
use std::time::UNIX_EPOCH;
pub use uucore::libc::{
c_int, mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG,
S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR,
S_IXGRP, S_IXOTH, S_IXUSR,
#[cfg(unix)]
use libc::{
mode_t, strerror, S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFREG, S_IFSOCK,
};
use std::borrow::Cow;
use std::convert::{AsRef, From};
#[cfg(unix)]
use std::ffi::CString;
#[cfg(unix)]
use std::io::Error as IOError;
#[cfg(unix)]
use std::mem;
use std::path::Path;
use std::time::UNIX_EPOCH;
#[cfg(any(
target_os = "linux",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
pub use libc::statfs as Sstatfs;
#[cfg(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "bitrig",
target_os = "dragonfly"
))]
pub use libc::statvfs as Sstatfs;
#[cfg(any(
target_os = "linux",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
pub use libc::statfs as statfs_fn;
#[cfg(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "bitrig",
target_os = "dragonfly"
))]
pub use libc::statvfs as statfs_fn;
pub trait BirthTime {
fn pretty_birth(&self) -> String;
@ -47,78 +138,389 @@ impl BirthTime for Metadata {
}
}
pub fn pretty_time(sec: i64, nsec: i64) -> String {
// sec == seconds since UNIX_EPOCH
// nsec == nanoseconds since (UNIX_EPOCH + sec)
let tm = time::at(Timespec::new(sec, nsec as i32));
let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap();
if res.ends_with(" -0000") {
res.replace(" -0000", " +0000")
} else {
res
}
#[derive(Debug, Clone)]
pub struct MountInfo {
// it stores `volume_name` in windows platform and `dev_id` in unix platform
pub dev_id: String,
pub dev_name: String,
pub fs_type: String,
pub mount_dir: String,
pub mount_option: String, // we only care "bind" option
pub mount_root: String,
pub remote: bool,
pub dummy: bool,
}
pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str {
match mode & S_IFMT {
S_IFREG => {
if size != 0 {
"regular file"
} else {
"regular empty file"
impl MountInfo {
fn set_missing_fields(&mut self) {
#[cfg(unix)]
{
// We want to keep the dev_id on Windows
// but set dev_id
let path = CString::new(self.mount_dir.clone()).unwrap();
unsafe {
let mut stat = mem::zeroed();
if libc::stat(path.as_ptr(), &mut stat) == 0 {
self.dev_id = (stat.st_dev as i32).to_string();
} else {
self.dev_id = "".to_string();
}
}
}
S_IFDIR => "directory",
S_IFLNK => "symbolic link",
S_IFCHR => "character special file",
S_IFBLK => "block special file",
S_IFIFO => "fifo",
S_IFSOCK => "socket",
// TODO: Other file types
// See coreutils/gnulib/lib/file-type.c
_ => "weird file",
// set MountInfo::dummy
match self.fs_type.as_ref() {
"autofs" | "proc" | "subfs"
/* for Linux 2.6/3.x */
| "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs"
/* FreeBSD, Linux 2.4 */
| "devfs"
/* for NetBSD 3.0 */
| "kernfs"
/* for Irix 6.5 */
| "ignore" => self.dummy = true,
_ => self.dummy = self.fs_type == "none"
&& self.mount_option.find(MOUNT_OPT_BIND).is_none(),
}
// set MountInfo::remote
#[cfg(windows)]
{
self.remote = DRIVE_REMOTE == unsafe { GetDriveTypeW(String2LPWSTR!(self.mount_root)) };
}
#[cfg(unix)]
{
if self.dev_name.find(':').is_some()
|| (self.dev_name.starts_with("//") && self.fs_type == "smbfs"
|| self.fs_type == "cifs")
|| self.dev_name == "-hosts"
{
self.remote = true;
} else {
self.remote = false;
}
}
}
#[cfg(target_os = "linux")]
fn new(file_name: &str, raw: Vec<&str>) -> Option<MountInfo> {
match file_name {
// Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// "man proc" for more details
LINUX_MOUNTINFO => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[9].to_string(),
fs_type: raw[8].to_string(),
mount_root: raw[3].to_string(),
mount_dir: raw[4].to_string(),
mount_option: raw[5].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
LINUX_MTAB => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[0].to_string(),
fs_type: raw[2].to_string(),
mount_root: "".to_string(),
mount_dir: raw[1].to_string(),
mount_option: raw[3].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
_ => None,
}
}
#[cfg(windows)]
fn new(mut volume_name: String) -> Option<MountInfo> {
let mut dev_name_buf = [0u16; MAX_PATH];
volume_name.pop();
unsafe {
QueryDosDeviceW(
OsString::from(volume_name.clone())
.as_os_str()
.encode_wide()
.chain(Some(0))
.skip(4)
.collect::<Vec<u16>>()
.as_ptr(),
dev_name_buf.as_mut_ptr(),
dev_name_buf.len() as DWORD,
)
};
volume_name.push('\\');
let dev_name = LPWSTR2String(&dev_name_buf);
let mut mount_root_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
String2LPWSTR!(volume_name),
mount_root_buf.as_mut_ptr(),
mount_root_buf.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
// TODO: support the case when `GetLastError()` returns `ERROR_MORE_DATA`
return None;
}
let mount_root = LPWSTR2String(&mount_root_buf);
let mut fs_type_buf = [0u16; MAX_PATH];
let success = unsafe {
GetVolumeInformationW(
String2LPWSTR!(mount_root),
ptr::null_mut(),
0 as DWORD,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
fs_type_buf.as_mut_ptr(),
fs_type_buf.len() as DWORD,
)
};
let fs_type = if 0 != success {
Some(LPWSTR2String(&fs_type_buf))
} else {
None
};
let mut mn_info = MountInfo {
dev_id: volume_name,
dev_name,
fs_type: fs_type.unwrap_or_else(|| "".to_string()),
mount_root,
mount_dir: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
mn_info.set_missing_fields();
Some(mn_info)
}
}
use std::borrow::Cow;
use std::convert::{AsRef, From};
use std::ffi::CString;
use std::io::Error as IOError;
use std::mem;
use std::path::Path;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<Sstatfs> for MountInfo {
fn from(statfs: Sstatfs) -> Self {
let mut info = MountInfo {
dev_id: "".to_string(),
dev_name: unsafe {
CStr::from_ptr(&statfs.f_mntfromname[0])
.to_string_lossy()
.into_owned()
},
fs_type: unsafe {
CStr::from_ptr(&statfs.f_fstypename[0])
.to_string_lossy()
.into_owned()
},
mount_dir: unsafe {
CStr::from_ptr(&statfs.f_mntonname[0])
.to_string_lossy()
.into_owned()
},
mount_root: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
info.set_missing_fields();
info
}
}
#[cfg(any(
target_os = "linux",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
use uucore::libc::statfs as Sstatfs;
#[cfg(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "bitrig",
target_os = "dragonfly"
))]
use uucore::libc::statvfs as Sstatfs;
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
use libc::c_int;
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int;
#[cfg(any(
target_os = "linux",
target_vendor = "apple",
target_os = "android",
target_os = "freebsd"
))]
use uucore::libc::statfs as statfs_fn;
#[cfg(any(
target_os = "openbsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "bitrig",
target_os = "dragonfly"
))]
use uucore::libc::statvfs as statfs_fn;
#[cfg(any(
all(target_os = "freebsd"),
all(target_vendor = "apple", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int;
}
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "windows"))]
use std::ptr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
/// Read file system list.
pub fn read_fs_list() -> Vec<MountInfo> {
#[cfg(target_os = "linux")]
{
let (file_name, fobj) = File::open(LINUX_MOUNTINFO)
.map(|f| (LINUX_MOUNTINFO, f))
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))
.expect("failed to find mount list files");
let reader = BufReader::new(fobj);
reader
.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>();
MountInfo::new(file_name, raw_data)
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut Sstatfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1_i32) };
if len < 0 {
crash!(1, "getmntinfo failed");
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
mounts
.iter()
.map(|m| MountInfo::from(*m))
.collect::<Vec<_>>()
}
#[cfg(windows)]
{
let mut volume_name_buf = [0u16; MAX_PATH];
// As recommended in the MS documentation, retrieve the first volume before the others
let find_handle = unsafe {
FindFirstVolumeW(volume_name_buf.as_mut_ptr(), volume_name_buf.len() as DWORD)
};
if INVALID_HANDLE_VALUE == find_handle {
crash!(EXIT_ERR, "FindFirstVolumeW failed: {}", unsafe {
GetLastError()
});
}
let mut mounts = Vec::<MountInfo>::new();
loop {
let volume_name = LPWSTR2String(&volume_name_buf);
if !volume_name.starts_with("\\\\?\\") || !volume_name.ends_with('\\') {
show_warning!("A bad path was skipped: {}", volume_name);
continue;
}
if let Some(m) = MountInfo::new(volume_name) {
mounts.push(m);
}
if 0 == unsafe {
FindNextVolumeW(
find_handle,
volume_name_buf.as_mut_ptr(),
volume_name_buf.len() as DWORD,
)
} {
let err = unsafe { GetLastError() };
if err != winapi::shared::winerror::ERROR_NO_MORE_FILES {
crash!(EXIT_ERR, "FindNextVolumeW failed: {}", err);
}
break;
}
}
unsafe {
FindVolumeClose(find_handle);
}
mounts
}
}
#[derive(Debug, Clone)]
pub struct FsUsage {
pub blocksize: u64,
pub blocks: u64,
pub bfree: u64,
pub bavail: u64,
pub bavail_top_bit_set: bool,
pub files: u64,
pub ffree: u64,
}
impl FsUsage {
#[cfg(unix)]
pub fn new(statvfs: Sstatfs) -> FsUsage {
{
FsUsage {
blocksize: statvfs.f_bsize as u64, // or `statvfs.f_frsize` ?
blocks: statvfs.f_blocks as u64,
bfree: statvfs.f_bfree as u64,
bavail: statvfs.f_bavail as u64,
bavail_top_bit_set: ((statvfs.f_bavail as u64) & (1u64.rotate_right(1))) != 0,
files: statvfs.f_files as u64,
ffree: statvfs.f_ffree as u64,
}
}
}
#[cfg(not(unix))]
pub fn new(path: &Path) -> FsUsage {
let mut root_path = [0u16; MAX_PATH];
let success = unsafe {
GetVolumePathNamesForVolumeNameW(
//path_utf8.as_ptr(),
String2LPWSTR!(path.as_os_str()),
root_path.as_mut_ptr(),
root_path.len() as DWORD,
ptr::null_mut(),
)
};
if 0 == success {
crash!(
EXIT_ERR,
"GetVolumePathNamesForVolumeNameW failed: {}",
unsafe { GetLastError() }
);
}
let mut sectors_per_cluster = 0;
let mut bytes_per_sector = 0;
let mut number_of_free_clusters = 0;
let mut total_number_of_clusters = 0;
let success = unsafe {
GetDiskFreeSpaceW(
String2LPWSTR!(path.as_os_str()),
&mut sectors_per_cluster,
&mut bytes_per_sector,
&mut number_of_free_clusters,
&mut total_number_of_clusters,
)
};
if 0 == success {
// Fails in case of CD for example
//crash!(EXIT_ERR, "GetDiskFreeSpaceW failed: {}", unsafe {
//GetLastError()
//});
}
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
FsUsage {
// f_bsize File system block size.
blocksize: bytes_per_cluster as u64,
// f_blocks - Total number of blocks on the file system, in units of f_frsize.
// frsize = Fundamental file system block size (fragment size).
blocks: total_number_of_clusters as u64,
// Total number of free blocks.
bfree: number_of_free_clusters as u64,
// Total number of free blocks available to non-privileged processes.
bavail: 0 as u64,
bavail_top_bit_set: ((bytes_per_sector as u64) & (1u64.rotate_right(1))) != 0,
// Total number of file nodes (inodes) on the file system.
files: 0 as u64, // Not available on windows
// Total number of free file nodes (inodes).
ffree: 4096 as u64, // Meaningless on Windows
}
}
}
#[cfg(unix)]
pub trait FsMeta {
fn fs_type(&self) -> i64;
fn iosize(&self) -> u64;
@ -132,6 +534,7 @@ pub trait FsMeta {
fn namelen(&self) -> u64;
}
#[cfg(unix)]
impl FsMeta for Sstatfs {
fn blksize(&self) -> i64 {
self.f_bsize as i64
@ -184,7 +587,7 @@ impl FsMeta for Sstatfs {
#[cfg(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux"))]
fn fsid(&self) -> u64 {
let f_fsid: &[u32; 2] =
unsafe { &*(&self.f_fsid as *const uucore::libc::fsid_t as *const [u32; 2]) };
unsafe { &*(&self.f_fsid as *const libc::fsid_t as *const [u32; 2]) };
(u64::from(f_fsid[0])) << 32 | u64::from(f_fsid[1])
}
#[cfg(not(any(target_vendor = "apple", target_os = "freebsd", target_os = "linux")))]
@ -211,6 +614,7 @@ impl FsMeta for Sstatfs {
}
}
#[cfg(unix)]
pub fn statfs<P: AsRef<Path>>(path: P) -> Result<Sstatfs, String>
where
Vec<u8>: From<P>,
@ -234,6 +638,40 @@ where
}
}
pub fn pretty_time(sec: i64, nsec: i64) -> String {
// sec == seconds since UNIX_EPOCH
// nsec == nanoseconds since (UNIX_EPOCH + sec)
let tm = time::at(Timespec::new(sec, nsec as i32));
let res = time::strftime("%Y-%m-%d %H:%M:%S.%f %z", &tm).unwrap();
if res.ends_with(" -0000") {
res.replace(" -0000", " +0000")
} else {
res
}
}
#[cfg(unix)]
pub fn pretty_filetype<'a>(mode: mode_t, size: u64) -> &'a str {
match mode & S_IFMT {
S_IFREG => {
if size != 0 {
"regular file"
} else {
"regular empty file"
}
}
S_IFDIR => "directory",
S_IFLNK => "symbolic link",
S_IFCHR => "character special file",
S_IFBLK => "block special file",
S_IFIFO => "fifo",
S_IFSOCK => "socket",
// TODO: Other file types
// See coreutils/gnulib/lib/file-type.c
_ => "weird file",
}
}
pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> {
match fstype {
0x6163_6673 => "acfs".into(),
@ -354,183 +792,30 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> {
}
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
extern "C" {
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
#[link_name = "getmntinfo$INODE64"]
fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(any(
all(target_os = "freebsd"),
all(target_vendor = "apple", target_arch = "aarch64")
))]
fn getmntinfo(mntbufp: *mut *mut Sstatfs, flags: c_int) -> c_int;
}
#[derive(Debug, Clone)]
pub struct MountInfo {
// it stores `volume_name` in windows platform and `dev_id` in unix platform
dev_id: String,
dev_name: String,
fs_type: String,
pub mount_dir: String,
mount_option: String, // we only care "bind" option
mount_root: String,
remote: bool,
dummy: bool,
}
impl MountInfo {
fn set_missing_fields(&mut self) {
#[cfg(unix)]
{
// We want to keep the dev_id on Windows
// but set dev_id
let path = CString::new(self.mount_dir.clone()).unwrap();
unsafe {
let mut stat = mem::zeroed();
if libc::stat(path.as_ptr(), &mut stat) == 0 {
self.dev_id = (stat.st_dev as i32).to_string();
} else {
self.dev_id = "".to_string();
}
}
}
// set MountInfo::dummy
match self.fs_type.as_ref() {
"autofs" | "proc" | "subfs"
/* for Linux 2.6/3.x */
| "debugfs" | "devpts" | "fusectl" | "mqueue" | "rpc_pipefs" | "sysfs"
/* FreeBSD, Linux 2.4 */
| "devfs"
/* for NetBSD 3.0 */
| "kernfs"
/* for Irix 6.5 */
| "ignore" => self.dummy = true,
_ => self.dummy = self.fs_type == "none"
&& self.mount_option.find(MOUNT_OPT_BIND).is_none(),
}
// set MountInfo::remote
#[cfg(unix)]
{
if self.dev_name.find(':').is_some()
|| (self.dev_name.starts_with("//") && self.fs_type == "smbfs"
|| self.fs_type == "cifs")
|| self.dev_name == "-hosts"
{
self.remote = true;
} else {
self.remote = false;
}
}
#[test]
#[cfg(unix)]
fn test_file_type() {
assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
assert_eq!("weird file", pretty_filetype(0, 0));
}
#[cfg(target_os = "linux")]
fn new(file_name: &str, raw: Vec<&str>) -> Option<MountInfo> {
match file_name {
// Format: 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
// "man proc" for more details
"/proc/self/mountinfo" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[9].to_string(),
fs_type: raw[8].to_string(),
mount_root: raw[3].to_string(),
mount_dir: raw[4].to_string(),
mount_option: raw[5].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
"/etc/mtab" => {
let mut m = MountInfo {
dev_id: "".to_string(),
dev_name: raw[0].to_string(),
fs_type: raw[2].to_string(),
mount_root: "".to_string(),
mount_dir: raw[1].to_string(),
mount_option: raw[3].to_string(),
remote: false,
dummy: false,
};
m.set_missing_fields();
Some(m)
}
_ => None,
}
}
}
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ffi::CStr;
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
impl From<Sstatfs> for MountInfo {
fn from(statfs: Sstatfs) -> Self {
let mut info = MountInfo {
dev_id: "".to_string(),
dev_name: unsafe {
CStr::from_ptr(&statfs.f_mntfromname[0])
.to_string_lossy()
.into_owned()
},
fs_type: unsafe {
CStr::from_ptr(&statfs.f_fstypename[0])
.to_string_lossy()
.into_owned()
},
mount_dir: unsafe {
CStr::from_ptr(&statfs.f_mntonname[0])
.to_string_lossy()
.into_owned()
},
mount_root: "".to_string(),
mount_option: "".to_string(),
remote: false,
dummy: false,
};
info.set_missing_fields();
info
}
}
#[cfg(target_os = "linux")]
use std::fs::File;
#[cfg(target_os = "linux")]
use std::io::{BufRead, BufReader};
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::ptr;
#[cfg(any(target_vendor = "apple", target_os = "freebsd"))]
use std::slice;
pub fn read_fs_list() -> Vec<MountInfo> {
#[cfg(target_os = "linux")]
{
let (file_name, fobj) = File::open(LINUX_MOUNTINFO)
.map(|f| (LINUX_MOUNTINFO, f))
.or_else(|_| File::open(LINUX_MTAB).map(|f| (LINUX_MTAB, f)))
.expect("failed to find mount list files");
let reader = BufReader::new(fobj);
reader
.lines()
.filter_map(|line| line.ok())
.filter_map(|line| {
let raw_data = line.split_whitespace().collect::<Vec<&str>>();
MountInfo::new(file_name, raw_data)
})
.collect::<Vec<_>>()
}
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
{
let mut mptr: *mut Sstatfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as c_int) };
if len < 0 {
crash!(1, "getmntinfo failed");
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
mounts
.iter()
.map(|m| MountInfo::from(*m))
.collect::<Vec<_>>()
#[test]
fn test_fs_type() {
assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
assert_eq!("tmpfs", pretty_fstype(0x01021994));
assert_eq!("nfs", pretty_fstype(0x6969));
assert_eq!("btrfs", pretty_fstype(0x9123683e));
assert_eq!("xfs", pretty_fstype(0x58465342));
assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
assert_eq!("ntfs", pretty_fstype(0x5346544e));
assert_eq!("fat", pretty_fstype(0x4006));
assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
}
}

View file

@ -35,6 +35,8 @@ pub use crate::mods::ranges;
pub use crate::features::encoding;
#[cfg(feature = "fs")]
pub use crate::features::fs;
#[cfg(feature = "fsext")]
pub use crate::features::fsext;
#[cfg(feature = "parse_time")]
pub use crate::features::parse_time;
#[cfg(feature = "zero-copy")]
@ -187,6 +189,7 @@ mod tests {
vec.into_iter().collect_str(handling)
}
#[cfg(any(unix, target_os = "redox"))]
fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
//assert our string is invalid utf8
assert!(os_str.to_os_string().into_string().is_err());
@ -210,6 +213,7 @@ mod tests {
);
}
#[cfg(any(unix, target_os = "redox"))]
fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
//assert our string is invalid utf8
assert!(os_str.to_os_string().into_string().is_err());
@ -234,7 +238,7 @@ mod tests {
//create a vector containing only correct encoding
let test_vec = make_os_vec(&OsString::from("test2"));
//expect complete conversion without losses, even when lossy conversion is accepted
let _ = collect_os_str(test_vec.clone(), InvalidEncodingHandling::ConvertLossy)
let _ = collect_os_str(test_vec, InvalidEncodingHandling::ConvertLossy)
.expect_complete("Lossy conversion not expected in this test");
}

View file

@ -5,33 +5,6 @@ use crate::common::util::*;
extern crate stat;
pub use self::stat::*;
#[cfg(test)]
mod test_fsext {
use super::*;
#[test]
fn test_file_type() {
assert_eq!("block special file", pretty_filetype(S_IFBLK, 0));
assert_eq!("character special file", pretty_filetype(S_IFCHR, 0));
assert_eq!("regular file", pretty_filetype(S_IFREG, 1));
assert_eq!("regular empty file", pretty_filetype(S_IFREG, 0));
assert_eq!("weird file", pretty_filetype(0, 0));
}
#[test]
fn test_fs_type() {
assert_eq!("ext2/ext3", pretty_fstype(0xEF53));
assert_eq!("tmpfs", pretty_fstype(0x01021994));
assert_eq!("nfs", pretty_fstype(0x6969));
assert_eq!("btrfs", pretty_fstype(0x9123683e));
assert_eq!("xfs", pretty_fstype(0x58465342));
assert_eq!("zfs", pretty_fstype(0x2FC12FC1));
assert_eq!("ntfs", pretty_fstype(0x5346544e));
assert_eq!("fat", pretty_fstype(0x4006));
assert_eq!("UNKNOWN (0x1234)", pretty_fstype(0x1234));
}
}
#[test]
fn test_scanutil() {
assert_eq!(Some((-5, 2)), "-5zxc".scan_num::<i32>());