Skip to main content

btrfs_cli/property/
get.rs

1use super::{PropertyObjectType, detect_object_types, property_names};
2use crate::{Format, Runnable};
3use anyhow::{Context, Result, anyhow, bail};
4use btrfs_uapi::{filesystem::label_get, subvolume::subvolume_flags_get};
5use clap::Parser;
6use std::{fs::File, os::unix::io::AsFd, path::PathBuf};
7
8/// Get a property value of a btrfs object
9///
10/// If no name is specified, all properties for the object are printed.
11#[derive(Parser, Debug)]
12pub struct PropertyGetCommand {
13    /// Object type (inode, subvol, filesystem, device)
14    #[clap(short = 't', long = "type")]
15    pub object_type: Option<PropertyObjectType>,
16
17    /// Path to the btrfs object
18    pub object: PathBuf,
19
20    /// Property name to retrieve
21    pub name: Option<String>,
22}
23
24impl Runnable for PropertyGetCommand {
25    fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
26        let file = File::open(&self.object).with_context(|| {
27            format!("failed to open '{}'", self.object.display())
28        })?;
29
30        // Detect object type if not specified
31        let detected_types = detect_object_types(&self.object);
32        let target_type = match self.object_type {
33            Some(t) => t,
34            None => {
35                // If ambiguous, require the user to specify
36                if detected_types.len() > 1 {
37                    bail!(
38                        "object type is ambiguous, please use option -t (detected: {:?})",
39                        detected_types
40                    );
41                }
42                detected_types
43                    .first()
44                    .copied()
45                    .ok_or_else(|| anyhow!("object is not a btrfs object"))?
46            }
47        };
48
49        // If a specific property is requested, get it
50        if let Some(name) = &self.name {
51            get_property(&file, target_type, name, &self.object)?;
52        } else {
53            // Otherwise, list all properties with their values
54            for name in property_names(target_type) {
55                // Best-effort: print what we can, skip errors for individual properties
56                let _ = get_property(&file, target_type, name, &self.object);
57            }
58        }
59
60        Ok(())
61    }
62}
63
64fn get_property(
65    file: &File,
66    obj_type: PropertyObjectType,
67    name: &str,
68    path: &PathBuf,
69) -> Result<()> {
70    match (obj_type, name) {
71        (PropertyObjectType::Subvol, "ro") => {
72            let flags =
73                subvolume_flags_get(file.as_fd()).with_context(|| {
74                    format!(
75                        "failed to get read-only flag for '{}'",
76                        path.display()
77                    )
78                })?;
79            let is_readonly =
80                flags.contains(btrfs_uapi::subvolume::SubvolumeFlags::RDONLY);
81            println!("ro={}", if is_readonly { "true" } else { "false" });
82        }
83        (PropertyObjectType::Filesystem, "label")
84        | (PropertyObjectType::Device, "label") => {
85            let label = label_get(file.as_fd()).with_context(|| {
86                format!("failed to get label for '{}'", path.display())
87            })?;
88            println!("label={}", label.to_bytes().escape_ascii());
89        }
90        (PropertyObjectType::Inode, "compression") => {
91            get_compression_property(file, path)?;
92        }
93        _ => {
94            bail!(
95                "property '{}' is not applicable to object type {:?}",
96                name,
97                obj_type
98            );
99        }
100    }
101
102    Ok(())
103}
104
105fn get_compression_property(file: &File, path: &PathBuf) -> Result<()> {
106    use nix::libc::{ENODATA, fgetxattr};
107    use std::os::unix::io::AsRawFd;
108
109    let fd = file.as_raw_fd();
110    let xattr_name = "btrfs.compression\0";
111
112    // SAFETY: fgetxattr is safe to call with a valid fd and valid string pointer
113    let result = unsafe {
114        fgetxattr(
115            fd,
116            xattr_name.as_ptr() as *const i8,
117            std::ptr::null_mut(),
118            0,
119        )
120    };
121
122    if result < 0 {
123        let errno = nix::errno::Errno::last_raw();
124        if errno == ENODATA {
125            // Attribute doesn't exist; compression not set
126            return Ok(());
127        } else {
128            return Err(anyhow::anyhow!(
129                "failed to get compression for '{}': {}",
130                path.display(),
131                nix::errno::Errno::from_raw(errno)
132            ));
133        }
134    }
135
136    let len = result as usize;
137    let mut buf = vec![0u8; len];
138
139    // SAFETY: fgetxattr is safe to call with a valid fd, valid buffer, and valid string pointer
140    let result = unsafe {
141        fgetxattr(
142            fd,
143            xattr_name.as_ptr() as *const i8,
144            buf.as_mut_ptr() as *mut std::ffi::c_void,
145            len,
146        )
147    };
148
149    if result < 0 {
150        return Err(anyhow::anyhow!(
151            "failed to get compression for '{}'",
152            path.display()
153        ));
154    }
155
156    let value = String::from_utf8_lossy(&buf);
157    println!("compression={}", value);
158
159    Ok(())
160}