1use anyhow::bail;
3use anyhow::Result;
4use glob::glob;
5use log::{debug, trace, warn};
6use proc_mounts::{MountInfo, MountList, SwapIter};
7use scan_fmt::scan_fmt;
8use std::ffi::{OsStr, OsString};
9use std::fs;
10use std::io::Read;
11use std::os::linux::fs::MetadataExt;
12use std::path::{Path, PathBuf};
13use std::process::Command;
14
15lazy_static::lazy_static! {
16 static ref MOUNT_LIST: Result<MountList> = Ok(MountList::new()?);
17 static ref FINDMNT_BIN: String = super::find_bin("findmnt", Option::<&str>::None)
18 .expect("Failed to find findmnt bin")
19 .to_str()
20 .unwrap()
21 .to_owned();
22}
23
24pub fn path_to_mountpoint<P: AsRef<Path>>(path_in: P) -> Result<MountInfo> {
26 let path = path_in.as_ref();
27 let mut abs_path = fs::canonicalize(&path)?;
28 let mounts = match MOUNT_LIST.as_ref() {
29 Ok(v) => v,
30 Err(e) => bail!("Failed to list mount points ({:?})", &e),
31 };
32
33 loop {
34 if let Some(mi) = mounts.get_mount_by_dest(&abs_path) {
35 debug!("found mount point {:?} for {:?}", &mi.dest, &path);
36 return Ok(mi.clone());
37 }
38 if !abs_path.pop() {
39 bail!("Failed to find mount point for {:?}", path);
40 }
41 }
42}
43
44fn match_devnr<P: AsRef<Path>>(path_in: P, devnr: u64) -> bool {
45 let path = path_in.as_ref();
46 let mut buf = String::new();
47 if let Err(err) = fs::File::open(&path).and_then(|mut f| f.read_to_string(&mut buf)) {
48 warn!("Failed to open {:?} ({:?})", &path, &err);
49 return false;
50 }
51
52 trace!("matching {:?} content '{}'", &path, buf.trim());
53 let (maj, min) = match scan_fmt!(&buf, "{d}:{d}", u32, u32) {
54 Ok(v) => v,
55 Err(e) => {
56 warn!("Failed to parse '{}' ({:?})", buf.trim(), &e);
57 return false;
58 }
59 };
60
61 if devnr == libc::makedev(maj, min) {
62 trace!("matched {:?}", &path);
63 return true;
64 }
65 return false;
66}
67
68fn devnr_to_devname(devnr: u64) -> Result<OsString> {
70 let blk_dir = Path::new("/sys/block");
71 let blk_iter = blk_dir.read_dir()?;
72
73 for blk_path in blk_iter.filter_map(|r| r.ok()).map(|e| e.path()) {
74 let mut dev_path = PathBuf::from(&blk_path);
75
76 dev_path.push("dev");
77 if match_devnr(&dev_path, devnr) {
78 dev_path.pop();
79 return Ok(dev_path.file_name().unwrap().into());
80 }
81 dev_path.pop();
82
83 let pattern = format!(
84 "{}/{}*/dev",
85 dev_path.to_str().unwrap().to_string(),
86 dev_path.file_name().unwrap().to_str().unwrap()
87 );
88 for part in glob(&pattern).unwrap().filter_map(|x| x.ok()) {
89 if match_devnr(&part, devnr) {
90 return Ok(dev_path.file_name().unwrap().into());
91 }
92 }
93 }
94 bail!("No matching dev for 0x{:x}", devnr);
95}
96
97pub fn devname_to_devnr<D: AsRef<OsStr>>(name_in: D) -> Result<(u32, u32)> {
99 let mut path = PathBuf::from("/dev");
100 path.push(name_in.as_ref());
101 let rdev = fs::metadata(path)?.st_rdev();
102 Ok((unsafe { libc::major(rdev) }, unsafe { libc::minor(rdev) }))
103}
104
105pub fn path_to_devname<P: AsRef<Path>>(path: P) -> Result<OsString> {
107 let mtp = path_to_mountpoint(path.as_ref())?;
108
109 let output = Command::new(&*FINDMNT_BIN)
116 .arg("-no")
117 .arg("SOURCE")
118 .arg(&mtp.dest)
119 .output()?;
120
121 if !output.status.success() {
122 bail!("findmnt on {:?} failed with {:?}", &mtp.dest, &output);
123 }
124
125 let source = String::from_utf8(output.stdout).unwrap();
126 trace!("path_to_devname path={:?} source={:?}", path.as_ref(), source.trim());
127 devnr_to_devname(fs::metadata(source.trim())?.st_rdev())
128}
129
130fn read_model(dev_path: &Path) -> Result<String> {
131 let mut path = PathBuf::from(dev_path);
132 path.push("device");
133 path.push("model");
134
135 let mut model = String::new();
136 let mut f = fs::File::open(&path)?;
137 f.read_to_string(&mut model)?;
138 Ok(model.trim_end().to_string())
139}
140
141fn read_fwrev(dev_path: &Path) -> Result<String> {
142 let mut path = PathBuf::from(dev_path);
143 path.push("device");
144 path.push("firmware_rev");
145
146 let mut fwrev = String::new();
147 trace!("trying {:?}", &path);
148 if path.exists() {
149 let mut f = fs::File::open(&path)?;
150 f.read_to_string(&mut fwrev)?;
151 } else {
152 path.pop();
153 path.push("rev");
154 trace!("trying {:?}", &path);
155 if path.exists() {
156 let mut f = fs::File::open(&path)?;
157 f.read_to_string(&mut fwrev)?;
158 } else {
159 bail!("neither \"firmware_dev\" or \"rev\" is found");
160 }
161 }
162 Ok(fwrev.trim_end().to_string())
163}
164
165pub fn devname_to_model_fwrev_size<D: AsRef<OsStr>>(name_in: D) -> Result<(String, String, u64)> {
167 let unknown = "<UNKNOWN>";
168 let mut dev_path = PathBuf::from("/sys/block");
169 dev_path.push(name_in.as_ref());
170
171 let model = match read_model(&dev_path) {
172 Ok(v) => v,
173 Err(e) => {
174 warn!(
175 "storage_info: Failed to read model string for {:?} ({:#}), \
176 dm / md devices are not supported",
177 &dev_path, &e
178 );
179 unknown.to_string()
180 }
181 };
182
183 let fwrev = match read_fwrev(&dev_path) {
184 Ok(v) => v,
185 Err(e) => {
186 warn!(
187 "storage_info: Failed to read firmware revision for {:?} ({:#}), \
188 dm / md devices are not supported",
189 &dev_path, &e
190 );
191 unknown.to_string()
192 }
193 };
194
195 let mut size_path = dev_path.clone();
196 size_path.push("size");
197
198 let mut size = String::new();
199 let mut f = fs::File::open(&size_path)?;
200 f.read_to_string(&mut size)?;
201 let size = size.trim().parse::<u64>()? * 512;
202
203 Ok((model, fwrev, size))
204}
205
206pub fn swap_devnames() -> Result<Vec<OsString>> {
208 let mut devnames = Vec::new();
209 for swap in SwapIter::new()?.filter_map(|sw| sw.ok()) {
210 if swap.kind == "partition" {
211 devnames.push(devnr_to_devname(fs::metadata(&swap.source)?.st_rdev())?);
212 } else {
213 devnames.push(path_to_devname(&swap.source)?);
214 }
215 }
216 Ok(devnames)
217}
218
219pub fn is_devname_rotational<P: AsRef<OsStr>>(devname: P) -> Result<bool> {
221 let mut sysblk_path = PathBuf::from("/sys/block");
222 sysblk_path.push(devname.as_ref());
223 sysblk_path.push("queue");
224 sysblk_path.push("rotational");
225
226 let mut buf = String::new();
227 fs::File::open(&sysblk_path).and_then(|mut f| f.read_to_string(&mut buf))?;
228
229 trace!("read {:?} content '{}'", &sysblk_path, buf.trim());
230 match scan_fmt!(&buf, "{d}", u32) {
231 Ok(v) => Ok(v != 0),
232 Err(e) => bail!("parse error: '{}' ({:?})", &buf, &e),
233 }
234}
235
236pub fn is_path_rotational<P: AsRef<Path>>(path_in: P) -> bool {
238 let path = path_in.as_ref();
239
240 let devname = match path_to_devname(path) {
241 Ok(v) => v,
242 Err(e) => {
243 warn!(
244 "Failed to determine device name for {:?} ({:?}), assuming SSD",
245 path, &e
246 );
247 return false;
248 }
249 };
250
251 match is_devname_rotational(&devname) {
252 Ok(v) => v,
253 Err(e) => {
254 warn!(
255 "Failed to determine whether {:?} is rotational ({:?}), assuming SSD",
256 &path, &e
257 );
258 false
259 }
260 }
261}
262
263pub fn is_swap_rotational() -> bool {
265 match swap_devnames() {
266 Ok(devnames) => {
267 debug!("swap devices: {:?}", &devnames);
268 for devname in devnames {
269 match is_devname_rotational(&devname) {
270 Ok(true) => {
271 debug!("swap device {:?} is rotational", &devname);
272 return true;
273 }
274 Ok(false) => debug!("swap device {:?} is not rotational", &devname),
275 Err(e) => warn!(
276 "Failed to determine whether {:?} is rotational ({:?})",
277 &devname, &e
278 ),
279 }
280 }
281 }
282 Err(e) => warn!("Failed to determine swap devices ({:?}), assuming SSD", &e),
283 }
284 false
285}
286
287#[cfg(test)]
288mod tests {
289 use std::env;
290
291 #[test]
292 fn test() {
293 let _ = ::env_logger::try_init();
294 if let Ok(v) = env::var("ROTATIONAL_TARGET") {
295 println!("{} rotational={}", &v, super::is_path_rotational(&v));
296 }
297 println!("CWD rotational={}", super::is_path_rotational("."));
298 println!("swap rotational={}", super::is_swap_rotational());
299 }
300}