From d709179f7748ac52984704330c358da6fdb34fcb Mon Sep 17 00:00:00 2001 From: fox0 <15684995+fox0@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:20:20 +0700 Subject: [PATCH 1/3] fs: create mntent.h wrapper --- fs/df.rs | 3 ++ fs/mntent.rs | 134 ++++++++++++++++++++++++++++++++++++++++++++++ fs/tests/mtab.txt | 2 + 3 files changed, 139 insertions(+) create mode 100644 fs/mntent.rs create mode 100644 fs/tests/mtab.txt diff --git a/fs/df.rs b/fs/df.rs index 905b4e76..2995fffc 100644 --- a/fs/df.rs +++ b/fs/df.rs @@ -7,6 +7,9 @@ // SPDX-License-Identifier: MIT // +#[cfg(target_os = "linux")] +mod mntent; + use clap::Parser; use gettextrs::{bind_textdomain_codeset, gettext, setlocale, textdomain, LocaleCategory}; use plib::PROJECT_NAME; diff --git a/fs/mntent.rs b/fs/mntent.rs new file mode 100644 index 00000000..4532e6be --- /dev/null +++ b/fs/mntent.rs @@ -0,0 +1,134 @@ +// +// Copyright (c) 2024 fox0 +// +// This file is part of the posixutils-rs project covered under +// the MIT License. For the full license text, please see the LICENSE +// file in the root directory of this project. +// SPDX-License-Identifier: MIT +// + +//! The mtab file +//! https://www.gnu.org/software/libc/manual/html_node/mtab.html + +use libc::{endmntent, getmntent, setmntent, FILE}; +use std::ffi::{CStr, CString}; +use std::io; +use std::sync::Mutex; + +const _PATH_MOUNTED: &CStr = c"/etc/mtab"; + +/// The mtab (contraction of mounted file systems table) file +/// is a system information file, commonly found on Unix-like systems +pub struct MountTable { + inner: *mut FILE, +} + +/// Structure describing a mount table entry +#[derive(Debug, PartialEq)] +pub struct MountTableEntity { + /// Device or server for filesystem + pub fsname: CString, + /// Directory mounted on + pub dir: CString, + /// Type of filesystem: ufs, nfs, etc + pub fstype: CString, + /// Comma-separated options for fs + pub opts: CString, + /// Dump frequency (in days) + pub freq: i32, + /// Pass number for `fsck`` + pub passno: i32, +} + +impl MountTable { + pub fn try_new() -> Result { + Self::open(_PATH_MOUNTED, c"r") + } + + /// Open mtab file + fn open(filename: &CStr, mode: &CStr) -> Result { + // Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe mem fd lock + // https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html + let inner = unsafe { setmntent(filename.as_ptr(), mode.as_ptr()) }; + if inner.is_null() { + return Err(io::Error::last_os_error()); + } + Ok(Self { inner }) + } +} + +impl Iterator for MountTable { + type Item = MountTableEntity; + + fn next(&mut self) -> Option { + static THREAD_UNSAFE_FUNCTION_MUTEX: Mutex<()> = Mutex::new(()); + let _lock = THREAD_UNSAFE_FUNCTION_MUTEX.lock().unwrap(); + + // Preliminary: | MT-Unsafe race:mntentbuf locale | AS-Unsafe corrupt heap init | AC-Unsafe init corrupt lock mem + // https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html + let me = unsafe { getmntent(self.inner) }; + if me.is_null() { + return None; + } + + unsafe { + Some(MountTableEntity { + fsname: CStr::from_ptr((*me).mnt_fsname).into(), + dir: CStr::from_ptr((*me).mnt_dir).into(), + fstype: CStr::from_ptr((*me).mnt_type).into(), + opts: CStr::from_ptr((*me).mnt_opts).into(), + freq: (*me).mnt_freq, + passno: (*me).mnt_passno, + }) + } + } +} + +impl Drop for MountTable { + /// Close mtab file + fn drop(&mut self) { + // Preliminary: | MT-Safe | AS-Unsafe heap lock | AC-Unsafe lock mem fd + // https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html + let _rc = unsafe { endmntent(self.inner) }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_open() { + let mtab = MountTable::open(c"tests/mtab.txt", c"r"); + assert!(mtab.is_ok()); + } + + #[test] + fn test_iterable() { + let mtab = MountTable::open(c"tests/mtab.txt", c"r").unwrap(); + let vec = Vec::from_iter(mtab); + assert_eq!(vec.len(), 2); + assert_eq!( + vec[0], + MountTableEntity { + fsname: CString::new("/dev/sdb1").unwrap(), + dir: CString::new("/").unwrap(), + fstype: CString::new("ext3").unwrap(), + opts: CString::new("rw,relatime,errors=remount-ro").unwrap(), + freq: 0, + passno: 0, + } + ); + assert_eq!( + vec[1], + MountTableEntity { + fsname: CString::new("proc").unwrap(), + dir: CString::new("/proc").unwrap(), + fstype: CString::new("proc").unwrap(), + opts: CString::new("rw,noexec,nosuid,nodev").unwrap(), + freq: 0, + passno: 0, + } + ); + } +} diff --git a/fs/tests/mtab.txt b/fs/tests/mtab.txt new file mode 100644 index 00000000..86136870 --- /dev/null +++ b/fs/tests/mtab.txt @@ -0,0 +1,2 @@ +/dev/sdb1 / ext3 rw,relatime,errors=remount-ro 0 0 +proc /proc proc rw,noexec,nosuid,nodev 0 0 From 88b0342def2a1c352c86260cc28fdc52fd0bf5b5 Mon Sep 17 00:00:00 2001 From: fox0 <15684995+fox0@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:28:56 +0700 Subject: [PATCH 2/3] fs: use mntent.h wrapper --- fs/df.rs | 61 ++++++++++++++++++++------------------------------------ 1 file changed, 22 insertions(+), 39 deletions(-) diff --git a/fs/df.rs b/fs/df.rs index 2995fffc..3fd0865e 100644 --- a/fs/df.rs +++ b/fs/df.rs @@ -10,15 +10,17 @@ #[cfg(target_os = "linux")] mod mntent; +#[cfg(target_os = "linux")] +use crate::mntent::MountTable; + use clap::Parser; use gettextrs::{bind_textdomain_codeset, gettext, setlocale, textdomain, LocaleCategory}; use plib::PROJECT_NAME; -use std::ffi::{CStr, CString}; +#[cfg(target_os = "macos")] +use std::ffi::CStr; +use std::ffi::CString; use std::io; -#[cfg(target_os = "linux")] -const _PATH_MOUNTED: &'static str = "/etc/mtab"; - #[derive(Parser)] #[command(version, about = gettext("df - report free storage space"))] struct Args { @@ -57,9 +59,7 @@ fn to_cstr(array: &[libc::c_char]) -> &CStr { } } -fn stat(filename_str: &str) -> io::Result { - let filename = CString::new(filename_str).unwrap(); - +fn stat(filename: &CString) -> io::Result { unsafe { let mut st: libc::stat = std::mem::zeroed(); let rc = libc::stat(filename.as_ptr(), &mut st); @@ -105,11 +105,11 @@ impl MountList { } } - fn push(&mut self, fsstat: &libc::statfs, devname: &CStr, dirname: &CStr) { + fn push(&mut self, fsstat: &libc::statfs, devname: &CString, dirname: &CString) { let dev = { - if let Ok(st) = stat(devname.to_str().unwrap()) { + if let Ok(st) = stat(devname) { st.st_rdev as i64 - } else if let Ok(st) = stat(dirname.to_str().unwrap()) { + } else if let Ok(st) = stat(dirname) { st.st_dev as i64 } else { -1 @@ -139,9 +139,9 @@ fn read_mount_info() -> io::Result { let mounts: &[libc::statfs] = std::slice::from_raw_parts(mounts as _, n_mnt as _); for mount in mounts { - let devname = to_cstr(&mount.f_mntfromname); - let dirname = to_cstr(&mount.f_mntonname); - info.push(mount, devname, dirname); + let devname = to_cstr(&mount.f_mntfromname).into(); + let dirname = to_cstr(&mount.f_mntonname).into(); + info.push(mount, &devname, &dirname); } } @@ -152,47 +152,30 @@ fn read_mount_info() -> io::Result { fn read_mount_info() -> io::Result { let mut info = MountList::new(); - unsafe { - let path_mnt = CString::new(_PATH_MOUNTED).unwrap(); - let mnt_mode = CString::new("r").unwrap(); - let f = libc::setmntent(path_mnt.as_ptr(), mnt_mode.as_ptr()); - if f.is_null() { - return Err(io::Error::last_os_error()); - } - - loop { - let me = libc::getmntent(f); - if me.is_null() { - break; - } - - let me_devname = (*me).mnt_fsname; - let me_dirname = (*me).mnt_dir; - let devname = CStr::from_ptr(me_devname); - let dirname = CStr::from_ptr(me_dirname); - - let mut mount: libc::statfs = std::mem::zeroed(); - let rc = libc::statfs(dirname.as_ptr(), &mut mount); + let mounts = MountTable::try_new()?; + for mount in mounts { + unsafe { + let mut buf: libc::statfs = std::mem::zeroed(); + let rc = libc::statfs(mount.dir.as_ptr(), &mut buf); if rc < 0 { eprintln!( "{}: {}", - dirname.to_str().unwrap(), + mount.dir.to_str().unwrap(), io::Error::last_os_error() ); continue; } - info.push(&mount, devname, dirname); + info.push(&buf, &mount.fsname, &mount.dir); } - - libc::endmntent(f); } Ok(info) } fn mask_fs_by_file(info: &mut MountList, filename: &str) -> io::Result<()> { - let stat_res = stat(filename); + let c_filename = CString::new(filename).expect("`filename` contains an internal 0 byte"); + let stat_res = stat(&c_filename); if let Err(e) = stat_res { eprintln!("{}: {}", filename, e); return Err(e); From 67c0f0bac36908d3b2efefd9c1975191ab5514f0 Mon Sep 17 00:00:00 2001 From: fox0 <15684995+fox0@users.noreply.github.com> Date: Thu, 26 Sep 2024 21:55:16 +0700 Subject: [PATCH 3/3] fs: add one test for mntent --- fs/mntent.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/mntent.rs b/fs/mntent.rs index 4532e6be..693400cc 100644 --- a/fs/mntent.rs +++ b/fs/mntent.rs @@ -103,6 +103,13 @@ mod tests { assert!(mtab.is_ok()); } + #[test] + fn test_open_not_found() { + let mtab = MountTable::open(c"/tmp/not_found", c"r"); + let mtab = mtab.err().unwrap(); + assert_eq!(mtab.kind(), std::io::ErrorKind::NotFound); + } + #[test] fn test_iterable() { let mtab = MountTable::open(c"tests/mtab.txt", c"r").unwrap();