Skip to main content

btrfs_cli/subvolume/
show.rs

1use crate::{Format, Runnable, util::ParsedUuid};
2use anyhow::{Context, Result};
3use btrfs_uapi::{
4    send_receive::subvolume_search_by_uuid,
5    subvolume::{subvolume_info, subvolume_info_by_id},
6};
7use clap::Parser;
8use std::{
9    fs::File,
10    mem,
11    os::unix::io::AsFd,
12    path::PathBuf,
13    time::{SystemTime, UNIX_EPOCH},
14};
15
16/// Show detailed information about a subvolume
17///
18/// Displays UUIDs, generation numbers, creation time, flags, and send/receive
19/// transaction IDs for the subvolume that contains the given path.
20///
21/// The subvolume can be specified by path (default), or by root id or UUID
22/// that are looked up relative to the given path.
23#[derive(Parser, Debug)]
24pub struct SubvolumeShowCommand {
25    /// Look up subvolume by its root ID instead of path
26    #[clap(short = 'r', long = "rootid")]
27    pub rootid: Option<u64>,
28
29    /// Look up subvolume by its UUID instead of path
30    #[clap(short = 'u', long = "uuid", conflicts_with = "rootid")]
31    pub uuid: Option<ParsedUuid>,
32
33    /// Path to a subvolume or any file within it
34    pub path: PathBuf,
35}
36
37impl Runnable for SubvolumeShowCommand {
38    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
39        let file = File::open(&self.path).with_context(|| {
40            format!("failed to open '{}'", self.path.display())
41        })?;
42
43        let info = if let Some(rootid) = self.rootid {
44            subvolume_info_by_id(file.as_fd(), rootid).with_context(|| {
45                format!("failed to get subvolume info for rootid {rootid}")
46            })?
47        } else if let Some(ref uuid) = self.uuid {
48            let inner = &**uuid;
49            let rootid = subvolume_search_by_uuid(file.as_fd(), inner)
50                .with_context(|| {
51                    format!("failed to find subvolume with UUID {inner}")
52                })?;
53            subvolume_info_by_id(file.as_fd(), rootid).with_context(|| {
54                format!("failed to get subvolume info for UUID {inner}")
55            })?
56        } else {
57            subvolume_info(file.as_fd()).with_context(|| {
58                format!(
59                    "failed to get subvolume info for '{}'",
60                    self.path.display()
61                )
62            })?
63        };
64
65        println!("{}", self.path.display());
66        println!("\tName: \t\t\t{}", info.name);
67        println!("\tUUID: \t\t\t{}", format_uuid(&info.uuid));
68        println!("\tParent UUID: \t\t{}", format_uuid(&info.parent_uuid));
69        println!("\tReceived UUID: \t\t{}", format_uuid(&info.received_uuid));
70        println!("\tCreation time: \t\t{}", format_time(info.otime));
71        println!("\tSubvolume ID: \t\t{}", info.id);
72        println!("\tGeneration: \t\t{}", info.generation);
73        println!("\tGen at creation: \t{}", info.otransid);
74        println!("\tParent ID: \t\t{}", info.parent_id);
75        println!("\tTop level ID: \t\t{}", info.parent_id);
76        println!("\tFlags: \t\t\t{}", info.flags);
77        println!("\tSend transid: \t\t{}", info.stransid);
78        println!("\tSend time: \t\t{}", format_time(info.stime));
79        println!("\tReceive transid: \t{}", info.rtransid);
80        println!("\tReceive time: \t\t{}", format_time(info.rtime));
81
82        Ok(())
83    }
84}
85
86fn format_uuid(uuid: &uuid::Uuid) -> String {
87    if uuid.is_nil() {
88        "-".to_string()
89    } else {
90        uuid.hyphenated().to_string()
91    }
92}
93
94/// Format a [`SystemTime`] as a local-time datetime string in the same style
95/// as the C btrfs-progs tool: `YYYY-MM-DD HH:MM:SS ±HHMM`.
96///
97/// Returns `"-"` when the time is [`UNIX_EPOCH`] (i.e. not set).
98fn format_time(t: SystemTime) -> String {
99    if t == UNIX_EPOCH {
100        return "-".to_string();
101    }
102
103    let secs = match t.duration_since(UNIX_EPOCH) {
104        Ok(d) => d.as_secs() as nix::libc::time_t,
105        Err(_) => return "-".to_string(),
106    };
107
108    // SAFETY: localtime_r is async-signal-safe and writes into the tm we
109    // provide; we pass a valid pointer and the output is fully initialised
110    // before we read it.
111    let mut tm: nix::libc::tm = unsafe { mem::zeroed() };
112    let result = unsafe { nix::libc::localtime_r(&secs, &mut tm) };
113    if result.is_null() {
114        return "-".to_string();
115    }
116
117    let year = tm.tm_year + 1900;
118    let mon = tm.tm_mon + 1;
119    let mday = tm.tm_mday;
120    let hour = tm.tm_hour;
121    let min = tm.tm_min;
122    let sec = tm.tm_sec;
123
124    // tm_gmtoff is the UTC offset in seconds (positive = east of UTC).
125    let gmtoff = tm.tm_gmtoff; // seconds
126    let off_sign = if gmtoff < 0 { '-' } else { '+' };
127    let off_abs = gmtoff.unsigned_abs();
128    let off_h = off_abs / 3600;
129    let off_m = (off_abs % 3600) / 60;
130
131    format!(
132        "{year:04}-{mon:02}-{mday:02} {hour:02}:{min:02}:{sec:02} {off_sign}{off_h:02}{off_m:02}"
133    )
134}