refactor(#2352): quota show via daemon wire command

This commit is contained in:
damocles 2026-07-14 23:58:55 +02:00 committed by mara
commit 3797177e7f
3 changed files with 116 additions and 30 deletions

View file

@ -20,7 +20,6 @@ use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result, bail};
use clap::{Args, Parser, Subcommand};
use hive_c0re::coordinator::Coordinator;
/// Rebuild-queue DAG progress rendering (`wait_for_dags` + the spinner /
/// plain renderers), split out to keep this file manageable. `#[path]` keeps
@ -760,7 +759,7 @@ async fn main() -> Result<()> {
} => choom(&name, resume_session.as_deref()),
Cmd::Quota { cmd } => match cmd {
QuotaCmd::Enable => quota_enable(&socket).await,
QuotaCmd::Show { name } => quota_show(name.as_deref()).await,
QuotaCmd::Show { name } => quota_show(&socket, name.as_deref()).await,
QuotaCmd::Limit { name, size } => quota_limit(&socket, &name, &size).await,
},
Cmd::MarkdownDocs => {
@ -1046,37 +1045,41 @@ async fn quota_enable(socket: &Path) -> Result<()> {
}
/// `quota show [name]` — report per-agent disk usage from btrfs qgroups.
async fn quota_show(name: Option<&str>) -> Result<()> {
let agents: Vec<String> = match name {
Some(n) => vec![n.to_owned()],
None => Coordinator::kept_state_names(),
};
if agents.is_empty() {
/// The daemon resolves the agent set + reads each subvolume's usage (it
/// holds the privileged helper); the client just formats the returned rows.
async fn quota_show(socket: &Path, name: Option<&str>) -> Result<()> {
let resp = hive_c0re::client::request(
socket,
hive_host_sock::HostRequest::QuotaShow {
name: name.map(str::to_owned),
},
)
.await
.with_context(|| format!("connect to daemon socket {}", socket.display()))?;
if !resp.ok {
// Carries the "btrfs quota not enabled — run `hivectl quota enable`
// first" hint when qgroups are off.
bail!("{}", resp.error.as_deref().unwrap_or("quota show failed"));
}
let rows = resp.quota.unwrap_or_default();
if rows.is_empty() {
println!("no agents with a state dir found");
return Ok(());
}
for agent in &agents {
match hive_c0re::priv_client::read_subvolume_usage(agent).await {
Ok((rfer, excl)) => {
println!(
"{agent:<12} referenced {:>10} exclusive {:>10}",
human_bytes(rfer),
human_bytes(excl)
);
}
Err(e) => {
let msg = format!("{e:#}");
// btrfs-progs prints "ERROR: ... quota not enabled" to stderr
// when qgroups are off; match the stable fragment case-
// insensitively rather than an exact line (the surrounding
// wording varies across btrfs-progs versions).
if msg.to_ascii_lowercase().contains("quota not enabled") {
bail!("btrfs quota not enabled — run `hivectl quota enable` first");
}
// A plain-dir agent (no subvolume) has no qgroup; report it
// inline and keep going rather than aborting the whole sweep.
println!("{agent:<12} (no qgroup data — plain dir or: {msg})");
}
for row in &rows {
match (row.referenced, row.exclusive) {
(Some(rfer), Some(excl)) => println!(
"{:<12} referenced {:>10} exclusive {:>10}",
row.agent,
human_bytes(rfer),
human_bytes(excl)
),
// Plain-dir agent (no qgroup) — the daemon set an explanatory note.
_ => println!(
"{:<12} ({})",
row.agent,
row.note.as_deref().unwrap_or("no qgroup data")
),
}
}
Ok(())

View file

@ -201,6 +201,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
}
HostRequest::QuotaEnable => handle_quota_enable().await?,
HostRequest::QuotaLimit { name, limit } => handle_quota_limit(name, *limit).await?,
HostRequest::QuotaShow { name } => handle_quota_show(name.as_deref()).await?,
})
}
.await;
@ -429,6 +430,45 @@ async fn handle_quota_limit(name: &str, limit: Option<u64>) -> Result<HostRespon
Ok(HostResponse::success())
}
async fn handle_quota_show(name: Option<&str>) -> Result<HostResponse> {
let agents: Vec<String> = match name {
Some(n) => vec![n.to_owned()],
None => Coordinator::kept_state_names(),
};
let mut rows = Vec::with_capacity(agents.len());
for agent in &agents {
match crate::priv_client::read_subvolume_usage(agent).await {
Ok((referenced, exclusive)) => rows.push(hive_host_sock::QuotaRow {
agent: agent.clone(),
referenced: Some(referenced),
exclusive: Some(exclusive),
note: None,
}),
Err(e) => {
let msg = format!("{e:#}");
// btrfs-progs prints "ERROR: ... quota not enabled" to stderr
// when qgroups are off; short-circuit the whole sweep with the
// enable hint (case-insensitive fragment match — the wording
// varies across btrfs-progs versions).
if msg.to_ascii_lowercase().contains("quota not enabled") {
return Ok(HostResponse::error(
"btrfs quota not enabled — run `hivectl quota enable` first",
));
}
// A plain-dir agent (no subvolume) has no qgroup; note it
// inline and keep going rather than aborting the whole sweep.
rows.push(hive_host_sock::QuotaRow {
agent: agent.clone(),
referenced: None,
exclusive: None,
note: Some(format!("no qgroup data — plain dir or: {msg}")),
});
}
}
}
Ok(HostResponse::quota(rows))
}
async fn handle_matrix_sync_admin() -> Result<HostResponse> {
require_matrix_present().await?;
let register_token =