1use anyhow::{Result, anyhow};
24use serde::{Deserialize, Serialize};
25use std::fs::read_to_string;
26use std::path::Path;
27
28use crate::traits::ToJson;
29use crate::utils::Size;
30
31#[derive(Debug, Deserialize, Serialize, Clone)]
45pub struct Partitions {
46 pub parts: Vec<Partition>,
47}
48
49impl Partitions {
50 pub fn new() -> Result<Self> {
51 let contents = read_to_string("/proc/partitions")?;
52 Self::from_str(&contents)
53 }
54
55 fn from_str(s: &str) -> Result<Self> {
56 let lines = s.lines().skip(1).filter(|s| {
57 !s.is_empty() && !s.starts_with('m') && !s.contains("loop") && !s.contains("ram")
58 });
59
60 let mut parts = Vec::new();
61 for line in lines {
62 match Partition::try_from(line) {
63 Ok(part) => parts.push(part),
64 Err(why) => return Err(anyhow!("{why}")),
65 }
66 }
67
68 Ok(Self { parts })
69 }
70}
71
72impl ToJson for Partitions {}
73
74#[derive(Debug, Deserialize, Serialize, Clone)]
75pub struct Partition {
76 pub major: usize,
77 pub minor: usize,
78 pub blocks: usize,
79 pub name: String,
80 pub dev_info: DeviceInfo,
81}
82
83impl Partition {
84 pub fn get_logical_size(&self) -> Option<Size> {
85 let lbsize = self.dev_info.logical_block_size;
86 match lbsize {
87 Some(lbsize) => {
88 let blocks = self.blocks;
89 Some(Size::B(blocks * lbsize))
90 }
91 None => None,
92 }
93 }
94}
95
96impl TryFrom<&str> for Partition {
97 type Error = String;
98 fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
99 let mut chs = value.split_whitespace();
100
101 match (chs.next(), chs.next(), chs.next(), chs.next()) {
102 (Some(major), Some(minor), Some(blocks), Some(name)) => {
103 let major = major.parse::<usize>().map_err(|err| format!("{err}"))?;
104 let minor = minor.parse::<usize>().map_err(|err| format!("{err}"))?;
105 let blocks = blocks.parse::<usize>().map_err(|err| format!("{err}"))?;
106
107 Ok(Self {
108 major,
109 minor,
110 blocks,
111 name: name.to_string(),
112 dev_info: DeviceInfo::get(name),
113 })
114 }
115 _ => Err(format!("String '{value}' parsing error")),
116 }
117 }
118}
119
120#[derive(Debug, Deserialize, Serialize, Clone)]
121pub struct DeviceInfo {
122 pub model: Option<String>,
123 pub vendor: Option<String>,
124 pub serial: Option<String>,
125 pub logical_block_size: Option<usize>,
126}
127
128impl DeviceInfo {
129 pub fn get(devname: &str) -> Self {
130 let path = Path::new("/sys/block/").join(devname);
131 let device = path.join("device");
132 let queue = path.join("queue");
133
134 let model = device.join("model");
135 let vendor = device.join("vendor");
136 let serial = device.join("serial");
137
138 let logical_block_size = queue.join("logical_block_size");
139 let logical_block_size = match read_to_string(logical_block_size) {
140 Ok(lbs) => lbs.trim().parse::<usize>().ok(),
141 Err(_) => None,
142 };
143
144 Self {
145 model: read_to_string(model).ok(),
146 vendor: read_to_string(vendor).ok(),
147 serial: read_to_string(serial).ok(),
148 logical_block_size,
149 }
150 }
151}
152
153#[derive(Debug, Deserialize, Serialize, Clone)]
154pub struct FileSystemStats {
155 pub total: usize,
156 pub used: usize,
157 pub free: usize,
158 pub usage_percentage: f32,
159}
160
161impl FileSystemStats {}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 const PARTITIONS: &str = "major minor #blocks name
168
169 259 0 250059096 nvme0n1
170 259 1 102400 nvme0n1p1
171 259 2 16384 nvme0n1p2
172 259 3 249068548 nvme0n1p3
173 259 4 866304 nvme0n1p4
174 8 0 468851544 sda
175 8 1 614400 sda1
176 8 2 73138176 sda2
177 8 3 337163264 sda3
178 8 4 57933824 sda4
179 253 0 3976960 zram0";
180
181 #[test]
182 fn partitions_from_str_test() {
183 let parts = Partitions::from_str(PARTITIONS).unwrap();
184 dbg!(&parts);
185 assert_eq!(parts.parts.len(), 10);
186 assert_eq!(&parts.parts[0].name, "nvme0n1");
187 assert_eq!(parts.parts[0].major, 259);
188 assert_eq!(parts.parts[0].minor, 0);
189 assert_eq!(parts.parts[0].blocks, 250059096);
190 }
191
192 #[test]
193 fn partition_invalid_str_test() {
194 let s = "256 0 nvme";
195 let part = Partition::try_from(s);
196 assert!(part.is_err());
197 }
198
199 #[test]
200 fn partition_valid_str_test() {
201 let s = "255 4 666 sda";
202 let part = Partition::try_from(s);
203 assert!(part.is_ok());
204 }
205}