btrfs_cli/filesystem/
show.rs1use super::UnitMode;
2use crate::{Format, Runnable, util::fmt_size};
3use anyhow::{Context, Result};
4use btrfs_uapi::{
5 device::device_info_all,
6 filesystem::{filesystem_info, label_get},
7 space::space_info,
8};
9use clap::Parser;
10use std::{collections::HashSet, fs::File, os::unix::io::AsFd};
11
12#[derive(Parser, Debug)]
14pub struct FilesystemShowCommand {
15 #[clap(long, short = 'd')]
17 pub all_devices: bool,
18
19 #[clap(long, short)]
21 pub mounted: bool,
22
23 #[clap(flatten)]
24 pub units: UnitMode,
25
26 pub filter: Option<String>,
28}
29
30impl Runnable for FilesystemShowCommand {
31 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
32 if self.all_devices {
33 anyhow::bail!("--all-devices is not yet implemented");
34 }
35
36 let mode = self.units.resolve();
37 let mounts =
38 parse_btrfs_mounts().context("failed to read /proc/self/mounts")?;
39
40 if mounts.is_empty() {
41 println!("No btrfs filesystem found.");
42 return Ok(());
43 }
44
45 let mut seen_uuids = HashSet::new();
46 let mut first = true;
47 for mount in &mounts {
48 let Ok(file) = File::open(mount) else {
49 continue;
50 };
51 let fd = file.as_fd();
52
53 let Ok(info) = filesystem_info(fd) else {
54 continue;
55 };
56
57 if let Some(filter) = &self.filter {
58 let uuid_str = info.uuid.as_hyphenated().to_string();
59 let label = label_get(fd).unwrap_or_default();
60 let label_str = label.to_string_lossy();
61 if mount != filter
62 && uuid_str != *filter
63 && label_str != filter.as_str()
64 {
65 continue;
66 }
67 }
68
69 if !seen_uuids.insert(info.uuid) {
70 continue;
71 }
72
73 let label = label_get(fd)
74 .map(|l| l.to_string_lossy().into_owned())
75 .unwrap_or_default();
76
77 let devices = device_info_all(fd, &info).with_context(|| {
78 format!("failed to get device info for '{mount}'")
79 })?;
80
81 let used_bytes = space_info(fd)
82 .map(|entries| {
83 entries.iter().map(|e| e.used_bytes).sum::<u64>()
84 })
85 .unwrap_or(0);
86
87 if !first {
88 println!();
89 }
90 first = false;
91
92 if label.is_empty() {
93 print!("Label: none ");
94 } else {
95 print!("Label: '{label}' ");
96 }
97 println!(" uuid: {}", info.uuid.as_hyphenated());
98 println!(
99 "\tTotal devices {} FS bytes used {}",
100 info.num_devices,
101 fmt_size(used_bytes, &mode)
102 );
103
104 for dev in &devices {
105 println!(
106 "\tdevid {:4} size {} used {} path {}",
107 dev.devid,
108 fmt_size(dev.total_bytes, &mode),
109 fmt_size(dev.bytes_used, &mode),
110 dev.path,
111 );
112 }
113 }
114
115 Ok(())
116 }
117}
118
119fn parse_btrfs_mounts() -> Result<Vec<String>> {
120 let contents = std::fs::read_to_string("/proc/self/mounts")
121 .context("failed to read /proc/self/mounts")?;
122 let mounts = contents
123 .lines()
124 .filter_map(|line| {
125 let mut fields = line.splitn(6, ' ');
126 let _device = fields.next()?;
127 let mountpoint = fields.next()?;
128 let fstype = fields.next()?;
129 if fstype == "btrfs" {
130 Some(mountpoint.to_owned())
131 } else {
132 None
133 }
134 })
135 .collect();
136 Ok(mounts)
137}