use std::fmt::{self, Debug}; use std::mem; use std::os::unix::io::AsRawFd; #[cfg(not(any(target_os = "linux", target_os = "android")))] use std::ffi::CStr; use crate::{NixPath, Result, errno::Errno}; #[cfg(target_os = "android")] pub type fsid_t = libc::__fsid_t; #[cfg(not(target_os = "android"))] pub type fsid_t = libc::fsid_t; #[derive(Clone, Copy)] #[repr(transparent)] pub struct Statfs(libc::statfs); #[cfg(target_os = "freebsd")] type fs_type_t = u32; #[cfg(target_os = "android")] type fs_type_t = libc::c_ulong; #[cfg(all(target_os = "linux", target_arch = "s390x"))] type fs_type_t = libc::c_uint; #[cfg(all(target_os = "linux", target_env = "musl"))] type fs_type_t = libc::c_ulong; #[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))] type fs_type_t = libc::__fsword_t; #[cfg(any( target_os = "freebsd", target_os = "android", all(target_os = "linux", target_arch = "s390x"), all(target_os = "linux", target_env = "musl"), all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))), ))] #[derive(Eq, Copy, Clone, PartialEq, Debug)] pub struct FsType(pub fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const ADFS_SUPER_MAGIC: FsType = FsType(libc::ADFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const AFFS_SUPER_MAGIC: FsType = FsType(libc::AFFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const CODA_SUPER_MAGIC: FsType = FsType(libc::CODA_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const CRAMFS_MAGIC: FsType = FsType(libc::CRAMFS_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const EFS_SUPER_MAGIC: FsType = FsType(libc::EFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const EXT2_SUPER_MAGIC: FsType = FsType(libc::EXT2_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const EXT3_SUPER_MAGIC: FsType = FsType(libc::EXT3_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const EXT4_SUPER_MAGIC: FsType = FsType(libc::EXT4_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const HPFS_SUPER_MAGIC: FsType = FsType(libc::HPFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const HUGETLBFS_MAGIC: FsType = FsType(libc::HUGETLBFS_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const ISOFS_SUPER_MAGIC: FsType = FsType(libc::ISOFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const JFFS2_SUPER_MAGIC: FsType = FsType(libc::JFFS2_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const MINIX_SUPER_MAGIC: FsType = FsType(libc::MINIX_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const MINIX_SUPER_MAGIC2: FsType = FsType(libc::MINIX_SUPER_MAGIC2 as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const MINIX2_SUPER_MAGIC: FsType = FsType(libc::MINIX2_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const MINIX2_SUPER_MAGIC2: FsType = FsType(libc::MINIX2_SUPER_MAGIC2 as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const MSDOS_SUPER_MAGIC: FsType = FsType(libc::MSDOS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const NCP_SUPER_MAGIC: FsType = FsType(libc::NCP_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const NFS_SUPER_MAGIC: FsType = FsType(libc::NFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const OPENPROM_SUPER_MAGIC: FsType = FsType(libc::OPENPROM_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const OVERLAYFS_SUPER_MAGIC: FsType = FsType(libc::OVERLAYFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const PROC_SUPER_MAGIC: FsType = FsType(libc::PROC_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const QNX4_SUPER_MAGIC: FsType = FsType(libc::QNX4_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const REISERFS_SUPER_MAGIC: FsType = FsType(libc::REISERFS_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const SMB_SUPER_MAGIC: FsType = FsType(libc::SMB_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const TMPFS_MAGIC: FsType = FsType(libc::TMPFS_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const USBDEVICE_SUPER_MAGIC: FsType = FsType(libc::USBDEVICE_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const CGROUP_SUPER_MAGIC: FsType = FsType(libc::CGROUP_SUPER_MAGIC as fs_type_t); #[cfg(all(target_os = "linux", not(target_env = "musl")))] pub const CGROUP2_SUPER_MAGIC: FsType = FsType(libc::CGROUP2_SUPER_MAGIC as fs_type_t); impl Statfs { /// Magic code defining system type #[cfg(not(any( target_os = "openbsd", target_os = "dragonfly", target_os = "ios", target_os = "macos" )))] pub fn filesystem_type(&self) -> FsType { FsType(self.0.f_type) } /// Magic code defining system type #[cfg(not(any(target_os = "linux", target_os = "android")))] pub fn filesystem_type_name(&self) -> &str { let c_str = unsafe { CStr::from_ptr(self.0.f_fstypename.as_ptr()) }; c_str.to_str().unwrap() } /// Optimal transfer block size #[cfg(any(target_os = "ios", target_os = "macos"))] pub fn optimal_transfer_size(&self) -> i32 { self.0.f_iosize } /// Optimal transfer block size #[cfg(target_os = "openbsd")] pub fn optimal_transfer_size(&self) -> u32 { self.0.f_iosize } /// Optimal transfer block size #[cfg(all(target_os = "linux", target_arch = "s390x"))] pub fn optimal_transfer_size(&self) -> u32 { self.0.f_bsize } /// Optimal transfer block size #[cfg(any( target_os = "android", all(target_os = "linux", target_env = "musl") ))] pub fn optimal_transfer_size(&self) -> libc::c_ulong { self.0.f_bsize } /// Optimal transfer block size #[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))] pub fn optimal_transfer_size(&self) -> libc::__fsword_t { self.0.f_bsize } /// Optimal transfer block size #[cfg(target_os = "dragonfly")] pub fn optimal_transfer_size(&self) -> libc::c_long { self.0.f_iosize } /// Optimal transfer block size #[cfg(target_os = "freebsd")] pub fn optimal_transfer_size(&self) -> u64 { self.0.f_iosize } /// Size of a block #[cfg(any(target_os = "ios", target_os = "macos", target_os = "openbsd"))] pub fn block_size(&self) -> u32 { self.0.f_bsize } /// Size of a block // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 #[cfg(all(target_os = "linux", target_arch = "s390x"))] pub fn block_size(&self) -> u32 { self.0.f_bsize } /// Size of a block // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 #[cfg(all(target_os = "linux", target_env = "musl"))] pub fn block_size(&self) -> libc::c_ulong { self.0.f_bsize } /// Size of a block // f_bsize on linux: https://github.com/torvalds/linux/blob/master/fs/nfs/super.c#L471 #[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))] pub fn block_size(&self) -> libc::__fsword_t { self.0.f_bsize } /// Size of a block #[cfg(target_os = "freebsd")] pub fn block_size(&self) -> u64 { self.0.f_bsize } /// Size of a block #[cfg(target_os = "android")] pub fn block_size(&self) -> libc::c_ulong { self.0.f_bsize } /// Size of a block #[cfg(target_os = "dragonfly")] pub fn block_size(&self) -> libc::c_long { self.0.f_bsize } /// Maximum length of filenames #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] pub fn maximum_name_length(&self) -> u32 { self.0.f_namemax } /// Maximum length of filenames #[cfg(all(target_os = "linux", target_arch = "s390x"))] pub fn maximum_name_length(&self) -> u32 { self.0.f_namelen } /// Maximum length of filenames #[cfg(all(target_os = "linux", target_env = "musl"))] pub fn maximum_name_length(&self) -> libc::c_ulong { self.0.f_namelen } /// Maximum length of filenames #[cfg(all(target_os = "linux", not(any(target_arch = "s390x", target_env = "musl"))))] pub fn maximum_name_length(&self) -> libc::__fsword_t { self.0.f_namelen } /// Maximum length of filenames #[cfg(target_os = "android")] pub fn maximum_name_length(&self) -> libc::c_ulong { self.0.f_namelen } /// Total data blocks in filesystem #[cfg(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", ))] pub fn blocks(&self) -> u64 { self.0.f_blocks } /// Total data blocks in filesystem #[cfg(target_os = "dragonfly")] pub fn blocks(&self) -> libc::c_long { self.0.f_blocks } /// Total data blocks in filesystem #[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] pub fn blocks(&self) -> u64 { self.0.f_blocks } /// Total data blocks in filesystem #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) )))] pub fn blocks(&self) -> libc::c_ulong { self.0.f_blocks } /// Free blocks in filesystem #[cfg(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", ))] pub fn blocks_free(&self) -> u64 { self.0.f_bfree } /// Free blocks in filesystem #[cfg(target_os = "dragonfly")] pub fn blocks_free(&self) -> libc::c_long { self.0.f_bfree } /// Free blocks in filesystem #[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] pub fn blocks_free(&self) -> u64 { self.0.f_bfree } /// Free blocks in filesystem #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) )))] pub fn blocks_free(&self) -> libc::c_ulong { self.0.f_bfree } /// Free blocks available to unprivileged user #[cfg(any(target_os = "ios", target_os = "macos", target_os = "android"))] pub fn blocks_available(&self) -> u64 { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(target_os = "dragonfly")] pub fn blocks_available(&self) -> libc::c_long { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(any(target_os = "freebsd", target_os = "openbsd"))] pub fn blocks_available(&self) -> i64 { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] pub fn blocks_available(&self) -> u64 { self.0.f_bavail } /// Free blocks available to unprivileged user #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) )))] pub fn blocks_available(&self) -> libc::c_ulong { self.0.f_bavail } /// Total file nodes in filesystem #[cfg(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", ))] pub fn files(&self) -> u64 { self.0.f_files } /// Total file nodes in filesystem #[cfg(target_os = "dragonfly")] pub fn files(&self) -> libc::c_long { self.0.f_files } /// Total file nodes in filesystem #[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] pub fn files(&self) -> libc::fsfilcnt_t { self.0.f_files } /// Total file nodes in filesystem #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) )))] pub fn files(&self) -> libc::c_ulong { self.0.f_files } /// Free file nodes in filesystem #[cfg(any( target_os = "android", target_os = "ios", target_os = "macos", target_os = "openbsd" ))] pub fn files_free(&self) -> u64 { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(target_os = "dragonfly")] pub fn files_free(&self) -> libc::c_long { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(target_os = "freebsd")] pub fn files_free(&self) -> i64 { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))))] pub fn files_free(&self) -> libc::fsfilcnt_t { self.0.f_ffree } /// Free file nodes in filesystem #[cfg(not(any( target_os = "ios", target_os = "macos", target_os = "android", target_os = "freebsd", target_os = "openbsd", target_os = "dragonfly", all(target_os = "linux", any(target_env = "musl", all(target_arch = "x86_64", target_pointer_width = "32"))) )))] pub fn files_free(&self) -> libc::c_ulong { self.0.f_ffree } /// Filesystem ID pub fn filesystem_id(&self) -> fsid_t { self.0.f_fsid } } impl Debug for Statfs { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Statfs") .field("optimal_transfer_size", &self.optimal_transfer_size()) .field("block_size", &self.block_size()) .field("blocks", &self.blocks()) .field("blocks_free", &self.blocks_free()) .field("blocks_available", &self.blocks_available()) .field("files", &self.files()) .field("files_free", &self.files_free()) .field("filesystem_id", &self.filesystem_id()) .finish() } } pub fn statfs(path: &P) -> Result { unsafe { let mut stat = mem::MaybeUninit::::uninit(); let res = path.with_nix_path(|path| libc::statfs(path.as_ptr(), stat.as_mut_ptr()))?; Errno::result(res).map(|_| Statfs(stat.assume_init())) } } pub fn fstatfs(fd: &T) -> Result { unsafe { let mut stat = mem::MaybeUninit::::uninit(); Errno::result(libc::fstatfs(fd.as_raw_fd(), stat.as_mut_ptr())) .map(|_| Statfs(stat.assume_init())) } } #[cfg(test)] mod test { use std::fs::File; use crate::sys::statfs::*; use crate::sys::statvfs::*; use std::path::Path; #[test] fn statfs_call() { check_statfs("/tmp"); check_statfs("/dev"); check_statfs("/run"); check_statfs("/"); } #[test] fn fstatfs_call() { check_fstatfs("/tmp"); check_fstatfs("/dev"); check_fstatfs("/run"); check_fstatfs("/"); } fn check_fstatfs(path: &str) { if !Path::new(path).exists() { return; } let vfs = statvfs(path.as_bytes()).unwrap(); let file = File::open(path).unwrap(); let fs = fstatfs(&file).unwrap(); assert_fs_equals(fs, vfs); } fn check_statfs(path: &str) { if !Path::new(path).exists() { return; } let vfs = statvfs(path.as_bytes()).unwrap(); let fs = statfs(path.as_bytes()).unwrap(); assert_fs_equals(fs, vfs); } fn assert_fs_equals(fs: Statfs, vfs: Statvfs) { assert_eq!(fs.files() as u64, vfs.files() as u64); assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); } // This test is ignored because files_free/blocks_free can change after statvfs call and before // statfs call. #[test] #[ignore] fn statfs_call_strict() { check_statfs_strict("/tmp"); check_statfs_strict("/dev"); check_statfs_strict("/run"); check_statfs_strict("/"); } // This test is ignored because files_free/blocks_free can change after statvfs call and before // fstatfs call. #[test] #[ignore] fn fstatfs_call_strict() { check_fstatfs_strict("/tmp"); check_fstatfs_strict("/dev"); check_fstatfs_strict("/run"); check_fstatfs_strict("/"); } fn check_fstatfs_strict(path: &str) { if !Path::new(path).exists() { return; } let vfs = statvfs(path.as_bytes()); let file = File::open(path).unwrap(); let fs = fstatfs(&file); assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) } fn check_statfs_strict(path: &str) { if !Path::new(path).exists() { return; } let vfs = statvfs(path.as_bytes()); let fs = statfs(path.as_bytes()); assert_fs_equals_strict(fs.unwrap(), vfs.unwrap()) } fn assert_fs_equals_strict(fs: Statfs, vfs: Statvfs) { assert_eq!(fs.files_free() as u64, vfs.files_free() as u64); assert_eq!(fs.blocks_free() as u64, vfs.blocks_free() as u64); assert_eq!(fs.blocks_available() as u64, vfs.blocks_available() as u64); assert_eq!(fs.files() as u64, vfs.files() as u64); assert_eq!(fs.blocks() as u64, vfs.blocks() as u64); assert_eq!(fs.block_size() as u64, vfs.fragment_size() as u64); } }