1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

stat: implement support for macos

This commit is contained in:
Jan Scheer 2021-05-03 22:30:56 +02:00
parent 6908413b00
commit 56761ba584
4 changed files with 205 additions and 10 deletions

View file

@ -17,6 +17,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"] }
uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" }

View file

@ -9,6 +9,12 @@
extern crate time;
#[cfg(target_os = "linux")]
static LINUX_MTAB: &str = "/etc/mtab";
#[cfg(target_os = "linux")]
static LINUX_MOUNTINFO: &str = "/proc/self/mountinfo";
static MOUNT_OPT_BIND: &str = "bind";
use self::time::Timespec;
use std::time::UNIX_EPOCH;
pub use uucore::libc::{
@ -413,3 +419,184 @@ pub fn pretty_fstype<'a>(fstype: i64) -> Cow<'a, str> {
other => format!("UNKNOWN ({:#x})", other).into(),
}
}
#[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(
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;
}
}
}
#[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<_>>()
}
}

View file

@ -18,8 +18,6 @@ use uucore::entries;
use clap::{App, Arg, ArgMatches};
use std::borrow::Cow;
use std::convert::AsRef;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::path::Path;
use std::{cmp, fs, iter};
@ -97,7 +95,6 @@ pub mod options {
static ARG_FILES: &str = "files";
const MOUNT_INFO: &str = "/etc/mtab";
pub const F_ALTER: u8 = 1;
pub const F_ZERO: u8 = 1 << 1;
pub const F_LEFT: u8 = 1 << 2;
@ -490,13 +487,9 @@ impl Stater {
// mount points aren't displayed when showing filesystem information
None
} else {
let reader = BufReader::new(
File::open(MOUNT_INFO).unwrap_or_else(|_| panic!("Failed to read {}", MOUNT_INFO)),
);
let mut mount_list = reader
.lines()
.filter_map(Result::ok)
.filter_map(|line| line.split_whitespace().nth(1).map(ToOwned::to_owned))
let mut mount_list = read_fs_list()
.iter()
.map(|mi| mi.mount_dir.clone())
.collect::<Vec<String>>();
// Reverse sort. The longer comes first.
mount_list.sort();

View file

@ -317,6 +317,20 @@ fn test_multi_files() {
.stdout_is(expected_result(&args));
}
#[cfg(any(target_os = "linux", target_os = "freebsd", target_vendor = "apple"))]
#[test]
fn test_one_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "TEST_FILE.mp4";
at.touch(file);
ucmd.arg(file)
.succeeds()
.stdout_contains(format!("File: `{}'", file))
.stdout_contains(format!("Size: 0"))
.stdout_contains(format!("Access: (0644/-rw-r--r--)"));
}
#[test]
#[cfg(target_os = "linux")]
fn test_printf() {