Skip to content

Commit dd1f45b

Browse files
authored
Merge pull request #2159 from bpeetz/master
disk_space: Support btrfs backend
2 parents e6f8ed9 + a0bda30 commit dd1f45b

File tree

1 file changed

+101
-11
lines changed

1 file changed

+101
-11
lines changed

src/blocks/disk_space.rs

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! `alert` | A value which will trigger critical block state | `10.0`
1313
//! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"`
1414
//! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None`
15+
//! `backend` | The backend to use when querying disk usage. Possible values are `"vfs"` (like `du(1)`) and `"btrfs"` | `"vfs"`
1516
//!
1617
//! Placeholder | Value | Type | Unit
1718
//! -------------|--------------------------------------------------------------------|--------|-------
@@ -63,9 +64,12 @@
6364
6465
// make_log_macro!(debug, "disk_space");
6566

67+
use std::cell::OnceCell;
68+
6669
use super::prelude::*;
6770
use crate::formatting::prefix::Prefix;
6871
use nix::sys::statvfs::statvfs;
72+
use tokio::process::Command;
6973

7074
#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
7175
#[serde(rename_all = "lowercase")]
@@ -76,11 +80,20 @@ pub enum InfoType {
7680
Used,
7781
}
7882

83+
#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
84+
#[serde(rename_all = "lowercase")]
85+
pub enum Backend {
86+
#[default]
87+
Vfs,
88+
Btrfs,
89+
}
90+
7991
#[derive(Deserialize, Debug, SmartDefault)]
8092
#[serde(deny_unknown_fields, default)]
8193
pub struct Config {
8294
#[default("/".into())]
8395
pub path: ShellString,
96+
pub backend: Backend,
8497
pub info_type: InfoType,
8598
pub format: FormatConfig,
8699
pub format_alt: Option<FormatConfig>,
@@ -128,17 +141,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
128141
loop {
129142
let mut widget = Widget::new().with_format(format.clone());
130143

131-
let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?;
132-
133-
// Casting to be compatible with 32-bit systems
134-
#[allow(clippy::unnecessary_cast)]
135-
let (total, used, available, free) = {
136-
let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
137-
let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
138-
* (statvfs.fragment_size() as u64);
139-
let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
140-
let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
141-
(total, used, available, free)
144+
let (total, used, available, free) = match config.backend {
145+
Backend::Vfs => get_vfs(&*path)?,
146+
Backend::Btrfs => get_btrfs(&path).await?,
142147
};
143148

144149
let result = match config.info_type {
@@ -205,3 +210,88 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
205210
}
206211
}
207212
}
213+
214+
fn get_vfs<P>(path: &P) -> Result<(u64, u64, u64, u64)>
215+
where
216+
P: ?Sized + nix::NixPath,
217+
{
218+
let statvfs = statvfs(path).error("failed to retrieve statvfs")?;
219+
220+
// Casting to be compatible with 32-bit systems
221+
#[allow(clippy::unnecessary_cast)]
222+
{
223+
let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
224+
let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
225+
* (statvfs.fragment_size() as u64);
226+
let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
227+
let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
228+
229+
Ok((total, used, available, free))
230+
}
231+
}
232+
233+
async fn get_btrfs(path: &str) -> Result<(u64, u64, u64, u64)> {
234+
const OUTPUT_CHANGED: &str = "Btrfs filesystem usage output format changed";
235+
236+
fn remove_estimate_min(estimate_str: &str) -> Result<&str> {
237+
estimate_str.trim_matches('\t')
238+
.split_once("\t")
239+
.ok_or(Error::new(OUTPUT_CHANGED))
240+
.map(|v| v.0)
241+
}
242+
243+
macro_rules! get {
244+
($source:expr, $name:expr, $variable:ident) => {
245+
get!(@pre_op (|a| {Ok::<_, Error>(a)}), $source, $name, $variable)
246+
};
247+
(@pre_op $function:expr, $source:expr, $name:expr, $variable:ident) => {
248+
if $source.starts_with(concat!($name, ":")) {
249+
let (found_name, variable_str) =
250+
$source.split_once(":").ok_or(Error::new(OUTPUT_CHANGED))?;
251+
252+
let variable_str = $function(variable_str)?;
253+
254+
debug_assert_eq!(found_name, $name);
255+
$variable
256+
.set(variable_str.trim().parse().error(OUTPUT_CHANGED)?)
257+
.map_err(|_| Error::new(OUTPUT_CHANGED))?;
258+
}
259+
};
260+
}
261+
262+
let filesystem_usage = Command::new("btrfs")
263+
.args(["filesystem", "usage", "--raw", path])
264+
.output()
265+
.await
266+
.error("Failed to collect btrfs filesystem usage info")?
267+
.stdout;
268+
269+
{
270+
let final_total = OnceCell::new();
271+
let final_used = OnceCell::new();
272+
let final_free = OnceCell::new();
273+
274+
let mut lines = filesystem_usage.lines();
275+
while let Some(line) = lines
276+
.next_line()
277+
.await
278+
.error("Failed to read output of btrfs filesystem usage")?
279+
{
280+
let line = line.trim();
281+
282+
// See btrfs-filesystem(8) for an explanation for the rows.
283+
get!(line, "Device size", final_total);
284+
get!(line, "Used", final_used);
285+
get!(@pre_op remove_estimate_min, line, "Free (estimated)", final_free);
286+
}
287+
288+
Ok((
289+
*final_total.get().ok_or(Error::new(OUTPUT_CHANGED))?,
290+
*final_used.get().ok_or(Error::new(OUTPUT_CHANGED))?,
291+
// HACK(@bpeetz): We also return the free disk space as the available one, because btrfs
292+
// does not tell us which disk space is reserved for the fs. <2025-05-18>
293+
*final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
294+
*final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
295+
))
296+
}
297+
}

0 commit comments

Comments
 (0)