btrfs_cli/quota/
status.rs1use crate::{Format, RunContext, Runnable, util::open_path};
2use anyhow::{Context, Result};
3use btrfs_uapi::{quota::QuotaRescanStatus, sysfs::SysfsBtrfs};
4use clap::Parser;
5use cols::Cols;
6use std::{os::unix::io::AsFd, path::PathBuf};
7use uuid::Uuid;
8
9#[derive(Parser, Debug)]
41pub struct QuotaStatusCommand {
42 pub path: PathBuf,
44
45 #[clap(long)]
47 pub is_enabled: bool,
48}
49
50fn describe_mode(mode: &str) -> &str {
51 match mode {
52 "qgroup" => "full accounting",
53 "squota" => "simplified accounting",
54 other => other,
55 }
56}
57
58impl Runnable for QuotaStatusCommand {
59 fn run(&self, ctx: &RunContext) -> Result<()> {
60 let file = open_path(&self.path)?;
61 let fd = file.as_fd();
62
63 let fs =
64 btrfs_uapi::filesystem::filesystem_info(fd).with_context(|| {
65 format!(
66 "failed to get filesystem info for '{}'",
67 self.path.display()
68 )
69 })?;
70
71 let status =
72 SysfsBtrfs::new(&fs.uuid).quota_status().with_context(|| {
73 format!(
74 "failed to read quota status for '{}'",
75 self.path.display()
76 )
77 })?;
78
79 if self.is_enabled {
80 if !status.enabled {
81 std::process::exit(1);
84 }
85 return Ok(());
86 }
87
88 let rescan = if status.enabled {
89 Some(btrfs_uapi::quota::quota_rescan_status(fd))
90 } else {
91 None
92 };
93
94 match ctx.format {
95 Format::Modern => {
96 print_status_modern(
97 &self.path,
98 &fs.uuid,
99 &status,
100 rescan.as_ref(),
101 );
102 }
103 Format::Text => {
104 print_status_text(&self.path, &status);
105 }
106 Format::Json => unreachable!(),
107 }
108
109 Ok(())
110 }
111}
112
113fn print_status_text(
114 path: &std::path::Path,
115 status: &btrfs_uapi::sysfs::QuotaStatus,
116) {
117 println!("Quotas on {}:", path.display());
118
119 if !status.enabled {
120 println!(" Enabled: no");
121 return;
122 }
123
124 println!(" Enabled: yes");
125
126 if let Some(ref mode) = status.mode {
127 println!(
128 " Mode: {} ({})",
129 mode,
130 describe_mode(mode)
131 );
132 }
133
134 if let Some(inconsistent) = status.inconsistent {
135 println!(
136 " Inconsistent: {}{}",
137 if inconsistent { "yes" } else { "no" },
138 if inconsistent { " (rescan needed)" } else { "" }
139 );
140 }
141
142 if let Some(override_limits) = status.override_limits {
143 println!(
144 " Override limits: {}",
145 if override_limits { "yes" } else { "no" }
146 );
147 }
148
149 if let Some(threshold) = status.drop_subtree_threshold {
150 println!(" Drop subtree threshold: {threshold}");
151 }
152
153 if let Some(total) = status.total_count {
154 println!(" Total count: {total}");
155 }
156
157 if let Some(level0) = status.level0_count {
158 println!(" Level 0: {level0}");
159 }
160}
161
162#[derive(Cols)]
163struct StatusRow {
164 #[column(header = "PROPERTY")]
165 label: String,
166 #[column(header = "VALUE")]
167 value: String,
168}
169
170fn print_status_modern(
171 path: &std::path::Path,
172 uuid: &Uuid,
173 status: &btrfs_uapi::sysfs::QuotaStatus,
174 rescan: Option<&nix::Result<QuotaRescanStatus>>,
175) {
176 println!("Quotas on {}:", path.display());
177
178 let mut rows: Vec<StatusRow> = Vec::new();
179
180 rows.push(StatusRow {
181 label: "UUID".to_string(),
182 value: uuid.as_hyphenated().to_string(),
183 });
184
185 rows.push(StatusRow {
186 label: "Enabled".to_string(),
187 value: if status.enabled { "yes" } else { "no" }.to_string(),
188 });
189
190 if !status.enabled {
191 let mut out = std::io::stdout().lock();
192 let _ = StatusRow::print_table(&rows, &mut out);
193 return;
194 }
195
196 if let Some(ref mode) = status.mode {
197 rows.push(StatusRow {
198 label: "Mode".to_string(),
199 value: format!("{mode} ({})", describe_mode(mode)),
200 });
201 }
202
203 if let Some(inconsistent) = status.inconsistent {
204 rows.push(StatusRow {
205 label: "Inconsistent".to_string(),
206 value: if inconsistent {
207 "yes (rescan needed)"
208 } else {
209 "no"
210 }
211 .to_string(),
212 });
213 }
214
215 if let Some(override_limits) = status.override_limits {
216 rows.push(StatusRow {
217 label: "Override limits".to_string(),
218 value: if override_limits { "yes" } else { "no" }.to_string(),
219 });
220 }
221
222 if let Some(threshold) = status.drop_subtree_threshold {
223 rows.push(StatusRow {
224 label: "Drop subtree threshold".to_string(),
225 value: threshold.to_string(),
226 });
227 }
228
229 if let Some(total) = status.total_count {
230 rows.push(StatusRow {
231 label: "Total count".to_string(),
232 value: total.to_string(),
233 });
234 }
235
236 if let Some(level0) = status.level0_count {
237 rows.push(StatusRow {
238 label: "Level 0".to_string(),
239 value: level0.to_string(),
240 });
241 }
242
243 if let Some(rescan_result) = rescan {
244 rows.push(StatusRow {
245 label: "Rescan".to_string(),
246 value: match rescan_result {
247 Ok(rs) if rs.running => {
248 format!("in progress (objectid {})", rs.progress)
249 }
250 Ok(_) => "not in progress".to_string(),
251 Err(_) => "unknown (insufficient privileges)".to_string(),
252 },
253 });
254 }
255
256 let mut out = std::io::stdout().lock();
257 let _ = StatusRow::print_table(&rows, &mut out);
258}