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