mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +00:00
commit
fbe7506fb3
9 changed files with 1053 additions and 6 deletions
23
Cargo.lock
generated
23
Cargo.lock
generated
|
@ -408,6 +408,17 @@ dependencies = [
|
|||
"uucore 0.0.2 (git+https://github.com/uutils/uucore/?tag=0.0.2)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "df"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uucore 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.6.2"
|
||||
|
@ -1912,6 +1923,16 @@ name = "utf8-ranges"
|
|||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"wild 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.0.2"
|
||||
|
@ -1951,6 +1972,7 @@ dependencies = [
|
|||
"cp 0.0.1",
|
||||
"cut 0.0.1",
|
||||
"date 0.0.1",
|
||||
"df 0.0.1",
|
||||
"dircolors 0.0.1",
|
||||
"dirname 0.0.1",
|
||||
"du 0.0.1",
|
||||
|
@ -2295,6 +2317,7 @@ dependencies = [
|
|||
"checksum unix_socket 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564"
|
||||
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
|
||||
"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
|
||||
"checksum uucore 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d9991e24be65c5df85c9f16445554785a68e5ae7ec7feb230424ba35c24ebad7"
|
||||
"checksum uucore 0.0.2 (git+https://github.com/uutils/uucore/?tag=0.0.2)" = "<none>"
|
||||
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
|
|
@ -33,6 +33,7 @@ feat_common_core = [
|
|||
"cp",
|
||||
"cut",
|
||||
"date",
|
||||
"df",
|
||||
"dircolors",
|
||||
"dirname",
|
||||
"echo",
|
||||
|
@ -228,6 +229,7 @@ comm = { optional=true, path="src/uu/comm" }
|
|||
cp = { optional=true, path="src/uu/cp" }
|
||||
cut = { optional=true, path="src/uu/cut" }
|
||||
date = { optional=true, path="src/uu/date" }
|
||||
df = { optional=true, path="src/uu/df" }
|
||||
dircolors= { optional=true, path="src/uu/dircolors" }
|
||||
dirname = { optional=true, path="src/uu/dirname" }
|
||||
du = { optional=true, path="src/uu/du" }
|
||||
|
|
|
@ -54,6 +54,7 @@ PROGS := \
|
|||
comm \
|
||||
cp \
|
||||
cut \
|
||||
df \
|
||||
dircolors \
|
||||
dirname \
|
||||
echo \
|
||||
|
|
12
README.md
12
README.md
|
@ -276,17 +276,17 @@ Utilities
|
|||
| arch | cp | chcon |
|
||||
| base32 | expr | csplit |
|
||||
| base64 | install | dd |
|
||||
| basename | ls | df |
|
||||
| cat | more | numfmt |
|
||||
| chgrp | od (`--strings` and 128-bit data types missing) | pr |
|
||||
| chmod | printf | runcon |
|
||||
| chown | sort | stty |
|
||||
| basename | ls | numfmt |
|
||||
| cat | more | pr |
|
||||
| chgrp | od (`--strings` and 128-bit data types missing) | runcon |
|
||||
| chmod | printf | stty |
|
||||
| chown | sort | |
|
||||
| chroot | split | |
|
||||
| cksum | tail | |
|
||||
| comm | test | |
|
||||
| cut | date | |
|
||||
| dircolors | join | |
|
||||
| dirname | | |
|
||||
| dirname | df | |
|
||||
| du | | |
|
||||
| echo | | |
|
||||
| env | | |
|
||||
|
|
21
src/uu/df/Cargo.toml
Normal file
21
src/uu/df/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "df"
|
||||
version = "0.0.1"
|
||||
authors = []
|
||||
|
||||
[lib]
|
||||
name = "uu_df"
|
||||
path = "df.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.32.0"
|
||||
libc = "0.2.42"
|
||||
uucore = "0.0.1"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
kernel32-sys = "0.2.2"
|
||||
winapi = { version = "0.3", features = ["handleapi"] }
|
||||
|
||||
[[bin]]
|
||||
name = "df"
|
||||
path = "main.rs"
|
985
src/uu/df/df.rs
Normal file
985
src/uu/df/df.rs
Normal file
|
@ -0,0 +1,985 @@
|
|||
#![crate_name = "uu_df"]
|
||||
|
||||
/*
|
||||
* This file is part of the uutils coreutils package.
|
||||
*
|
||||
* (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.
|
||||
*/
|
||||
|
||||
extern crate clap;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
#[cfg(windows)]
|
||||
extern crate kernel32;
|
||||
#[cfg(windows)]
|
||||
extern crate winapi;
|
||||
|
||||
use clap::{App, Arg};
|
||||
#[cfg(windows)]
|
||||
use kernel32::{
|
||||
FindFirstVolumeW, FindNextVolumeW, FindVolumeClose, GetDriveTypeW, GetLastError,
|
||||
GetVolumeInformationW, GetVolumePathNamesForVolumeNameW, QueryDosDeviceW,
|
||||
};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::ffi::CString;
|
||||
#[cfg(unix)]
|
||||
use std::mem;
|
||||
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
use libc::c_int;
|
||||
#[cfg(target_os = "macos")]
|
||||
use libc::statfs;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
use std::ffi::CStr;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "windows"))]
|
||||
use std::ptr;
|
||||
#[cfg(any(target_os = "macos", target_os = "freebsd"))]
|
||||
use std::slice;
|
||||
|
||||
#[cfg(target_os = "freebsd")]
|
||||
use 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\
|
||||
or all file systems by default.";
|
||||
|
||||
static EXIT_OK: i32 = 0;
|
||||
#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "windows"))]
|
||||
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";
|
||||
static OPT_TOTAL: &str = "total";
|
||||
static OPT_HUMAN_READABLE: &str = "human-readable";
|
||||
static OPT_HUMAN_READABLE_2: &str = "human-readable-2";
|
||||
static OPT_INODES: &str = "inodes";
|
||||
static OPT_KILO: &str = "kilo";
|
||||
static OPT_LOCAL: &str = "local";
|
||||
static OPT_NO_SYNC: &str = "no-sync";
|
||||
static OPT_OUTPUT: &str = "output";
|
||||
static OPT_PORTABILITY: &str = "portability";
|
||||
static OPT_SYNC: &str = "sync";
|
||||
static OPT_TYPE: &str = "type";
|
||||
static OPT_PRINT_TYPE: &str = "print-type";
|
||||
static OPT_EXCLUDE_TYPE: &str = "exclude-type";
|
||||
static OPT_VERSION: &str = "version";
|
||||
|
||||
static MOUNT_OPT_BIND: &str = "bind";
|
||||
|
||||
/// Store names of file systems as a selector.
|
||||
/// Note: `exclude` takes priority over `include`.
|
||||
struct FsSelector {
|
||||
include: HashSet<String>,
|
||||
exclude: HashSet<String>,
|
||||
}
|
||||
|
||||
struct Options {
|
||||
show_local_fs: bool,
|
||||
show_all_fs: bool,
|
||||
show_listed_fs: bool,
|
||||
show_fs_type: bool,
|
||||
show_inode_instead: bool,
|
||||
print_grand_total: bool,
|
||||
// block_size: usize,
|
||||
human_readable_base: i64,
|
||||
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_os = "macos", 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_os = "macos"))]
|
||||
extern "C" {
|
||||
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
|
||||
#[link_name = "getmntinfo$INODE64"]
|
||||
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
|
||||
|
||||
#[cfg(all(
|
||||
target_os = "freebsd",
|
||||
not(all(target_os = "macos", target_arch = "x86_64"))
|
||||
))]
|
||||
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "freebsd", target_os = "macos"))]
|
||||
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 {
|
||||
include: HashSet::new(),
|
||||
exclude: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn include(&mut self, fs_type: String) {
|
||||
self.include.insert(fs_type);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn exclude(&mut self, fs_type: String) {
|
||||
self.exclude.insert(fs_type);
|
||||
}
|
||||
|
||||
fn should_select(&self, fs_type: &str) -> bool {
|
||||
if self.exclude.contains(fs_type) {
|
||||
return false;
|
||||
}
|
||||
self.include.is_empty() || self.include.contains(fs_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn new() -> Options {
|
||||
Options {
|
||||
show_local_fs: false,
|
||||
show_all_fs: false,
|
||||
show_listed_fs: false,
|
||||
show_fs_type: false,
|
||||
show_inode_instead: false,
|
||||
print_grand_total: false,
|
||||
// block_size: match env::var("BLOCKSIZE") {
|
||||
// Ok(size) => size.parse().unwrap(),
|
||||
// Err(_) => 512,
|
||||
// },
|
||||
human_readable_base: -1,
|
||||
fs_selector: FsSelector::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
"/proc/self/mountinfo" => {
|
||||
let mut m = MountInfo {
|
||||
dev_id: "".to_string(),
|
||||
dev_name: raw[8].to_string(),
|
||||
fs_type: raw[7].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> {
|
||||
let _stat_path = if !mountinfo.mount_dir.is_empty() {
|
||||
mountinfo.mount_dir.clone()
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
mountinfo.dev_name.clone()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// On windows, we expect the volume id
|
||||
mountinfo.dev_id.clone()
|
||||
}
|
||||
};
|
||||
#[cfg(unix)]
|
||||
unsafe {
|
||||
let path = CString::new(_stat_path).unwrap();
|
||||
let mut statvfs = mem::zeroed();
|
||||
if libc::statvfs(path.as_ptr(), &mut statvfs) < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(Filesystem {
|
||||
mountinfo,
|
||||
usage: FsUsage::new(statvfs),
|
||||
})
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
Some(Filesystem {
|
||||
mountinfo,
|
||||
usage: FsUsage::new(Path::new(&_stat_path)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_os = "macos"))]
|
||||
{
|
||||
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>, opt: &Options) -> Vec<MountInfo> {
|
||||
vmi.into_iter()
|
||||
.filter_map(|mi| {
|
||||
if (mi.remote && opt.show_local_fs)
|
||||
|| (mi.dummy && !opt.show_all_fs && !opt.show_listed_fs)
|
||||
|| !opt.fs_selector.should_select(&mi.fs_type)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some((mi.dev_id.clone(), mi))
|
||||
}
|
||||
})
|
||||
.fold(
|
||||
HashMap::<String, Cell<MountInfo>>::new(),
|
||||
|mut acc, (id, mi)| {
|
||||
#[allow(clippy::map_entry)]
|
||||
{
|
||||
if acc.contains_key(&id) {
|
||||
let seen = acc.get(&id).unwrap().replace(mi.clone());
|
||||
let target_nearer_root = seen.mount_dir.len() > mi.mount_dir.len();
|
||||
// With bind mounts, prefer items nearer the root of the source
|
||||
let source_below_root = !seen.mount_root.is_empty()
|
||||
&& !mi.mount_root.is_empty()
|
||||
&& seen.mount_root.len() < mi.mount_root.len();
|
||||
// let "real" devices with '/' in the name win.
|
||||
if (!mi.dev_name.starts_with('/') || seen.dev_name.starts_with('/'))
|
||||
// let points towards the root of the device win.
|
||||
&& (!target_nearer_root || source_below_root)
|
||||
// let an entry overmounted on a new device win...
|
||||
&& (seen.dev_name == mi.dev_name
|
||||
/* ... but only when matching an existing mnt point,
|
||||
to avoid problematic replacement when given
|
||||
inaccurate mount lists, seen with some chroot
|
||||
environments for example. */
|
||||
|| seen.mount_dir != mi.mount_dir)
|
||||
{
|
||||
acc.get(&id).unwrap().replace(seen);
|
||||
}
|
||||
} else {
|
||||
acc.insert(id, Cell::new(mi));
|
||||
}
|
||||
acc
|
||||
}
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.map(|ent| ent.1.into_inner())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Convert `value` to a human readable string based on `base`.
|
||||
/// e.g. It returns 1G when value is 1 * 1024 * 1024 * 1024 and base is 1024.
|
||||
/// Note: It returns `value` if `base` isn't positive.
|
||||
fn human_readable(value: u64, base: i64) -> String {
|
||||
#![allow(non_snake_case)]
|
||||
if base <= 0 {
|
||||
return value.to_string();
|
||||
}
|
||||
let KB: u64 = base as u64;
|
||||
let MB: u64 = KB * base as u64;
|
||||
let GB: u64 = MB * base as u64;
|
||||
let TB: u64 = GB * base as u64;
|
||||
if value >= TB {
|
||||
format!("{:.1}T", (value as f64) / (TB as f64))
|
||||
} else if value >= GB {
|
||||
format!("{:.1}G", (value as f64) / (GB as f64))
|
||||
} else if value >= MB {
|
||||
format!("{:.1}M", (value as f64) / (MB as f64))
|
||||
} else if value >= KB {
|
||||
format!("{:.1}K", (value as f64) / (KB as f64))
|
||||
} else {
|
||||
format!("{}B", value)
|
||||
}
|
||||
}
|
||||
|
||||
fn use_size(free_size: u64, total_size: u64) -> String {
|
||||
if total_size == 0 {
|
||||
return String::from("-");
|
||||
}
|
||||
return format!(
|
||||
"{:.0}%",
|
||||
100f64 - 100f64 * (free_size as f64 / total_size as f64)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> i32 {
|
||||
let usage = get_usage();
|
||||
let matches = App::new(executable!())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.usage(&usage[..])
|
||||
.arg(
|
||||
Arg::with_name(OPT_ALL)
|
||||
.short("a")
|
||||
.long("all")
|
||||
.help("include dummy file systems"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_BLOCKSIZE)
|
||||
.short("B")
|
||||
.long("block-size")
|
||||
.takes_value(true)
|
||||
.help(
|
||||
"scale sizes by SIZE before printing them; e.g.\
|
||||
'-BM' prints sizes in units of 1,048,576 bytes",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_DIRECT)
|
||||
.long("direct")
|
||||
.help("show statistics for a file instead of mount point"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_TOTAL)
|
||||
.long("total")
|
||||
.help("produce a grand total"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_HUMAN_READABLE)
|
||||
.short("h")
|
||||
.long("human-readable")
|
||||
.conflicts_with(OPT_HUMAN_READABLE_2)
|
||||
.help("print sizes in human readable format (e.g., 1K 234M 2G)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_HUMAN_READABLE_2)
|
||||
.short("H")
|
||||
.long("si")
|
||||
.conflicts_with(OPT_HUMAN_READABLE)
|
||||
.help("likewise, but use powers of 1000 not 1024"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_INODES)
|
||||
.short("i")
|
||||
.long("inodes")
|
||||
.help("list inode information instead of block usage"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_KILO)
|
||||
.short("k")
|
||||
.help("like --block-size=1K"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_LOCAL)
|
||||
.short("l")
|
||||
.long("local")
|
||||
.help("limit listing to local file systems"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_NO_SYNC)
|
||||
.long("no-sync")
|
||||
.conflicts_with(OPT_SYNC)
|
||||
.help("do not invoke sync before getting usage info (default)"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_OUTPUT)
|
||||
.long("output")
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.help(
|
||||
"use the output format defined by FIELD_LIST,\
|
||||
or print all fields if FIELD_LIST is omitted.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PORTABILITY)
|
||||
.short("P")
|
||||
.long("portability")
|
||||
.help("use the POSIX output format"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_SYNC)
|
||||
.long("sync")
|
||||
.conflicts_with(OPT_NO_SYNC)
|
||||
.help("invoke sync before getting usage info"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_TYPE)
|
||||
.short("t")
|
||||
.long("type")
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.help("limit listing to file systems of type TYPE"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_PRINT_TYPE)
|
||||
.short("T")
|
||||
.long("print-type")
|
||||
.help("print file system type"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_EXCLUDE_TYPE)
|
||||
.short("x")
|
||||
.long("exclude-type")
|
||||
.takes_value(true)
|
||||
.use_delimiter(true)
|
||||
.help("limit listing to file systems not of type TYPE"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(OPT_VERSION)
|
||||
.short("v")
|
||||
.long("version")
|
||||
.help("output version information and exit"),
|
||||
)
|
||||
.get_matches_from(&args);
|
||||
|
||||
if matches.is_present(OPT_VERSION) {
|
||||
println!("{} {}", executable!(), VERSION);
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if matches.is_present(OPT_INODES) {
|
||||
println!("{}: doesn't support -i option", executable!());
|
||||
return EXIT_OK;
|
||||
}
|
||||
}
|
||||
|
||||
let mut opt = Options::new();
|
||||
if matches.is_present(OPT_LOCAL) {
|
||||
opt.show_local_fs = true;
|
||||
}
|
||||
if matches.is_present(OPT_ALL) {
|
||||
opt.show_all_fs = true;
|
||||
}
|
||||
if matches.is_present(OPT_TOTAL) {
|
||||
opt.print_grand_total = true;
|
||||
}
|
||||
if matches.is_present(OPT_INODES) {
|
||||
opt.show_inode_instead = true;
|
||||
}
|
||||
if matches.is_present(OPT_PRINT_TYPE) {
|
||||
opt.show_fs_type = true;
|
||||
}
|
||||
if matches.is_present(OPT_HUMAN_READABLE) {
|
||||
opt.human_readable_base = 1024;
|
||||
}
|
||||
if matches.is_present(OPT_HUMAN_READABLE_2) {
|
||||
opt.human_readable_base = 1000;
|
||||
}
|
||||
for fs_type in matches.values_of_lossy(OPT_TYPE).unwrap_or_default() {
|
||||
opt.fs_selector.include(fs_type.to_owned());
|
||||
}
|
||||
for fs_type in matches
|
||||
.values_of_lossy(OPT_EXCLUDE_TYPE)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
opt.fs_selector.exclude(fs_type.to_owned());
|
||||
}
|
||||
|
||||
let fs_list = filter_mount_list(read_fs_list(), &opt)
|
||||
.into_iter()
|
||||
.filter_map(Filesystem::new)
|
||||
.filter(|fs| fs.usage.blocks != 0 || opt.show_all_fs || opt.show_listed_fs)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// set headers
|
||||
let mut header = vec!["Filesystem"];
|
||||
if opt.show_fs_type {
|
||||
header.push("Type");
|
||||
}
|
||||
header.extend_from_slice(&if opt.show_inode_instead {
|
||||
["Inodes", "Iused", "IFree", "IUses%"]
|
||||
} else {
|
||||
[
|
||||
if opt.human_readable_base == -1 {
|
||||
"1k-blocks"
|
||||
} else {
|
||||
"Size"
|
||||
},
|
||||
"Used",
|
||||
"Available",
|
||||
"Use%",
|
||||
]
|
||||
});
|
||||
header.push("Mounted on");
|
||||
|
||||
for (idx, title) in header.iter().enumerate() {
|
||||
if idx == 0 || idx == header.len() - 1 {
|
||||
print!("{0: <16} ", title);
|
||||
} else if opt.show_fs_type && idx == 1 {
|
||||
print!("{0: <5} ", title);
|
||||
} else if idx == header.len() - 2 {
|
||||
print!("{0: >5} ", title);
|
||||
} else {
|
||||
print!("{0: >12} ", title);
|
||||
}
|
||||
}
|
||||
println!();
|
||||
for fs in fs_list.iter() {
|
||||
print!("{0: <16} ", fs.mountinfo.dev_name);
|
||||
if opt.show_fs_type {
|
||||
print!("{0: <5} ", fs.mountinfo.fs_type);
|
||||
}
|
||||
if opt.show_inode_instead {
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(fs.usage.files, opt.human_readable_base)
|
||||
);
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(fs.usage.files - fs.usage.ffree, opt.human_readable_base)
|
||||
);
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(fs.usage.ffree, opt.human_readable_base)
|
||||
);
|
||||
print!(
|
||||
"{0: >5} ",
|
||||
format!(
|
||||
"{0:.1}%",
|
||||
100f64 - 100f64 * (fs.usage.ffree as f64 / fs.usage.files as f64)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
let total_size = fs.usage.blocksize * fs.usage.blocks;
|
||||
let free_size = fs.usage.blocksize * fs.usage.bfree;
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(total_size, opt.human_readable_base)
|
||||
);
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(total_size - free_size, opt.human_readable_base)
|
||||
);
|
||||
print!(
|
||||
"{0: >12} ",
|
||||
human_readable(free_size, opt.human_readable_base)
|
||||
);
|
||||
print!("{0: >5} ", use_size(free_size, total_size));
|
||||
}
|
||||
print!("{0: <16}", fs.mountinfo.mount_dir);
|
||||
println!();
|
||||
}
|
||||
|
||||
EXIT_OK
|
||||
}
|
5
src/uu/df/main.rs
Normal file
5
src/uu/df/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
extern crate uu_df;
|
||||
|
||||
fn main() {
|
||||
std::process::exit(uu_df::uumain(std::env::args().collect()));
|
||||
}
|
9
tests/test_df.rs
Normal file
9
tests/test_df.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use common::util::*;
|
||||
|
||||
#[test]
|
||||
fn test_df_compatible() {
|
||||
let (_, mut ucmd) = at_and_ucmd!();
|
||||
let result = ucmd.arg("-ah").run();
|
||||
assert!(result.success);
|
||||
}
|
||||
// TODO
|
|
@ -54,6 +54,7 @@ generic! {
|
|||
"cut", test_cut;
|
||||
"dircolors", test_dircolors;
|
||||
"dirname", test_dirname;
|
||||
"df", test_df;
|
||||
"du", test_du;
|
||||
"echo", test_echo;
|
||||
"env", test_env;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue