sysinfo/unix/linux/
disk.rs1use crate::sys::utils::{get_all_data, to_cpath};
4use crate::{Disk, DiskKind};
5
6use libc::statvfs;
7use std::ffi::{OsStr, OsString};
8use std::fs;
9use std::mem;
10use std::os::unix::ffi::OsStrExt;
11use std::path::{Path, PathBuf};
12
13macro_rules! cast {
14 ($x:expr) => {
15 u64::from($x)
16 };
17}
18
19pub(crate) struct DiskInner {
20 type_: DiskKind,
21 device_name: OsString,
22 file_system: OsString,
23 mount_point: PathBuf,
24 total_space: u64,
25 available_space: u64,
26 is_removable: bool,
27}
28
29impl DiskInner {
30 pub(crate) fn kind(&self) -> DiskKind {
31 self.type_
32 }
33
34 pub(crate) fn name(&self) -> &OsStr {
35 &self.device_name
36 }
37
38 pub(crate) fn file_system(&self) -> &OsStr {
39 &self.file_system
40 }
41
42 pub(crate) fn mount_point(&self) -> &Path {
43 &self.mount_point
44 }
45
46 pub(crate) fn total_space(&self) -> u64 {
47 self.total_space
48 }
49
50 pub(crate) fn available_space(&self) -> u64 {
51 self.available_space
52 }
53
54 pub(crate) fn is_removable(&self) -> bool {
55 self.is_removable
56 }
57
58 pub(crate) fn refresh(&mut self) -> bool {
59 unsafe {
60 let mut stat: statvfs = mem::zeroed();
61 let mount_point_cpath = to_cpath(&self.mount_point);
62 if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 {
63 let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail));
64 self.available_space = cast!(tmp);
65 true
66 } else {
67 false
68 }
69 }
70 }
71}
72
73impl crate::DisksInner {
74 pub(crate) fn new() -> Self {
75 Self {
76 disks: Vec::with_capacity(2),
77 }
78 }
79
80 pub(crate) fn refresh_list(&mut self) {
81 get_all_list(
82 &mut self.disks,
83 &get_all_data("/proc/mounts", 16_385).unwrap_or_default(),
84 )
85 }
86
87 pub(crate) fn list(&self) -> &[Disk] {
88 &self.disks
89 }
90
91 pub(crate) fn list_mut(&mut self) -> &mut [Disk] {
92 &mut self.disks
93 }
94}
95
96fn new_disk(
97 device_name: &OsStr,
98 mount_point: &Path,
99 file_system: &OsStr,
100 removable_entries: &[PathBuf],
101) -> Option<Disk> {
102 let mount_point_cpath = to_cpath(mount_point);
103 let type_ = find_type_for_device_name(device_name);
104 let mut total = 0;
105 let mut available = 0;
106 unsafe {
107 let mut stat: statvfs = mem::zeroed();
108 if retry_eintr!(statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat)) == 0 {
109 let bsize = cast!(stat.f_bsize);
110 let blocks = cast!(stat.f_blocks);
111 let bavail = cast!(stat.f_bavail);
112 total = bsize.saturating_mul(blocks);
113 available = bsize.saturating_mul(bavail);
114 }
115 if total == 0 {
116 return None;
117 }
118 let mount_point = mount_point.to_owned();
119 let is_removable = removable_entries
120 .iter()
121 .any(|e| e.as_os_str() == device_name);
122 Some(Disk {
123 inner: DiskInner {
124 type_,
125 device_name: device_name.to_owned(),
126 file_system: file_system.to_owned(),
127 mount_point,
128 total_space: cast!(total),
129 available_space: cast!(available),
130 is_removable,
131 },
132 })
133 }
134}
135
136#[allow(clippy::manual_range_contains)]
137fn find_type_for_device_name(device_name: &OsStr) -> DiskKind {
138 let device_name_path = device_name.to_str().unwrap_or_default();
149 let real_path = fs::canonicalize(device_name).unwrap_or_else(|_| PathBuf::from(device_name));
150 let mut real_path = real_path.to_str().unwrap_or_default();
151 if device_name_path.starts_with("/dev/mapper/") {
152 if real_path != device_name_path {
154 return find_type_for_device_name(OsStr::new(&real_path));
155 }
156 } else if device_name_path.starts_with("/dev/sd") || device_name_path.starts_with("/dev/vd") {
157 real_path = real_path.trim_start_matches("/dev/");
159 real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9');
160 } else if device_name_path.starts_with("/dev/nvme") {
161 real_path = match real_path.find('p') {
163 Some(idx) => &real_path["/dev/".len()..idx],
164 None => &real_path["/dev/".len()..],
165 };
166 } else if device_name_path.starts_with("/dev/root") {
167 if real_path != device_name_path {
169 return find_type_for_device_name(OsStr::new(&real_path));
170 }
171 } else if device_name_path.starts_with("/dev/mmcblk") {
172 real_path = match real_path.find('p') {
174 Some(idx) => &real_path["/dev/".len()..idx],
175 None => &real_path["/dev/".len()..],
176 };
177 } else {
178 real_path = real_path.trim_start_matches("/dev/");
181 }
182
183 let trimmed: &OsStr = OsStrExt::from_bytes(real_path.as_bytes());
184
185 let path = Path::new("/sys/block/")
186 .to_owned()
187 .join(trimmed)
188 .join("queue/rotational");
189 match get_all_data(path, 8)
191 .unwrap_or_default()
192 .trim()
193 .parse()
194 .ok()
195 {
196 Some(1) => DiskKind::HDD,
198 Some(0) => DiskKind::SSD,
200 Some(x) => DiskKind::Unknown(x),
202 None => DiskKind::Unknown(-1),
204 }
205}
206
207fn get_all_list(container: &mut Vec<Disk>, content: &str) {
208 container.clear();
209 let removable_entries = match fs::read_dir("/dev/disk/by-id/") {
212 Ok(r) => r
213 .filter_map(|res| Some(res.ok()?.path()))
214 .filter_map(|e| {
215 if e.file_name()
216 .and_then(|x| Some(x.to_str()?.starts_with("usb-")))
217 .unwrap_or_default()
218 {
219 e.canonicalize().ok()
220 } else {
221 None
222 }
223 })
224 .collect::<Vec<PathBuf>>(),
225 _ => Vec::new(),
226 };
227
228 for disk in content
229 .lines()
230 .map(|line| {
231 let line = line.trim_start();
232 let mut fields = line.split_whitespace();
236 let fs_spec = fields.next().unwrap_or("");
237 let fs_file = fields
238 .next()
239 .unwrap_or("")
240 .replace("\\134", "\\")
241 .replace("\\040", " ")
242 .replace("\\011", "\t")
243 .replace("\\012", "\n");
244 let fs_vfstype = fields.next().unwrap_or("");
245 (fs_spec, fs_file, fs_vfstype)
246 })
247 .filter(|(fs_spec, fs_file, fs_vfstype)| {
248 let filtered = match *fs_vfstype {
250 "rootfs" | "sysfs" | "proc" | "devtmpfs" |
254 "cgroup" |
255 "cgroup2" |
256 "pstore" | "squashfs" | "rpc_pipefs" | "iso9660" => true,
261 "tmpfs" => !cfg!(feature = "linux-tmpfs"),
262 "cifs" | "nfs" | "nfs4" => !cfg!(feature = "linux-netdevs"),
264 _ => false,
265 };
266
267 !(filtered ||
268 fs_file.starts_with("/sys") || fs_file.starts_with("/proc") ||
270 (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) ||
271 fs_spec.starts_with("sunrpc"))
272 })
273 .filter_map(|(fs_spec, fs_file, fs_vfstype)| {
274 new_disk(
275 fs_spec.as_ref(),
276 Path::new(&fs_file),
277 fs_vfstype.as_ref(),
278 &removable_entries,
279 )
280 })
281 {
282 container.push(disk);
283 }
284}
285
286