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 file = match File::open(mount) {
49 Ok(f) => f,
50 Err(_) => continue,
51 };
52 let fd = file.as_fd();
53
54 let info = match filesystem_info(fd) {
55 Ok(i) => i,
56 Err(_) => continue,
57 };
58
59 if let Some(filter) = &self.filter {
60 let uuid_str = info.uuid.as_hyphenated().to_string();
61 let label = label_get(fd).unwrap_or_default();
62 let label_str = label.to_string_lossy();
63 if mount != filter
64 && uuid_str != *filter
65 && label_str != filter.as_str()
66 {
67 continue;
68 }
69 }
70
71 if !seen_uuids.insert(info.uuid) {
72 continue;
73 }
74
75 let label = label_get(fd)
76 .map(|l| l.to_string_lossy().into_owned())
77 .unwrap_or_default();
78
79 let devices = device_info_all(fd, &info).with_context(|| {
80 format!("failed to get device info for '{mount}'")
81 })?;
82
83 let used_bytes = space_info(fd)
84 .map(|entries| {
85 entries.iter().map(|e| e.used_bytes).sum::<u64>()
86 })
87 .unwrap_or(0);
88
89 if !first {
90 println!();
91 }
92 first = false;
93
94 if label.is_empty() {
95 print!("Label: none ");
96 } else {
97 print!("Label: '{label}' ");
98 }
99 println!(" uuid: {}", info.uuid.as_hyphenated());
100 println!(
101 "\tTotal devices {} FS bytes used {}",
102 info.num_devices,
103 fmt_size(used_bytes, &mode)
104 );
105
106 for dev in &devices {
107 println!(
108 "\tdevid {:4} size {} used {} path {}",
109 dev.devid,
110 fmt_size(dev.total_bytes, &mode),
111 fmt_size(dev.bytes_used, &mode),
112 dev.path,
113 );
114 }
115 }
116
117 Ok(())
118 }
119}
120
121fn parse_btrfs_mounts() -> Result<Vec<String>> {
122 let contents = std::fs::read_to_string("/proc/self/mounts")
123 .context("failed to read /proc/self/mounts")?;
124 let mounts = contents
125 .lines()
126 .filter_map(|line| {
127 let mut fields = line.splitn(6, ' ');
128 let _device = fields.next()?;
129 let mountpoint = fields.next()?;
130 let fstype = fields.next()?;
131 if fstype == "btrfs" {
132 Some(mountpoint.to_owned())
133 } else {
134 None
135 }
136 })
137 .collect();
138 Ok(mounts)
139}