btrfs_cli/property/
get.rs1use 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#[derive(Parser, Debug)]
12pub struct PropertyGetCommand {
13 #[clap(short = 't', long = "type")]
15 pub object_type: Option<PropertyObjectType>,
16
17 pub object: PathBuf,
19
20 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 let detected_types = detect_object_types(&self.object);
32 let target_type = match self.object_type {
33 Some(t) => t,
34 None => {
35 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 let Some(name) = &self.name {
51 get_property(&file, target_type, name, &self.object)?;
52 } else {
53 for name in property_names(target_type) {
55 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 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 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 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}