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#[derive(Debug, Clone, Hash, Eq, PartialEq)]
14pub struct SwapInfo {
15 pub source: PathBuf,
17 pub kind: OsString,
19 pub size: usize,
21 pub used: usize,
23 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 #[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#[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 pub fn get_swapped(&self, path: &Path) -> bool {
133 self.0.iter().any(|mount| mount.source == path)
134 }
135}
136
137pub 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}