proc_mounts/
swaps.rs

1use std::{
2    char,
3    ffi::OsString,
4    fmt::{self, Display, Formatter},
5    fs::File,
6    io::{self, BufRead, BufReader, Error, ErrorKind},
7    os::unix::ffi::OsStringExt,
8    path::{Path, PathBuf},
9    str::FromStr,
10};
11
12/// A swap entry, which defines an active swap.
13#[derive(Debug, Clone, Hash, Eq, PartialEq)]
14pub struct SwapInfo {
15    /// The path where the swap originates from.
16    pub source: PathBuf,
17    /// The kind of swap, such as `partition` or `file`.
18    pub kind: OsString,
19    /// The size of the swap partition.
20    pub size: usize,
21    /// Whether the swap is used or not.
22    pub used: usize,
23    /// The priority of a swap, which indicates the order of usage.
24    pub priority: isize,
25}
26
27impl Display for SwapInfo {
28    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
29        write!(
30            fmt,
31            "{} {} {} {} {}",
32            self.source.display(),
33            self.kind.to_str().ok_or(fmt::Error)?,
34            self.size,
35            self.used,
36            self.priority
37        )
38    }
39}
40
41impl FromStr for SwapInfo {
42    type Err = io::Error;
43
44    fn from_str(line: &str) -> Result<Self, Self::Err> {
45        let mut parts = line.split_whitespace();
46
47        fn parse<F: FromStr>(string: &OsString) -> io::Result<F> {
48            let string = string.to_str().ok_or_else(|| {
49                Error::new(ErrorKind::InvalidData, "/proc/swaps contains non-UTF8 entry")
50            })?;
51
52            string.parse::<F>().map_err(|_| {
53                Error::new(ErrorKind::InvalidData, "/proc/swaps contains invalid data")
54            })
55        }
56
57        macro_rules! next_value {
58            ($err:expr) => {{
59                parts
60                    .next()
61                    .ok_or_else(|| Error::new(ErrorKind::Other, $err))
62                    .and_then(|val| Self::parse_value(val))
63            }};
64        }
65
66        Ok(SwapInfo {
67            source:   PathBuf::from(next_value!("Missing source")?),
68            kind:     next_value!("Missing kind")?,
69            size:     parse::<usize>(&next_value!("Missing size")?)?,
70            used:     parse::<usize>(&next_value!("Missing used")?)?,
71            priority: parse::<isize>(&next_value!("Missing priority")?)?,
72        })
73    }
74}
75
76impl SwapInfo {
77    // Attempt to parse a `/proc/swaps`-like line.
78    #[deprecated]
79    pub fn parse_line(line: &str) -> io::Result<SwapInfo> { line.parse::<Self>() }
80
81    fn parse_value(value: &str) -> io::Result<OsString> {
82        let mut ret = Vec::new();
83
84        let mut bytes = value.bytes();
85        while let Some(b) = bytes.next() {
86            match b {
87                b'\\' => {
88                    let mut code = 0;
89                    for _i in 0..3 {
90                        if let Some(b) = bytes.next() {
91                            code *= 8;
92                            code += u32::from_str_radix(&(b as char).to_string(), 8)
93                                .map_err(|err| Error::new(ErrorKind::Other, err))?;
94                        } else {
95                            return Err(Error::new(ErrorKind::Other, "truncated octal code"));
96                        }
97                    }
98                    ret.push(code as u8);
99                }
100                _ => {
101                    ret.push(b);
102                }
103            }
104        }
105
106        Ok(OsString::from_vec(ret))
107    }
108}
109
110/// A list of parsed swap entries from `/proc/swaps`.
111#[derive(Debug, Clone, Hash, Eq, PartialEq)]
112pub struct SwapList(pub Vec<SwapInfo>);
113
114impl SwapList {
115    pub fn parse_from<'a, I: Iterator<Item = &'a str>>(lines: I) -> io::Result<SwapList> {
116        lines.map(SwapInfo::from_str).collect::<io::Result<Vec<SwapInfo>>>().map(SwapList)
117    }
118
119    pub fn new() -> io::Result<SwapList> {
120        Ok(SwapList(SwapIter::new()?.collect::<io::Result<Vec<SwapInfo>>>()?))
121    }
122
123    pub fn new_from_file<P: AsRef<Path>>(path: P) -> io::Result<SwapList> {
124        Ok(SwapList(SwapIter::new_from_file(path)?.collect::<io::Result<Vec<SwapInfo>>>()?))
125    }
126
127    pub fn new_from_reader<R: BufRead>(reader: R) -> io::Result<SwapList> {
128        Ok(SwapList(SwapIter::new_from_reader(reader)?.collect::<io::Result<Vec<SwapInfo>>>()?))
129    }
130
131    /// Returns true if the given path is a entry in the swap list.
132    pub fn get_swapped(&self, path: &Path) -> bool {
133        self.0.iter().any(|mount| mount.source == path)
134    }
135}
136
137/// Iteratively parse the `/proc/swaps` file.
138pub struct SwapIter<R: BufRead> {
139    file:   R,
140    buffer: String,
141}
142
143impl SwapIter<BufReader<File>> {
144    pub fn new() -> io::Result<Self> { Self::new_from_file("/proc/swaps") }
145
146    pub fn new_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
147        Self::new_from_reader(BufReader::new(File::open(path)?))
148    }
149}
150
151impl<R: BufRead> SwapIter<R> {
152    pub fn new_from_reader(mut reader: R) -> io::Result<Self> {
153        let mut buffer = String::with_capacity(512);
154        reader.read_line(&mut buffer)?;
155        buffer.clear();
156
157        Ok(Self { file: reader, buffer })
158    }
159}
160
161impl<R: BufRead> Iterator for SwapIter<R> {
162    type Item = io::Result<SwapInfo>;
163
164    fn next(&mut self) -> Option<Self::Item> {
165        self.buffer.clear();
166        match self.file.read_line(&mut self.buffer) {
167            Ok(read) if read == 0 => None,
168            Ok(_) => Some(SwapInfo::from_str(&self.buffer)),
169            Err(why) => Some(Err(why)),
170        }
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177    use std::{ffi::OsString, path::PathBuf};
178
179    const SAMPLE: &str = r#"Filename				Type		Size	Used	Priority
180/dev/sda5                               partition	8388600	0	-2"#;
181
182    #[test]
183    fn swaps() {
184        let swaps = SwapList::parse_from(SAMPLE.lines().skip(1)).unwrap();
185        assert_eq!(
186            swaps,
187            SwapList(vec![SwapInfo {
188                source:   PathBuf::from("/dev/sda5"),
189                kind:     OsString::from("partition"),
190                size:     8_388_600,
191                used:     0,
192                priority: -2,
193            }])
194        );
195
196        assert!(swaps.get_swapped(Path::new("/dev/sda5")));
197        assert!(!swaps.get_swapped(Path::new("/dev/sda1")));
198    }
199}