Skip to main content

btrfs_cli/
util.rs

1use anyhow::{Context, Result};
2use std::str::FromStr;
3use uuid::Uuid;
4
5/// Format a byte count as a human-readable string using binary prefixes.
6pub fn human_bytes(bytes: u64) -> String {
7    const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
8    let mut value = bytes as f64;
9    let mut unit = 0;
10    while value >= 1024.0 && unit + 1 < UNITS.len() {
11        value /= 1024.0;
12        unit += 1;
13    }
14    if unit == 0 {
15        format!("{bytes}B")
16    } else {
17        format!("{value:.2}{}", UNITS[unit])
18    }
19}
20
21/// Parse a size string with an optional binary suffix (K, M, G, T, P, E).
22pub fn parse_size_with_suffix(s: &str) -> Result<u64> {
23    let (num_str, suffix) = match s.find(|c: char| c.is_alphabetic()) {
24        Some(i) => (&s[..i], &s[i..]),
25        None => (s, ""),
26    };
27    let n: u64 = num_str
28        .parse()
29        .with_context(|| format!("invalid size number: '{num_str}'"))?;
30    let multiplier: u64 = match suffix.to_uppercase().as_str() {
31        "" => 1,
32        "K" => 1024,
33        "M" => 1024 * 1024,
34        "G" => 1024 * 1024 * 1024,
35        "T" => 1024u64.pow(4),
36        "P" => 1024u64.pow(5),
37        "E" => 1024u64.pow(6),
38        _ => anyhow::bail!("unknown size suffix: '{suffix}'"),
39    };
40    n.checked_mul(multiplier)
41        .ok_or_else(|| anyhow::anyhow!("size overflow: '{s}'"))
42}
43
44/// A UUID value parsed from a CLI argument.
45///
46/// Accepts `clear` (nil UUID), `random` (random v4 UUID), `time` (v7
47/// time-ordered UUID), or any standard UUID string (with or without hyphens).
48#[derive(Debug, Clone, Copy)]
49pub struct ParsedUuid(Uuid);
50
51impl std::ops::Deref for ParsedUuid {
52    type Target = Uuid;
53    fn deref(&self) -> &Uuid {
54        &self.0
55    }
56}
57
58/// Parse a qgroup ID string of the form `"<level>/<subvolid>"` into a packed u64.
59///
60/// The packed form is `(level as u64) << 48 | subvolid`.
61/// Example: `"0/5"` → `5`, `"1/256"` → `0x0001_0000_0000_0100`.
62pub fn parse_qgroupid(s: &str) -> anyhow::Result<u64> {
63    let (level_str, id_str) = s
64        .split_once('/')
65        .ok_or_else(|| anyhow::anyhow!("invalid qgroup ID '{}': expected <level>/<id>", s))?;
66    let level: u64 = level_str
67        .parse()
68        .map_err(|_| anyhow::anyhow!("invalid qgroup level '{}' in '{}'", level_str, s))?;
69    let subvolid: u64 = id_str
70        .parse()
71        .map_err(|_| anyhow::anyhow!("invalid qgroup subvolid '{}' in '{}'", id_str, s))?;
72    Ok((level << 48) | subvolid)
73}
74
75impl FromStr for ParsedUuid {
76    type Err = String;
77
78    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
79        match s {
80            "clear" => Ok(Self(Uuid::nil())),
81            "random" => Ok(Self(Uuid::new_v4())),
82            "time" => Ok(Self(Uuid::now_v7())),
83            _ => Uuid::parse_str(s)
84                .map(Self)
85                .map_err(|e| format!("invalid UUID: {e}")),
86        }
87    }
88}