Skip to main content

btrfs_cli/property/
get.rs

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