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 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}