ferrix_lib/
parts.rs

1/* parts.rs
2 *
3 * Copyright 2025 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Get information about mounted partitions
22
23use 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)]
32// pub struct StorageInfo {
33//     pub device: String,
34//     pub mnt_point: String,
35//     pub fs: String,
36//     pub total_size: Size,
37//     pub used: Size,
38//     pub available: Size,
39//     pub device_model: Option<String>,
40//     pub block_size: usize,
41// }
42
43/// List of partitions
44#[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}