1use crate::util::{read_to_string_mut, blkdev_sector_size};
4use crate::unit::DataSize;
5
6use std::path::Path;
7use std::{fs, io};
8use std::convert::TryInto;
9
10use byte_parser::{StrParser, ParseIterator, parse_iter};
11
12
13#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Partitions {
16 raw: String
17}
18
19impl Partitions {
20
21 fn path() -> &'static Path {
22 Path::new("/proc/partitions")
23 }
24
25 #[cfg(test)]
26 fn from_string(raw: String) -> Self {
27 Self {raw}
28 }
29
30 pub fn read() -> io::Result<Self> {
32 Ok(Self {
33 raw: fs::read_to_string(Self::path())?
34 })
35 }
36
37 pub fn reload(&mut self) -> io::Result<()> {
39 read_to_string_mut(Self::path(), &mut self.raw)
40 }
41
42 pub fn entries<'a>(&'a self) -> impl Iterator<Item=PartitionEntry<'a>> {
43 self.raw.trim()
44 .split('\n')
45 .skip(2).map(PartitionEntry::from_str)
47 }
48
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct PartitionEntry<'a> {
53 raw: &'a str
54}
55
56impl<'a> PartitionEntry<'a> {
57
58 fn from_str(raw: &'a str) -> Self {
59 Self {raw}
60 }
61
62 pub fn values(&self) -> impl Iterator<Item=&'a str> {
64 self.raw.split(' ')
65 .map(str::trim)
66 .filter(|s| !s.is_empty())
67 }
68
69 pub fn major(&self) -> Option<usize> {
71 self.values().nth(0)?
72 .parse().ok()
73 }
74
75 pub fn minor(&self) -> Option<usize> {
77 self.values().nth(1)?
78 .parse().ok()
79 }
80
81 pub fn blocks(&self) -> Option<usize> {
83 self.values().nth(2)?
84 .parse().ok()
85 }
86
87 pub fn name(&self) -> Option<&'a str> {
89 self.values().nth(3)
90 }
91
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub struct MountPoints {
97 raw: String
98}
99
100impl MountPoints {
101
102 fn path() -> &'static Path {
103 Path::new("/proc/self/mountinfo")
104 }
105
106 #[cfg(test)]
107 fn from_string(raw: String) -> Self {
108 Self {raw}
109 }
110
111 pub fn read() -> io::Result<Self> {
113 Ok(Self {
114 raw: fs::read_to_string(Self::path())?
115 })
116 }
117
118 pub fn reload(&mut self) -> io::Result<()> {
120 read_to_string_mut(Self::path(), &mut self.raw)
121 }
122
123 pub fn points<'a>(&'a self) -> impl Iterator<Item=MountPoint<'a>> {
125 self.raw.trim()
126 .split('\n')
127 .map(MountPoint::from_str)
128 }
129
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct MountPoint<'a> {
134 raw: &'a str
135}
136
137impl<'a> MountPoint<'a> {
138
139 fn from_str(raw: &'a str) -> Self {
140 Self {raw}
141 }
142
143 #[inline]
145 pub fn values(&self) -> impl Iterator<Item=&'a str> {
146 self.raw.split(' ')
147 }
148
149 pub fn mount_id(&self) -> Option<usize> {
151 self.values().nth(0)?
152 .parse().ok()
153 }
154
155 pub fn parent_id(&self) -> Option<usize> {
158 self.values().nth(1)?
159 .parse().ok()
160 }
161
162 #[inline]
164 pub fn major_minor(&self) -> Option<&'a str> {
165 self.values().nth(2)
166 }
167
168 pub fn major(&self) -> Option<usize> {
170 self.major_minor()?
171 .split(':')
172 .nth(0)?
173 .parse().ok()
174 }
175
176 pub fn minor(&self) -> Option<usize> {
178 self.major_minor()?
179 .split(':')
180 .nth(1)?
181 .parse().ok()
182 }
183
184 pub fn root(&self) -> Option<&'a str> {
187 self.values().nth(3)
188 }
189
190 pub fn mount_point(&self) -> Option<&'a str> {
193 self.values().nth(4)
194 }
195
196 pub fn mount_options(&self) -> Option<&'a str> {
198 self.values().nth(5)
199 }
200
201 pub fn optional_fields(&self) -> impl Iterator<Item=(&'a str, Option<&'a str>)> {
204 self.values().skip(6)
205 .take_while(|&i| i != "-")
206 .map(|opt| {
207 let mut iters = opt.split(':');
208 (
209 iters.next().unwrap(),
210 iters.next()
211 )
214 })
215 }
216
217 fn after_separator(&self) -> impl Iterator<Item=&'a str> {
218 self.values().skip(5)
219 .skip_while(|&i| i != "-")
220 .skip(1)}
222
223 pub fn filesystem_type(&self) -> Option<&'a str> {
225 self.after_separator().nth(0)
227 }
228
229 pub fn mount_source(&self) -> Option<&'a str> {
234 self.after_separator().nth(1)
235 }
241
242 pub fn super_options(&self) -> Option<&'a str> {
244 self.after_separator().nth(2)
245 }
246
247 pub fn stats(&self) -> io::Result<FsStat> {
249 FsStat::read(self.mount_point().unwrap_or(""))
250 }
251
252}
253
254#[derive(Clone)]
256pub struct FsStat {
257 raw: libc::statfs
258}
259
260impl FsStat {
261
262 pub fn read(path: impl AsRef<Path>) -> io::Result<Self> {
265 crate::util::statfs(path)
266 .map(|raw| Self { raw })
267 }
268
269 pub fn has_blocks(&self) -> bool {
271 self.total_blocks()
272 .map(|b| b > 0)
273 .unwrap_or(false)
274 }
275
276 pub fn block_size(&self) -> Option<usize> {
278 self.raw.f_bsize.try_into().ok()
279 }
280
281 pub fn total_blocks(&self) -> Option<usize> {
283 self.raw.f_blocks.try_into().ok()
284 }
285
286 pub fn free_blocks(&self) -> Option<usize> {
289 self.raw.f_bfree.try_into().ok()
290 }
291
292 pub fn available_blocks(&self) -> Option<usize> {
295 self.raw.f_bavail.try_into().ok()
296 }
297
298 pub fn used_blocks(&self) -> Option<usize> {
300 Some(self.total_blocks()? - self.free_blocks()?)
301 }
302
303 pub fn total(&self) -> Option<DataSize> {
305 DataSize::from_size_bytes(self.total_blocks()? * self.block_size()?)
306 }
307
308 pub fn free(&self) -> Option<DataSize> {
310 DataSize::from_size_bytes(self.free_blocks()? * self.block_size()?)
311 }
312
313 pub fn available(&self) -> Option<DataSize> {
316 DataSize::from_size_bytes(self.available_blocks()? * self.block_size()?)
317 }
318
319 pub fn used(&self) -> Option<DataSize> {
322 DataSize::from_size_bytes(self.used_blocks()? * self.block_size()?)
323 }
324
325}
326
327#[derive(Debug, Clone, PartialEq, Eq)]
329pub struct Raids {
330 raw: String
331}
332
333impl Raids {
334
335 fn path() -> &'static Path {
336 Path::new("/proc/mdstat")
337 }
338
339 #[cfg(test)]
340 fn from_string(raw: String) -> Self {
341 Self {raw}
342 }
343
344 pub fn read() -> io::Result<Self> {
346 Ok(Self {
347 raw: fs::read_to_string(Self::path())?
348 })
349 }
350
351 pub fn reload(&mut self) -> io::Result<()> {
353 read_to_string_mut(Self::path(), &mut self.raw)
354 }
355
356 pub fn raids(&self) -> impl Iterator<Item=Raid<'_>> {
358 let mut first_line = false;
359 parse_iter(
360 StrParser::new(self.raw.trim()),
361 move |parser| {
362 if !first_line {
363 parser.consume_while_byte_fn(|&b| b != b'\n');
364 parser.advance();
366 first_line = true;
367 }
368 parser.peek()?;
369 let key = parser.record()
370 .while_byte_fn(|&b| b != b':')
371 .consume_to_str()
372 .trim();
373
374 if key == "unused devices" {
375 return None
376 }
377 parser.advance();
379
380 let mut parser = parser.record();
381 let mut one = false;
382
383 loop {
384
385 if one && matches!(parser.peek(), Some(b'\n')) {
386 let s = parser.to_str().trim();
388 parser.advance();
389 return Some(Raid::from_str(key, s))
390 }
391
392 if one {
393 one = false;
394 continue
395 }
396
397 match parser.next() {
398 Some(b'\n') => one = true,
399 None => {
400 return Some(Raid::from_str(key, parser.to_str().trim()))
402 },
403 _ => {}
404 }
405
406 }
407 }
408 )
409 }
410
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
416pub struct Raid<'a> {
417 name: &'a str,
418 raw: &'a str
419}
420
421impl<'a> Raid<'a> {
422
423 fn from_str(name: &'a str, raw: &'a str) -> Self {
424 Self {name, raw}
425 }
426
427 #[inline]
429 pub fn values(&self) -> impl Iterator<Item=impl Iterator<Item=&'a str>> {
430 self.raw.split('\n')
431 .map(str::trim)
432 .map(|l| l.split(' '))
433 }
434
435 pub fn name(&self) -> &'a str {
437 self.name
438 }
439
440 pub fn state(&self) -> Option<&'a str> {
442 self.values()
443 .nth(0)?
444 .nth(0)
445 }
446
447 fn line(&self, line: usize) -> impl Iterator<Item=&'a str> {
448 let mut iter = self.values().nth(line);
449 std::iter::from_fn(move || iter.as_mut()?.next())
450 }
451
452 pub fn kind(&self) -> Option<&'a str> {
455 self.line(0).nth(1)
456 }
457
458 pub fn devices(&self) -> impl Iterator<Item=(usize, &'a str)> {
460 self.line(0)
461 .skip(2)
462 .filter_map(|dev| {
463 let mut split = dev.split(&['[', ']'][..]);
464 let name = split.next()?;
465 Some((
466 split.next()?.parse().ok()?,
467 name
468 ))
469 })
470 }
471
472 pub fn usable_blocks(&self) -> Option<usize> {
474 self.line(1)
475 .nth(0)?
476 .parse().ok()
477 }
478
479 pub fn used_devices(&self) -> Option<usize> {
482 self.line(1)
483 .find(|l| l.starts_with('['))?
484 .split('/')
485 .nth(0)?
486 .strip_prefix('[')?
487 .parse().ok()
488 }
489
490 pub fn ideal_devices(&self) -> Option<usize> {
493 self.line(1)
494 .find(|l| l.starts_with('['))?
495 .split('/')
496 .nth(1)?
497 .strip_suffix(']')?
498 .parse().ok()
499 }
500
501 pub fn progress(&self) -> Option<&'a str> {
504 let l = self.raw.split('\n')
505 .nth(2)?
506 .trim();
507 l.starts_with('[')
508 .then(|| l)
509 }
510
511 pub fn stats(&self) -> io::Result<FsStat> {
513 FsStat::read(format!("/dev/{}", self.name()))
514 }
515
516}
517
518pub fn sector_size(path: impl AsRef<Path>) -> io::Result<u64> {
522 blkdev_sector_size(fs::File::open(path)?)
523}
524
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 fn partitions() -> Partitions {
531 Partitions::from_string("\
532major minor #blocks name
533
534 7 0 142152 loop0
535 7 1 101528 loop1
536 259 0 500107608 nvme0n1
537 259 1 510976 nvme0n1p1\n\
538 ".into())
539 }
540
541 fn cmp_entry(major: usize, minor: usize, blocks: usize, name: &str, e: &PartitionEntry<'_>) {
542 assert_eq!(e.major().unwrap(), major);
543 assert_eq!(e.minor().unwrap(), minor);
544 assert_eq!(e.blocks().unwrap(), blocks);
545 assert_eq!(e.name().unwrap(), name);
546 }
547
548 #[test]
549 fn all_partitions() {
550 let part = partitions();
551 let mut e = part.entries();
552 println!("e: {:?}", part.entries().collect::<Vec<_>>());
553 cmp_entry(7, 0, 142152, "loop0", &e.next().unwrap());
554 cmp_entry(7, 1, 101528, "loop1", &e.next().unwrap());
555 cmp_entry(259, 0, 500107608, "nvme0n1", &e.next().unwrap());
556 cmp_entry(259, 1, 510976, "nvme0n1p1", &e.next().unwrap());
557 assert!(e.next().is_none());
558 }
559
560 fn mount_points() -> MountPoints {
561 MountPoints::from_string("\
56226 29 0:5 / /dev rw,nosuid,noexec,relatime shared:2 - devtmpfs udev rw,size=8123832k,nr_inodes=2030958,mode=755
56327 26 0:24 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
56435 33 0:30 / /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime shared:11 other - cgroup cgroup rw,xattr,name=systemd
5652509 28 0:25 /snapd/ns /run/snapd/ns rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=1631264k,mode=755
5662893 2509 0:4 mnt:[4026532961] /run/snapd/ns/snap-store.mnt rw - nsfs nsfs rw\n\
567 ".into())
568 }
569
570 fn cmp_point(
571 mount_id: usize,
572 parent_id: usize,
573 major_minor: &str,
574 root: &str,
575 mount_point: &str,
576 mount_options: &str,
577 optional_fields: &[(&str, Option<&str>)],
578 filesystem_type: &str,
579 mount_source: &str,
580 super_options: &str,
581 point: &MountPoint<'_>
582 ) {
583 assert_eq!(point.mount_id().unwrap(), mount_id);
584 assert_eq!(point.parent_id().unwrap(), parent_id);
585 assert_eq!(point.major_minor().unwrap(), major_minor);
586 assert_eq!(point.root().unwrap(), root);
587 assert_eq!(point.mount_point().unwrap(), mount_point);
588 assert_eq!(point.mount_options().unwrap(), mount_options);
589 assert_eq!(point.optional_fields().collect::<Vec<_>>(), optional_fields);
590 assert_eq!(point.filesystem_type().unwrap(), filesystem_type);
591 assert_eq!(point.mount_source().unwrap(), mount_source);
592 assert_eq!(point.super_options().unwrap(), super_options);
593 }
594
595 #[test]
596 fn all_mount_points() {
597 let mt = mount_points();
598 let mut mt = mt.points();
599 cmp_point(
600 26, 29, "0:5", "/", "/dev", "rw,nosuid,noexec,relatime",
601 &[("shared", Some("2"))], "devtmpfs", "udev",
602 "rw,size=8123832k,nr_inodes=2030958,mode=755",
603 &mt.next().unwrap()
604 );
605 cmp_point(
606 27, 26, "0:24", "/", "/dev/pts", "rw,nosuid,noexec,relatime",
607 &[("shared", Some("3"))], "devpts", "devpts",
608 "rw,gid=5,mode=620,ptmxmode=000",
609 &mt.next().unwrap()
610 );
611 cmp_point(
612 35, 33, "0:30", "/", "/sys/fs/cgroup/systemd", "rw,nosuid,nodev,noexec,relatime",
613 &[("shared", Some("11")), ("other", None)], "cgroup", "cgroup", "rw,xattr,name=systemd",
614 &mt.next().unwrap()
615 );
616 cmp_point(
617 2509, 28, "0:25", "/snapd/ns", "/run/snapd/ns", "rw,nosuid,nodev,noexec,relatime",
618 &[], "tmpfs", "tmpfs", "rw,size=1631264k,mode=755",
619 &mt.next().unwrap()
620 );
621 cmp_point(
622 2893, 2509, "0:4", "mnt:[4026532961]", "/run/snapd/ns/snap-store.mnt", "rw",
623 &[], "nsfs", "nsfs", "rw",
624 &mt.next().unwrap()
625 );
626 }
627
628 #[test]
629 fn raid_case_1() {
630 let raids = Raids::from_string("\
631Personalities : [raid1] [linear] [multipath] [raid0] [raid6] [raid5] [raid4] [raid10]
632md10 : active raid1 sdd[0] sdc[1]
633 3906886464 blocks super 1.2 [2/2] [UU]
634 bitmap: 0/30 pages [0KB], 65536KB chunk
635
636md0 : active raid1 sdb[1] sda[0]
637 499975488 blocks super 1.2 [2/2] [UU]
638 bitmap: 3/4 pages [12KB], 65536KB chunk
639
640unused devices: <none>\n".into());
641 assert_eq!(raids.raids().count(), 2);
642 let first = raids.raids().next().unwrap();
643 assert_eq!(first.name(), "md10");
644 assert_eq!(first.used_devices().unwrap(), 2);
645 assert_eq!(first.ideal_devices().unwrap(), 2);
646 assert!(first.progress().is_none());
647 assert_eq!(first.devices().count(), first.used_devices().unwrap());
648 }
649
650 #[test]
651 fn raid_case_2() {
652 let raids = Raids::from_string("\
653Personalities : [raid1] [raid6] [raid5] [raid4]
654md127 : active raid5 sdh1[6] sdg1[4] sdf1[3] sde1[2] sdd1[1] sdc1[0]
655 1464725760 blocks level 5, 64k chunk, algorithm 2 [6/5] [UUUUU_]
656 [==>..................] recovery = 12.6% (37043392/292945152) finish=127.5min speed=33440K/sec
657
658unused devices: <none>\n".into());
659 assert_eq!(raids.raids().count(), 1);
660 let first = raids.raids().next().unwrap();
661 let comp_dev: Vec<_> = first.devices().collect();
662 assert_eq!(comp_dev, [(6, "sdh1"), (4, "sdg1"), (3, "sdf1"), (2, "sde1"), (1, "sdd1"), (0, "sdc1")]);
663 assert_eq!(first.kind().unwrap(), "raid5");
664 assert_eq!(first.usable_blocks().unwrap(), 1464725760);
665 assert_eq!(first.used_devices().unwrap(), 6);
666 assert_eq!(first.ideal_devices().unwrap(), 5);
667 assert_eq!(first.progress().unwrap(), "[==>..................] recovery = 12.6% (37043392/292945152) finish=127.5min speed=33440K/sec");
668 assert_eq!(first.devices().count(), first.used_devices().unwrap());
669 }
670
671 #[test]
672 fn raid_case_3() {
673 let raids = Raids::from_string("\
674Personalities : [linear] [raid0] [raid1] [raid5] [raid4] [raid6]
675md0 : active raid6 sdf1[0] sde1[1] sdd1[2] sdc1[3] sdb1[4] sda1[5] hdb1[6]
676 1225557760 blocks level 6, 256k chunk, algorithm 2 [7/7] [UUUUUUU]
677 bitmap: 0/234 pages [0KB], 512KB chunk
678
679unused devices: <none>\n".into());
680 assert_eq!(raids.raids().count(), 1);
681 let first = raids.raids().next().unwrap();
682 assert_eq!(first.devices().count(), first.used_devices().unwrap());
683 }
684
685}
686
687