Skip to main content

btrfs_cli/filesystem/
show.rs

1use 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/// Show information about one or more mounted or unmounted filesystems
13#[derive(Parser, Debug)]
14pub struct FilesystemShowCommand {
15    /// Search all devices, including unmounted ones
16    #[clap(long, short = 'd')]
17    pub all_devices: bool,
18
19    /// Search only mounted filesystems
20    #[clap(long, short)]
21    pub mounted: bool,
22
23    #[clap(flatten)]
24    pub units: UnitMode,
25
26    /// Path, UUID, device or label to show (shows all if omitted)
27    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}