bsudlib/
fs.rs

1use crate::utils::bytes_to_gib;
2use crate::utils::exec;
3use easy_error::format_err;
4use lfs_core::{self, Stats};
5use log::debug;
6use proc_mounts::MountList;
7use std::error::Error;
8use std::fs::create_dir;
9use std::fs::File;
10use std::io::Read;
11use std::path::Path;
12use std::path::PathBuf;
13
14pub fn device_seems_formated(device_path: &String) -> Result<bool, Box<dyn Error>> {
15    debug!("does device {} seems formated ?", device_path);
16    // Read fs header, consider unformated if reading only zeros
17    let mut buffer = [0; 1_000_000];
18    let mut file = File::open(device_path)?;
19    let n = file.read(&mut buffer[..])?;
20    for byte in &buffer[..n] {
21        if *byte != 0 {
22            debug!("does device {} seems formated ? -> true", device_path);
23            return Ok(true);
24        }
25    }
26    debug!("does device {} seems formated ? -> false", device_path);
27    Ok(false)
28}
29
30pub fn format(device_path: &String) -> Result<(), Box<dyn Error>> {
31    exec("mkfs.btrfs", &[device_path])?;
32    Ok(())
33}
34
35pub fn is_folder(path: &String) -> bool {
36    PathBuf::from(path).is_dir()
37}
38
39pub fn create_folder(path: &String) -> Result<(), Box<dyn Error>> {
40    Ok(create_dir(path)?)
41}
42
43pub fn is_mounted(device_path: &String, mount_target: &String) -> Result<bool, Box<dyn Error>> {
44    let mount_list = MountList::new()?;
45    let source = Path::new(device_path.as_str());
46    let Some(mount_info) = mount_list.get_mount_by_source(source) else {
47        debug!("{} is not mounted", device_path);
48        return Ok(false);
49    };
50    let dest = PathBuf::from(mount_target.clone());
51    if mount_info.dest != dest {
52        return Err(Box::new(format_err!(
53            "{:?} seems to be mounted on {:?}, not in {}",
54            source,
55            mount_info.dest,
56            mount_target
57        )));
58    }
59    debug!(
60        "{:?} is mounted on {:?}, all good",
61        mount_info.source, mount_info.dest
62    );
63    Ok(true)
64}
65
66pub fn mount(device_path: &String, mount_target: &String) -> Result<(), Box<dyn Error>> {
67    exec("mount", &[device_path, mount_target])?;
68    Ok(())
69}
70
71pub fn umount(device_path: &String) -> Result<(), Box<dyn Error>> {
72    exec("umount", &[device_path])?;
73    Ok(())
74}
75
76fn get_stats(device_path: &String) -> Result<Option<Stats>, Box<dyn Error>> {
77    let mut read_options = lfs_core::ReadOptions::default();
78    read_options.remote_stats(false);
79    for mount in lfs_core::read_mounts(&read_options)? {
80        if mount.info.fs == *device_path {
81            let stats = mount.stats?;
82            return Ok(Some(stats));
83        }
84    }
85    Ok(None)
86}
87
88pub fn used_bytes(device_path: &String) -> Result<usize, Box<dyn Error>> {
89    debug!("used_bytes");
90    let Some(stats) = get_stats(device_path)? else {
91        return Err(Box::new(format_err!(
92            "used_bytes cannot get fs stats from {}",
93            device_path
94        )));
95    };
96    let used_bytes = stats.used() as usize;
97    debug!(
98        "used_bytes on {}: {}B ({}GiB)",
99        device_path,
100        used_bytes,
101        bytes_to_gib(used_bytes)
102    );
103    Ok(used_bytes)
104}
105
106pub fn size_bytes(device_path: &String) -> Result<usize, Box<dyn Error>> {
107    debug!("size_bytes");
108    let Some(stats) = get_stats(device_path)? else {
109        return Err(Box::new(format_err!(
110            "size_bytes cannot get fs stats from {}",
111            device_path
112        )));
113    };
114    let size_bytes = stats.size() as usize;
115    debug!(
116        "size_bytes on {}: {}B ({}GiB)",
117        device_path,
118        size_bytes,
119        bytes_to_gib(size_bytes)
120    );
121    Ok(size_bytes)
122}
123
124pub fn available_bytes(device_path: &String) -> Result<usize, Box<dyn Error>> {
125    debug!("available_bytes");
126    let Some(stats) = get_stats(device_path)? else {
127        return Err(Box::new(format_err!(
128            "available_bytes cannot get fs stats from {}",
129            device_path
130        )));
131    };
132    let available_bytes = stats.available() as usize;
133    debug!(
134        "available_bytes on {}: {}B ({}GiB)",
135        device_path,
136        available_bytes,
137        bytes_to_gib(available_bytes)
138    );
139    Ok(available_bytes)
140}
141
142pub fn used_perc(device_path: &String) -> Result<f32, Box<dyn Error>> {
143    debug!("available_perc");
144    let Some(stats) = get_stats(device_path)? else {
145        return Err(Box::new(format_err!(
146            "available_perc cannot get fs stats from {}",
147            device_path
148        )));
149    };
150    let available_perc = stats.used() as f32 / stats.size() as f32;
151    debug!("available_perc on {}: {}", device_path, available_perc);
152    Ok(available_perc)
153}
154
155pub fn extend_fs_max(mount_target: &String) -> Result<(), Box<dyn Error>> {
156    exec("btrfs", &["filesystem", "resize", "max", mount_target])?;
157    Ok(())
158}
159
160pub fn resize(mount_path: &str, new_size_bytes: usize) -> Result<(), Box<dyn Error>> {
161    let new_size = format!("{}", new_size_bytes);
162    exec(
163        "btrfs",
164        &["filesystem", "resize", new_size.as_str(), mount_path],
165    )?;
166    Ok(())
167}