Skip to main content

btrfs_cli/filesystem/
show.rs

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