proc-mounts-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100133220ustar { "git": { "sha1": "2eca05a2b8bd27c993bc22c9742263547e1dbc72" }, "path_in_vcs": "" }proc-mounts-0.3.0/.gitignore000064400000000000000000000000220072674642500141240ustar 00000000000000target Cargo.lock proc-mounts-0.3.0/CHANGELOG.md000064400000000000000000000017150072674642500137570ustar 00000000000000# 0.2.2 - Added a `MountTab` type for non-destructive editing of fstab - Split the mounts module into sub-modules - Fix the `Display` for `MountInfo` to write `defaults` if the `options` field is empty - Added `Default` derives # 0.2.1 - Implement `Display` for `MountInfo` and `SwapInfo` - Implement `FromStr` for `MountInfo` and `SwapInfo` - Add deprecation notice for `MountInfo`/`SwapInfo`::`parse_line` # 0.2.0 - Support parsing the `/etc/fstab` file, in addition to `/proc/mounts` - `MountList::new_from_file("/etc/fstab")` - `MountIter::new_from_file("/etc/fstb")` - Support parsing any type which implements `BufRead`: - `MountList::new_from_reader(reader)` - `MountIter::new_from_reader(reader)` - Support equivalents for swap tab files - `SwapIter::new_from_file("/proc/swaps")` - `SwapIter::new_from_reader(reader)` - `SwapList::new_from_file("/proc/swaps")` - `SwapList::new_from_reader(reader)` # 0.1.2 - Initial release proc-mounts-0.3.0/Cargo.lock0000644000000034300000000000100112750ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "partition-identity" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa925f9becb532d758b0014b472c576869910929cf4c3f8054b386f19ab9e21" dependencies = [ "thiserror", ] [[package]] name = "proc-macro2" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "proc-mounts" version = "0.3.0" dependencies = [ "partition-identity", ] [[package]] name = "quote" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "thiserror" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" proc-mounts-0.3.0/Cargo.toml0000644000000015010000000000100113150ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "proc-mounts" version = "0.3.0" description = "Fetch active mounts and swaps on a Linux system" readme = "README.md" keywords = ["linux", "proc", "mounts", "swaps"] categories = ["os::unix-apis"] license = "MIT" repository = "https://github.com/pop-os/proc-mounts" resolver = "2" [dependencies.partition-identity] version = "0.3.0" proc-mounts-0.3.0/Cargo.toml.orig000064400000000000000000000005300072674642500150270ustar 00000000000000[package] name = "proc-mounts" version = "0.3.0" edition = "2021" description = "Fetch active mounts and swaps on a Linux system" repository = "https://github.com/pop-os/proc-mounts" readme = "README.md" license = "MIT" categories = ["os::unix-apis"] keywords = ["linux", "proc", "mounts", "swaps"] [dependencies] partition-identity = "0.3.0" proc-mounts-0.3.0/LICENSE000064400000000000000000000020510072674642500131450ustar 00000000000000MIT License Copyright (c) 2018 System76 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. proc-mounts-0.3.0/README.md000064400000000000000000000007170072674642500134260ustar 00000000000000# proc-mounts Rust crate that provides easy access to data from the `/proc/swaps` and `/proc/mounts` files. ```rust extern crate proc_mounts; use proc_mounts::{MountIter, SwapIter}; use std::io; fn main() -> io::Result<()> { println!("# Active Mounts"); for mount in MountIter::new()? { println!("{:#?}", mount); } println!("# Active Swaps"); for swap in SwapIter::new()? { println!("{:#?}", swap); } Ok(()) } ```proc-mounts-0.3.0/examples/example.rs000064400000000000000000000013710072674642500157630ustar 00000000000000extern crate proc_mounts; use proc_mounts::{MountIter, SwapIter}; use std::io; fn main() -> io::Result<()> { println!("# Active Mounts"); for mount in MountIter::new()? { match mount { Ok(mount) => println!("{:?}: {:?}", mount.source, mount.dest), Err(why) => eprintln!("error reading mount: {}", why), } } println!("# Active Swaps"); for swap in SwapIter::new()? { println!("{:#?}", swap); } println!("# Active Fstab Mounts"); for mount in MountIter::new_from_file("/etc/fstab")? { match mount { Ok(mount) => println!("{:?}: {:?}", mount.source, mount.dest), Err(why) => eprintln!("error reading mount: {}", why), } } Ok(()) } proc-mounts-0.3.0/rust-toolchain000064400000000000000000000000070072674642500150350ustar 000000000000001.59.0 proc-mounts-0.3.0/rustfmt.toml000064400000000000000000000006560072674642500145520ustar 00000000000000unstable_features = true comment_width = 100 condense_wildcard_suffixes = true fn_single_line = true format_strings = true imports_indent = "Block" max_width = 100 normalize_comments = true reorder_imports = true reorder_modules = true reorder_impl_items = true struct_field_align_threshold = 30 use_field_init_shorthand = true wrap_comments = true merge_imports = true force_multiline_blocks = false use_small_heuristics = "Max" proc-mounts-0.3.0/src/lib.rs000064400000000000000000000011100072674642500140360ustar 00000000000000//! Provides easy access to data from the `/proc/swaps` and `/proc/mounts` files. //! //! ```rust,no_run //! extern crate proc_mounts; //! //! use proc_mounts::{MountIter, SwapIter}; //! use std::io; //! //! fn main() -> io::Result<()> { //! println!("# Active Mounts"); //! for mount in MountIter::new()? { //! println!("{:#?}", mount); //! } //! //! println!("# Active Swaps"); //! for swap in SwapIter::new()? { //! println!("{:#?}", swap); //! } //! //! Ok(()) //! } //! ``` mod mounts; mod swaps; pub use self::{mounts::*, swaps::*};proc-mounts-0.3.0/src/mounts/info.rs000064400000000000000000000103360072674642500155620ustar 00000000000000use partition_identity::PartitionID; use std::{ char, ffi::OsString, fmt::{self, Display, Formatter}, io::{self, Error, ErrorKind}, os::unix::ffi::OsStringExt, path::PathBuf, str::FromStr, }; /// A mount entry which contains information regarding how and where a source /// is mounted. #[derive(Debug, Default, Clone, Hash, Eq, PartialEq)] pub struct MountInfo { /// The source which is mounted. pub source: PathBuf, /// Where the source is mounted. pub dest: PathBuf, /// The type of the mounted file system. pub fstype: String, /// Options specified for this file system. pub options: Vec, /// Defines if the file system should be dumped. pub dump: i32, /// Defines if the file system should be checked, and in what order. pub pass: i32, } impl Display for MountInfo { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!( fmt, "{} {} {} {} {} {}", self.source.display(), self.dest.display(), self.fstype, if self.options.is_empty() { "defaults".into() } else { self.options.join(",") }, self.dump, self.pass ) } } impl FromStr for MountInfo { type Err = io::Error; fn from_str(line: &str) -> Result { let mut parts = line.split_whitespace(); fn map_err(why: &'static str) -> io::Error { Error::new(ErrorKind::InvalidData, why) } let source = parts.next().ok_or_else(|| map_err("missing source"))?; let dest = parts.next().ok_or_else(|| map_err("missing dest"))?; let fstype = parts.next().ok_or_else(|| map_err("missing type"))?; let options = parts.next().ok_or_else(|| map_err("missing options"))?; let dump = parts.next().map_or(Ok(0), |value| { value.parse::().map_err(|_| map_err("dump value is not a number")) })?; let pass = parts.next().map_or(Ok(0), |value| { value.parse::().map_err(|_| map_err("pass value is not a number")) })?; let path = Self::parse_value(source)?; let path = path.to_str().ok_or_else(|| map_err("non-utf8 paths are unsupported"))?; let source = if path.starts_with("/dev/disk/by-") { Self::fetch_from_disk_by_path(path)? } else { PathBuf::from(path) }; let path = Self::parse_value(dest)?; let path = path.to_str().ok_or_else(|| map_err("non-utf8 paths are unsupported"))?; let dest = PathBuf::from(path); Ok(MountInfo { source, dest, fstype: fstype.to_owned(), options: options.split(',').map(String::from).collect(), dump, pass, }) } } impl MountInfo { /// Attempt to parse a `/proc/mounts`-like line. #[deprecated] pub fn parse_line(line: &str) -> io::Result { line.parse::() } fn fetch_from_disk_by_path(path: &str) -> io::Result { PartitionID::from_disk_by_path(path) .map_err(|why| Error::new(ErrorKind::InvalidData, format!("{}: {}", path, why)))? .get_device_path() .ok_or_else(|| { Error::new(ErrorKind::NotFound, format!("device path for {} was not found", path)) }) } fn parse_value(value: &str) -> io::Result { let mut ret = Vec::new(); let mut bytes = value.bytes(); while let Some(b) = bytes.next() { match b { b'\\' => { let mut code = 0; for _i in 0..3 { if let Some(b) = bytes.next() { code *= 8; code += u32::from_str_radix(&(b as char).to_string(), 8) .map_err(|err| Error::new(ErrorKind::Other, err))?; } else { return Err(Error::new(ErrorKind::Other, "truncated octal code")); } } ret.push(code as u8); } _ => { ret.push(b); } } } Ok(OsString::from_vec(ret)) } } proc-mounts-0.3.0/src/mounts/iter.rs000064400000000000000000000040630072674642500155720ustar 00000000000000use super::MountInfo; use std::{ fs::File, io::{self, BufRead, BufReader}, path::Path, str::FromStr, }; /// Iteratively parse the `/proc/mounts` file. pub struct MountIter { file: R, buffer: String, } impl MountIter> { pub fn new() -> io::Result { Self::new_from_file("/proc/mounts") } /// Read mounts from any mount-tab-like file. pub fn new_from_file>(path: P) -> io::Result { Ok(Self::new_from_reader(BufReader::new(File::open(path)?))) } } impl MountIter { /// Read mounts from any in-memory buffer. pub fn new_from_reader(readable: R) -> Self { Self { file: readable, buffer: String::with_capacity(512) } } /// Iterator-based variant of `source_mounted_at`. /// /// Returns true if the `source` is mounted at the given `dest`. /// /// Due to iterative parsing of the mount file, an error may be returned. pub fn source_mounted_at, P: AsRef>( source: D, path: P, ) -> io::Result { let source = source.as_ref(); let path = path.as_ref(); let mut is_found = false; let mounts = MountIter::new()?; for mount in mounts { let mount = mount?; if mount.source == source { is_found = mount.dest == path; break; } } Ok(is_found) } } impl Iterator for MountIter { type Item = io::Result; fn next(&mut self) -> Option { loop { self.buffer.clear(); match self.file.read_line(&mut self.buffer) { Ok(read) if read == 0 => return None, Ok(_) => { let line = self.buffer.trim_start(); if !(line.starts_with('#') || line.is_empty()) { return Some(MountInfo::from_str(line)); } } Err(why) => return Some(Err(why)), } } } } proc-mounts-0.3.0/src/mounts/list.rs000064400000000000000000000056520072674642500156070ustar 00000000000000use super::{MountInfo, MountIter}; use std::{ io::{self, BufRead}, os::unix::ffi::OsStrExt, path::Path, str::FromStr, }; /// A list of parsed mount entries from `/proc/mounts`. #[derive(Debug, Default, Clone, Hash, Eq, PartialEq)] pub struct MountList(pub Vec); impl MountList { /// Parse mounts given from an iterator of mount entry lines. pub fn parse_from<'a, I: Iterator>(lines: I) -> io::Result { lines.map(MountInfo::from_str).collect::>>().map(MountList) } /// Read a new list of mounts into memory from `/proc/mounts`. pub fn new() -> io::Result { Ok(MountList(MountIter::new()?.collect::>>()?)) } /// Read a new list of mounts into memory from any mount-tab-like file. pub fn new_from_file>(path: P) -> io::Result { Ok(MountList(MountIter::new_from_file(path)?.collect::>>()?)) } /// Read a new list of mounts into memory from any mount-tab-like file. pub fn new_from_reader(reader: R) -> io::Result { Ok(MountList(MountIter::new_from_reader(reader).collect::>>()?)) } // Returns true if the `source` is mounted at the given `dest`. pub fn source_mounted_at, P: AsRef>(&self, source: D, path: P) -> bool { self.get_mount_by_source(source) .map_or(false, |mount| mount.dest.as_path() == path.as_ref()) } /// Find the first mount which which has the `path` destination. pub fn get_mount_by_dest>(&self, path: P) -> Option<&MountInfo> { self.0.iter().find(|mount| mount.dest == path.as_ref()) } /// Find the first mount hich has the source `path`. pub fn get_mount_by_source>(&self, path: P) -> Option<&MountInfo> { self.0.iter().find(|mount| mount.source == path.as_ref()) } /// Iterate through each source that starts with the given `path`. pub fn source_starts_with<'a>( &'a self, path: &'a Path, ) -> Box + 'a> { self.starts_with(path.as_os_str().as_bytes(), |m| &m.source) } /// Iterate through each destination that starts with the given `path`. pub fn destination_starts_with<'a>( &'a self, path: &'a Path, ) -> Box + 'a> { self.starts_with(path.as_os_str().as_bytes(), |m| &m.dest) } fn starts_with<'a, F: Fn(&'a MountInfo) -> &'a Path + 'a>( &'a self, path: &'a [u8], func: F, ) -> Box + 'a> { let iterator = self.0.iter().filter(move |mount| { let input = func(mount).as_os_str().as_bytes(); input.len() >= path.len() && &input[..path.len()] == path }); Box::new(iterator) } } proc-mounts-0.3.0/src/mounts/mod.rs000064400000000000000000000045300072674642500154050ustar 00000000000000mod info; mod iter; mod list; mod tab; pub use self::{info::*, iter::*, list::*, tab::*}; #[cfg(test)] mod tests { use super::*; use std::path::{Path, PathBuf}; const SAMPLE: &str = r#"sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 udev /dev devtmpfs rw,nosuid,relatime,size=16420480k,nr_inodes=4105120,mode=755 0 0 tmpfs /run tmpfs rw,nosuid,noexec,relatime,size=3291052k,mode=755 0 0 /dev/sda2 / ext4 rw,noatime,errors=remount-ro,data=ordered fusectl /sys/fs/fuse/connections fusectl rw,relatime 0 0 /dev/sda1 /boot/efi vfat rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 0 /dev/sda6 /mnt/data ext4 rw,noatime,data=ordered 0 0"#; #[test] fn source_mounted_at() { let mounts = MountList::parse_from(SAMPLE.lines()).unwrap(); assert!(mounts.source_mounted_at("/dev/sda2", "/")); assert!(mounts.source_mounted_at("/dev/sda1", "/boot/efi")); } #[test] fn mounts() { let mounts = MountList::parse_from(SAMPLE.lines()).unwrap(); assert_eq!( mounts.get_mount_by_source(Path::new("/dev/sda1")).unwrap(), &MountInfo { source: PathBuf::from("/dev/sda1"), dest: PathBuf::from("/boot/efi"), fstype: "vfat".into(), options: vec![ "rw".into(), "relatime".into(), "fmask=0077".into(), "dmask=0077".into(), "codepage=437".into(), "iocharset=iso8859-1".into(), "shortname=mixed".into(), "errors=remount-ro".into(), ], dump: 0, pass: 0, } ); let path = &Path::new("/"); assert_eq!( mounts.destination_starts_with(path).map(|m| m.dest.clone()).collect::>(), vec![ PathBuf::from("/sys"), PathBuf::from("/proc"), PathBuf::from("/dev"), PathBuf::from("/run"), PathBuf::from("/"), PathBuf::from("/sys/fs/fuse/connections"), PathBuf::from("/boot/efi"), PathBuf::from("/mnt/data") ] ); } } proc-mounts-0.3.0/src/mounts/tab.rs000064400000000000000000000064160072674642500154010ustar 00000000000000use super::MountInfo; use std::{ fmt::{self, Display, Formatter}, io, ops::{Deref, DerefMut}, str::FromStr, }; /// An element in an abtract representation of the mount tab that was read into memory. #[derive(Clone, Debug, Eq, PartialEq)] pub enum AbstractMountElement { /// An element which is a comment Comment(String), /// An element which is an empty line. Empty, /// An element which defines a mount point Mount(MountInfo), } impl Display for AbstractMountElement { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { match self { AbstractMountElement::Comment(ref comment) => fmt.write_str(comment), AbstractMountElement::Empty => Ok(()), AbstractMountElement::Mount(ref entry) => fmt.write_fmt(format_args!("{}", entry)), } } } impl From for AbstractMountElement { fn from(comment: String) -> Self { AbstractMountElement::Comment(comment) } } impl From<()> for AbstractMountElement { fn from(_: ()) -> Self { AbstractMountElement::Empty } } impl From for AbstractMountElement { fn from(info: MountInfo) -> Self { AbstractMountElement::Mount(info) } } /// Provides an abstract representation of the contents of a mount tab. /// /// The use case for this type is to enable editing of the original file, or creating new copies, /// in a type-safe manner. Each element is an individual line from the original file. Elements /// may be inserted, removed, and replaced. #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct MountTab(pub Vec); impl MountTab { pub fn iter_mounts(&self) -> impl Iterator { self.0.iter().filter_map(|e| { if let AbstractMountElement::Mount(e) = e { Some(e) } else { None } }) } pub fn iter_mounts_mut(&mut self) -> impl Iterator { self.0.iter_mut().filter_map(|e| { if let AbstractMountElement::Mount(e) = e { Some(e) } else { None } }) } pub fn push>(&mut self, element: E) { self.0.push(element.into()); } } impl Deref for MountTab { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for MountTab { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl Display for MountTab { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { for entry in &self.0 { writeln!(fmt, "{}", entry)?; } Ok(()) } } impl FromStr for MountTab { type Err = io::Error; fn from_str(input: &str) -> Result { let mut entries = Vec::new(); for line in input.lines() { let line = line.trim_start(); if line.is_empty() { entries.push(AbstractMountElement::Empty); } else if line.starts_with('#') { entries.push(AbstractMountElement::Comment(line.to_owned())); } else { let info = line.parse::()?; entries.push(AbstractMountElement::Mount(info)); } } Ok(MountTab(entries)) } } proc-mounts-0.3.0/src/swaps.rs000064400000000000000000000140020072674642500144310ustar 00000000000000use std::{ char, ffi::OsString, fmt::{self, Display, Formatter}, fs::File, io::{self, BufRead, BufReader, Error, ErrorKind}, os::unix::ffi::OsStringExt, path::{Path, PathBuf}, str::FromStr, }; /// A swap entry, which defines an active swap. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct SwapInfo { /// The path where the swap originates from. pub source: PathBuf, /// The kind of swap, such as `partition` or `file`. pub kind: OsString, /// The size of the swap partition. pub size: usize, /// Whether the swap is used or not. pub used: usize, /// The priority of a swap, which indicates the order of usage. pub priority: isize, } impl Display for SwapInfo { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { write!( fmt, "{} {} {} {} {}", self.source.display(), self.kind.to_str().ok_or(fmt::Error)?, self.size, self.used, self.priority ) } } impl FromStr for SwapInfo { type Err = io::Error; fn from_str(line: &str) -> Result { let mut parts = line.split_whitespace(); fn parse(string: &OsString) -> io::Result { let string = string.to_str().ok_or_else(|| { Error::new(ErrorKind::InvalidData, "/proc/swaps contains non-UTF8 entry") })?; string.parse::().map_err(|_| { Error::new(ErrorKind::InvalidData, "/proc/swaps contains invalid data") }) } macro_rules! next_value { ($err:expr) => {{ parts .next() .ok_or_else(|| Error::new(ErrorKind::Other, $err)) .and_then(|val| Self::parse_value(val)) }}; } Ok(SwapInfo { source: PathBuf::from(next_value!("Missing source")?), kind: next_value!("Missing kind")?, size: parse::(&next_value!("Missing size")?)?, used: parse::(&next_value!("Missing used")?)?, priority: parse::(&next_value!("Missing priority")?)?, }) } } impl SwapInfo { // Attempt to parse a `/proc/swaps`-like line. #[deprecated] pub fn parse_line(line: &str) -> io::Result { line.parse::() } fn parse_value(value: &str) -> io::Result { let mut ret = Vec::new(); let mut bytes = value.bytes(); while let Some(b) = bytes.next() { match b { b'\\' => { let mut code = 0; for _i in 0..3 { if let Some(b) = bytes.next() { code *= 8; code += u32::from_str_radix(&(b as char).to_string(), 8) .map_err(|err| Error::new(ErrorKind::Other, err))?; } else { return Err(Error::new(ErrorKind::Other, "truncated octal code")); } } ret.push(code as u8); } _ => { ret.push(b); } } } Ok(OsString::from_vec(ret)) } } /// A list of parsed swap entries from `/proc/swaps`. #[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct SwapList(pub Vec); impl SwapList { pub fn parse_from<'a, I: Iterator>(lines: I) -> io::Result { lines.map(SwapInfo::from_str).collect::>>().map(SwapList) } pub fn new() -> io::Result { Ok(SwapList(SwapIter::new()?.collect::>>()?)) } pub fn new_from_file>(path: P) -> io::Result { Ok(SwapList(SwapIter::new_from_file(path)?.collect::>>()?)) } pub fn new_from_reader(reader: R) -> io::Result { Ok(SwapList(SwapIter::new_from_reader(reader)?.collect::>>()?)) } /// Returns true if the given path is a entry in the swap list. pub fn get_swapped(&self, path: &Path) -> bool { self.0.iter().any(|mount| mount.source == path) } } /// Iteratively parse the `/proc/swaps` file. pub struct SwapIter { file: R, buffer: String, } impl SwapIter> { pub fn new() -> io::Result { Self::new_from_file("/proc/swaps") } pub fn new_from_file>(path: P) -> io::Result { Self::new_from_reader(BufReader::new(File::open(path)?)) } } impl SwapIter { pub fn new_from_reader(mut reader: R) -> io::Result { let mut buffer = String::with_capacity(512); reader.read_line(&mut buffer)?; buffer.clear(); Ok(Self { file: reader, buffer }) } } impl Iterator for SwapIter { type Item = io::Result; fn next(&mut self) -> Option { self.buffer.clear(); match self.file.read_line(&mut self.buffer) { Ok(read) if read == 0 => None, Ok(_) => Some(SwapInfo::from_str(&self.buffer)), Err(why) => Some(Err(why)), } } } #[cfg(test)] mod tests { use super::*; use std::{ffi::OsString, path::PathBuf}; const SAMPLE: &str = r#"Filename Type Size Used Priority /dev/sda5 partition 8388600 0 -2"#; #[test] fn swaps() { let swaps = SwapList::parse_from(SAMPLE.lines().skip(1)).unwrap(); assert_eq!( swaps, SwapList(vec![SwapInfo { source: PathBuf::from("/dev/sda5"), kind: OsString::from("partition"), size: 8_388_600, used: 0, priority: -2, }]) ); assert!(swaps.get_swapped(Path::new("/dev/sda5"))); assert!(!swaps.get_swapped(Path::new("/dev/sda1"))); } }