Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions crates/degu/src/commands/quota.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
mod model;
mod output;
mod platform;

use crate::cli::QuotaArgs;
use crate::commands::next_action::{self, OutputMode, Request, Workflow};
Expand All @@ -15,7 +13,7 @@ pub(crate) fn run(args: QuotaArgs, ui: Ui) -> Result<()> {
let target_text = escape_terminal_text(&target.display().to_string());
let canonical = std::fs::canonicalize(&target)
.with_context(|| format!("quota target is unavailable: {target_text}"))?;
let report = platform::probe(&canonical)?;
let report = crate::quota::probe(&canonical)?;
output::print(&report, json, ui)?;
next_action::print(Request {
output: output_mode(json, ui),
Expand Down
6 changes: 3 additions & 3 deletions crates/degu/src/commands/quota/output.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::model::{QuotaDimension, QuotaGrace, QuotaGraceState, QuotaReport};
use crate::output::stdoutln;
use crate::presentation::{escape_terminal_text, human_bytes, ratio_bar};
use crate::quota::model::{QuotaDimension, QuotaGrace, QuotaGraceState, QuotaSnapshot};
use crate::runtime::{Glyphs, Ui};
use anyhow::Result;
use std::cmp::Ordering;
Expand All @@ -10,7 +10,7 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
const PERCENT_SCALE: f64 = 100.0;
const DETAIL_INDENT: usize = 11;

pub(super) fn print(report: &QuotaReport, json: bool, ui: Ui) -> Result<()> {
pub(super) fn print(report: &QuotaSnapshot, json: bool, ui: Ui) -> Result<()> {
if json {
stdoutln!("{}", serde_json::to_string_pretty(report)?)?;
} else {
Expand All @@ -19,7 +19,7 @@ pub(super) fn print(report: &QuotaReport, json: bool, ui: Ui) -> Result<()> {
Ok(())
}

fn render_human(report: &QuotaReport, ui: Ui) -> String {
fn render_human(report: &QuotaSnapshot, ui: Ui) -> String {
let glyphs = ui.glyphs;
let separator = glyphs.separator;
let target = escape_terminal_text(&report.scope.path.display().to_string());
Expand Down
9 changes: 5 additions & 4 deletions crates/degu/src/commands/quota/output/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::{format_count, render_dimension, render_human};
use crate::commands::quota::model::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaReport, QuotaScope,
use crate::quota::model::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaScope,
QuotaSnapshot,
};
use crate::runtime::{Glyphs, Ui};
use std::path::PathBuf;
Expand All @@ -12,7 +13,7 @@ fn quota_human_escapes_scope_and_provider_terminal_controls() {
PathBuf::from("/mnt/\tdata"),
"ext4\x1b[31m".to_owned(),
);
let report = QuotaReport::active(
let report = QuotaSnapshot::active(
scope,
1000,
ActiveQuota {
Expand Down Expand Up @@ -43,7 +44,7 @@ fn quota_human_reflows_fields_for_a_narrow_terminal() {
PathBuf::from("/home/user/a-very-long-mount-point"),
"ext4".to_owned(),
);
let report = QuotaReport::active(
let report = QuotaSnapshot::active(
scope,
1000,
ActiveQuota {
Expand Down
1 change: 1 addition & 0 deletions crates/degu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod findings_table;
mod lifecycle;
mod output;
mod presentation;
mod quota;
mod runtime;
mod source_selection;
mod value_parser;
Expand Down
12 changes: 12 additions & 0 deletions crates/degu/src/quota.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub(crate) mod model;
mod platform;

use std::path::Path;

pub(crate) use model::QuotaSnapshot;
pub(crate) use platform::ProbeError;

/// Read one authoritative quota snapshot for an already-resolved path.
pub(crate) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
platform::probe(path)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ use serde::Serialize;
use std::path::PathBuf;

#[derive(Debug, Serialize)]
pub(super) struct QuotaReport {
pub(super) scope: QuotaScope,
pub(super) subject: QuotaSubject,
pub(super) provider: &'static str,
pub(super) data_source: &'static str,
pub(super) state: &'static str,
pub(super) space: QuotaDimension,
pub(super) inodes: QuotaDimension,
pub(crate) struct QuotaSnapshot {
pub(crate) scope: QuotaScope,
pub(crate) subject: QuotaSubject,
pub(crate) provider: &'static str,
pub(crate) data_source: &'static str,
pub(crate) state: &'static str,
pub(crate) space: QuotaDimension,
pub(crate) inodes: QuotaDimension,
}

#[cfg(any(target_os = "linux", test))]
impl QuotaReport {
pub(super) fn active(scope: QuotaScope, subject_id: u32, quota: ActiveQuota) -> Self {
impl QuotaSnapshot {
pub(crate) fn active(scope: QuotaScope, subject_id: u32, quota: ActiveQuota) -> Self {
Self {
scope,
subject: QuotaSubject::user(subject_id),
Expand All @@ -28,23 +28,23 @@ impl QuotaReport {
}

#[cfg(any(target_os = "linux", test))]
pub(super) struct ActiveQuota {
pub(super) provider: &'static str,
pub(super) data_source: &'static str,
pub(super) space: QuotaDimension,
pub(super) inodes: QuotaDimension,
pub(crate) struct ActiveQuota {
pub(crate) provider: &'static str,
pub(crate) data_source: &'static str,
pub(crate) space: QuotaDimension,
pub(crate) inodes: QuotaDimension,
}

#[derive(Debug, Serialize)]
pub(super) struct QuotaScope {
pub(super) path: PathBuf,
pub(super) mount_point: PathBuf,
pub(super) filesystem: String,
pub(crate) struct QuotaScope {
pub(crate) path: PathBuf,
pub(crate) mount_point: PathBuf,
pub(crate) filesystem: String,
}

#[cfg(any(target_os = "linux", test))]
impl QuotaScope {
pub(super) fn new(path: PathBuf, mount_point: PathBuf, filesystem: String) -> Self {
pub(crate) fn new(path: PathBuf, mount_point: PathBuf, filesystem: String) -> Self {
Self {
path,
mount_point,
Expand All @@ -54,9 +54,9 @@ impl QuotaScope {
}

#[derive(Debug, Serialize)]
pub(super) struct QuotaSubject {
pub(super) kind: &'static str,
pub(super) id: u32,
pub(crate) struct QuotaSubject {
pub(crate) kind: &'static str,
pub(crate) id: u32,
}

#[cfg(any(target_os = "linux", test))]
Expand All @@ -67,18 +67,18 @@ impl QuotaSubject {
}

#[derive(Debug, Serialize)]
pub(super) struct QuotaDimension {
pub(super) used: u64,
pub(super) soft_limit: Option<u64>,
pub(super) hard_limit: Option<u64>,
pub(super) headroom_to_soft_limit: Option<u64>,
pub(super) headroom_to_hard_limit: Option<u64>,
pub(super) grace: Option<QuotaGrace>,
pub(crate) struct QuotaDimension {
pub(crate) used: u64,
pub(crate) soft_limit: Option<u64>,
pub(crate) hard_limit: Option<u64>,
pub(crate) headroom_to_soft_limit: Option<u64>,
pub(crate) headroom_to_hard_limit: Option<u64>,
pub(crate) grace: Option<QuotaGrace>,
}

#[cfg(any(target_os = "linux", test))]
impl QuotaDimension {
pub(super) fn new(used: u64, limits: QuotaLimits, grace: Option<QuotaGrace>) -> Self {
pub(crate) fn new(used: u64, limits: QuotaLimits, grace: Option<QuotaGrace>) -> Self {
let soft_limit = nonzero_limit(limits.soft);
let hard_limit = nonzero_limit(limits.hard);
Self {
Expand All @@ -93,18 +93,18 @@ impl QuotaDimension {
}

#[derive(Debug, Serialize)]
pub(super) struct QuotaGrace {
pub(super) state: QuotaGraceState,
pub(crate) struct QuotaGrace {
pub(crate) state: QuotaGraceState,
/// `None` means the provider reported an expired grace period without a
/// deadline (Lustre prints `none` or `expired`); degu never synthesizes
/// one. Serialized as an explicit `null`: the key is part of the frozen
/// JSON contract and must never be omitted.
pub(super) expires_at_unix: Option<u64>,
pub(crate) expires_at_unix: Option<u64>,
}

#[cfg(any(target_os = "linux", test))]
impl QuotaGrace {
pub(super) fn from_kernel_deadline(deadline: u64, observed_at_unix: u64) -> Option<Self> {
pub(crate) fn from_kernel_deadline(deadline: u64, observed_at_unix: u64) -> Option<Self> {
nonzero_limit(deadline).map(|expires_at_unix| Self {
state: if expires_at_unix > observed_at_unix {
QuotaGraceState::Active
Expand All @@ -118,22 +118,22 @@ impl QuotaGrace {

#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub(super) enum QuotaGraceState {
pub(crate) enum QuotaGraceState {
#[cfg_attr(all(not(target_os = "linux"), not(test)), allow(dead_code))]
Active,
#[cfg_attr(all(not(target_os = "linux"), not(test)), allow(dead_code))]
Expired,
}

#[cfg(any(target_os = "linux", test))]
pub(super) struct QuotaLimits {
pub(super) soft: u64,
pub(super) hard: u64,
pub(crate) struct QuotaLimits {
pub(crate) soft: u64,
pub(crate) hard: u64,
}

#[cfg(any(target_os = "linux", test))]
impl QuotaLimits {
pub(super) fn new(soft: u64, hard: u64) -> Self {
pub(crate) fn new(soft: u64, hard: u64) -> Self {
Self { soft, hard }
}
}
Expand All @@ -151,8 +151,8 @@ fn headroom(limit: Option<u64>, used: u64) -> Option<u64> {
#[cfg(test)]
mod tests {
use super::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaReport,
QuotaScope,
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaScope,
QuotaSnapshot,
};
use std::path::PathBuf;

Expand Down Expand Up @@ -235,7 +235,7 @@ mod tests {
PathBuf::from("/home"),
"ext4".to_owned(),
);
let report = QuotaReport::active(
let report = QuotaSnapshot::active(
scope,
1000,
ActiveQuota {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ mod linux;
#[cfg(target_os = "macos")]
mod macos;

use super::model::QuotaReport;
#[cfg(target_os = "linux")]
use super::model::QuotaScope;
use super::model::QuotaSnapshot;
use crate::presentation::escape_terminal_text as escaped;
use std::fmt;
use std::path::Path;
Expand All @@ -29,7 +29,7 @@ impl MountInfo {
}

#[derive(Debug)]
pub(super) enum ProbeError {
pub(crate) enum ProbeError {
#[cfg(target_os = "linux")]
NotConfigured {
filesystem: String,
Expand Down Expand Up @@ -161,17 +161,17 @@ fn failure_fields(error: &ProbeError) -> (&'static str, &str, &str, &str) {
}

#[cfg(target_os = "linux")]
pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
pub(super) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
linux::probe(path)
}

#[cfg(target_os = "macos")]
pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
pub(super) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
macos::probe(path)
}

#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
pub(super) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
Err(ProbeError::Unsupported {
filesystem: "unknown".to_owned(),
mount_point: path.display().to_string(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
mod lustre;

use super::{MountInfo, ProbeError};
use crate::commands::quota::model::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaLimits, QuotaReport,
};
use crate::quota::model::{ActiveQuota, QuotaDimension, QuotaGrace, QuotaLimits, QuotaSnapshot};
use std::ffi::{CString, OsString};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
Expand All @@ -25,7 +23,7 @@ struct QueryResult {
inodes: QuotaDimension,
}

pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
pub(super) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
let mount = inspect_mount(path)?;
// SAFETY: geteuid has no preconditions and does not mutate process state.
let subject_id = unsafe { libc::geteuid() };
Expand All @@ -36,10 +34,10 @@ pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
}
}

fn probe_vfs(mount: MountInfo, path: &Path, subject_id: u32) -> Result<QuotaReport, ProbeError> {
fn probe_vfs(mount: MountInfo, path: &Path, subject_id: u32) -> Result<QuotaSnapshot, ProbeError> {
let result = query_current_user(&mount, subject_id)?;
let scope = mount.scope(path);
Ok(QuotaReport::active(
Ok(QuotaSnapshot::active(
scope,
subject_id,
ActiveQuota {
Expand Down Expand Up @@ -225,7 +223,7 @@ mod tests {
use super::{
MountInfo, ProbeError, QueryResult, classify_error, incomplete, normalize, parse_mountinfo,
};
use crate::commands::quota::model::QuotaGraceState;
use crate::quota::model::QuotaGraceState;
use std::path::{Path, PathBuf};

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
//! a number it could not verify.

use super::{MountInfo, ProbeError};
use crate::commands::quota::model::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaReport,
use crate::quota::model::{
ActiveQuota, QuotaDimension, QuotaGrace, QuotaGraceState, QuotaLimits, QuotaSnapshot,
};
use std::io::Read;
use std::os::unix::ffi::OsStrExt;
Expand Down Expand Up @@ -116,7 +116,7 @@ pub(super) fn probe(
mount: MountInfo,
path: &Path,
subject_id: u32,
) -> Result<QuotaReport, ProbeError> {
) -> Result<QuotaSnapshot, ProbeError> {
require_rooted_mount_point(&mount)?;
verify_statfs_is_lustre(&mount)?;
let execution = capture(Path::new(LFS_BINARY), &mount, subject_id, LFS_TIMEOUT)?;
Expand All @@ -128,7 +128,7 @@ pub(super) fn probe(
let parsed = parse(&stdout, &mount.mount_point, subject_id, observed_at_unix)
.map_err(|reason| super::incomplete(&mount, reason))?;
let scope = mount.scope(path);
Ok(QuotaReport::active(
Ok(QuotaSnapshot::active(
scope,
subject_id,
ActiveQuota {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use super::{MountInfo, ProbeError};
use crate::commands::quota::model::QuotaReport;
use crate::quota::model::QuotaSnapshot;
use std::ffi::CString;
use std::mem::MaybeUninit;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};

pub(super) fn probe(path: &Path) -> Result<QuotaReport, ProbeError> {
pub(super) fn probe(path: &Path) -> Result<QuotaSnapshot, ProbeError> {
let mount = inspect_mount(path)?;
Err(ProbeError::Unsupported {
filesystem: mount.filesystem,
Expand Down