about summary refs log tree commit diff stats
path: root/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-24 21:52:56 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-07-24 21:52:56 +0200
commit0e90167647a53978705618f435e3d96454e6b6cf (patch)
treec0c58af1780ccef1c312fb1cfb7144b460a4f8d2 /pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
parentpkgs/tskm: Support raw paths in place of URLs (diff)
downloadnixos-config-0e90167647a53978705618f435e3d96454e6b6cf.zip
pkgs/i3status-rust: Inline the patches
The github patches seem to change their hash from time to time?
Diffstat (limited to 'pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch')
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch190
1 files changed, 190 insertions, 0 deletions
diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
new file mode 100644
index 00000000..8ef0af2e
--- /dev/null
+++ b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
@@ -0,0 +1,190 @@
+From 78d2936b67064e3b5e700a2859d00ea3dd6eda4c Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Sun, 18 May 2025 20:22:04 +0200
+Subject: [PATCH 1/3] disk_space: Support btrfs backend
+
+Btrfs is too smart for the statvfs based backend (i.e., only counting
+blocks leads to wrong numbers).
+
+For example, a btrfs disk with a lot of de-duplicated blocks (via the copy
+on write mechanism) might have a drastically over-reported disk usage.
+
+The btrfs backend is currently implemented by parsing the output of the
+`btrfs filesystem usage --raw` command. This is suboptimal, as this now
+relies on the command output not changing.
+
+Vendoring the algorithm used internally by the `btrfs` command does not
+seem to be a reasonable alternative, considering that the code[1] is
+rather complex, low level and would require semi-constant maintenance.
+Additionally, the c code would need bindings to be usable from rust.
+
+I assume, that the `btrfs` command output will stay rather similar in
+the future, as a lot of tools rely on directly parsing it (see the
+various scripts in the issue, this commit fixes).
+
+[1]: https://github.com/kdave/btrfs-progs/blob/eeab081e9d9fbdf4583122ed1caedf541383cf2d/cmds/filesystem-usage.c#L442
+
+Fixes: #1654
+---
+ src/blocks/disk_space.rs | 112 +++++++++++++++++++++++++++++++++++----
+ 1 file changed, 101 insertions(+), 11 deletions(-)
+
+diff --git a/src/blocks/disk_space.rs b/src/blocks/disk_space.rs
+index 79bfebd27..da0d3f518 100644
+--- a/src/blocks/disk_space.rs
++++ b/src/blocks/disk_space.rs
+@@ -12,6 +12,7 @@
+ //! `alert` | A value which will trigger critical block state | `10.0`
+ //! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"`
+ //! `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`
++//! `backend` | The backend to use when querying disk usage. Possible values are `"vfs"` (like `du(1)`) and `"btrfs"` | `"vfs"`
+ //!
+ //! Placeholder  | Value                                                              | Type   | Unit
+ //! -------------|--------------------------------------------------------------------|--------|-------
+@@ -63,9 +64,12 @@
+ 
+ // make_log_macro!(debug, "disk_space");
+ 
++use std::cell::OnceCell;
++
+ use super::prelude::*;
+ use crate::formatting::prefix::Prefix;
+ use nix::sys::statvfs::statvfs;
++use tokio::process::Command;
+ 
+ #[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
+ #[serde(rename_all = "lowercase")]
+@@ -76,11 +80,20 @@ pub enum InfoType {
+     Used,
+ }
+ 
++#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
++#[serde(rename_all = "lowercase")]
++pub enum Backend {
++    #[default]
++    Vfs,
++    Btrfs,
++}
++
+ #[derive(Deserialize, Debug, SmartDefault)]
+ #[serde(deny_unknown_fields, default)]
+ pub struct Config {
+     #[default("/".into())]
+     pub path: ShellString,
++    pub backend: Backend,
+     pub info_type: InfoType,
+     pub format: FormatConfig,
+     pub format_alt: Option<FormatConfig>,
+@@ -128,17 +141,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+     loop {
+         let mut widget = Widget::new().with_format(format.clone());
+ 
+-        let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?;
+-
+-        // Casting to be compatible with 32-bit systems
+-        #[allow(clippy::unnecessary_cast)]
+-        let (total, used, available, free) = {
+-            let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
+-            let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
+-                * (statvfs.fragment_size() as u64);
+-            let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
+-            let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
+-            (total, used, available, free)
++        let (total, used, available, free) = match config.backend {
++            Backend::Vfs => get_vfs(&*path)?,
++            Backend::Btrfs => get_btrfs(&path).await?,
+         };
+ 
+         let result = match config.info_type {
+@@ -205,3 +210,88 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
+         }
+     }
+ }
++
++fn get_vfs<P>(path: &P) -> Result<(u64, u64, u64, u64)>
++where
++    P: ?Sized + nix::NixPath,
++{
++    let statvfs = statvfs(path).error("failed to retrieve statvfs")?;
++
++    // Casting to be compatible with 32-bit systems
++    #[allow(clippy::unnecessary_cast)]
++    {
++        let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
++        let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
++            * (statvfs.fragment_size() as u64);
++        let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
++        let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
++
++        Ok((total, used, available, free))
++    }
++}
++
++async fn get_btrfs(path: &str) -> Result<(u64, u64, u64, u64)> {
++    const OUTPUT_CHANGED: &str = "Btrfs filesystem usage output format changed";
++
++    fn remove_estimate_min(estimate_str: &str) -> Result<&str> {
++        estimate_str.trim_matches('\t')
++            .split_once("\t")
++            .ok_or(Error::new(OUTPUT_CHANGED))
++            .map(|v| v.0)
++    }
++
++    macro_rules! get {
++        ($source:expr, $name:expr, $variable:ident) => {
++            get!(@pre_op (|a| {Ok::<_, Error>(a)}), $source, $name, $variable)
++        };
++        (@pre_op $function:expr, $source:expr, $name:expr, $variable:ident) => {
++            if $source.starts_with(concat!($name, ":")) {
++                let (found_name, variable_str) =
++                    $source.split_once(":").ok_or(Error::new(OUTPUT_CHANGED))?;
++
++                let variable_str = $function(variable_str)?;
++
++                debug_assert_eq!(found_name, $name);
++                $variable
++                    .set(variable_str.trim().parse().error(OUTPUT_CHANGED)?)
++                    .map_err(|_| Error::new(OUTPUT_CHANGED))?;
++            }
++        };
++    }
++
++    let filesystem_usage = Command::new("btrfs")
++        .args(["filesystem", "usage", "--raw", path])
++        .output()
++        .await
++        .error("Failed to collect btrfs filesystem usage info")?
++        .stdout;
++
++    {
++        let final_total = OnceCell::new();
++        let final_used = OnceCell::new();
++        let final_free = OnceCell::new();
++
++        let mut lines = filesystem_usage.lines();
++        while let Some(line) = lines
++            .next_line()
++            .await
++            .error("Failed to read output of btrfs filesystem usage")?
++        {
++            let line = line.trim();
++
++            // See btrfs-filesystem(8) for an explanation for the rows.
++            get!(line, "Device size", final_total);
++            get!(line, "Used", final_used);
++            get!(@pre_op remove_estimate_min, line, "Free (estimated)", final_free);
++        }
++
++        Ok((
++            *final_total.get().ok_or(Error::new(OUTPUT_CHANGED))?,
++            *final_used.get().ok_or(Error::new(OUTPUT_CHANGED))?,
++            // HACK(@bpeetz): We also return the free disk space as the available one, because btrfs
++            // does not tell us which disk space is reserved for the fs. <2025-05-18>
++            *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
++            *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
++        ))
++    }
++}
+-- 
+2.49.0
+