btrfs_cli/property/
get.rs1use super::{PropertyObjectType, detect_object_types, property_names};
2use crate::{RunContext, 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#[derive(Parser, Debug)]
16pub struct PropertyGetCommand {
17 #[clap(short = 't', long = "type")]
19 pub object_type: Option<PropertyObjectType>,
20
21 pub object: PathBuf,
23
24 pub name: Option<String>,
26}
27
28impl Runnable for PropertyGetCommand {
29 fn run(&self, _ctx: &RunContext) -> Result<()> {
30 let file = open_path(&self.object)?;
31
32 let detected_types = detect_object_types(&self.object);
34 let target_type = if let Some(t) = self.object_type {
35 t
36 } else {
37 if detected_types.len() > 1 {
39 bail!(
40 "object type is ambiguous, please use option -t (detected: {detected_types:?})"
41 );
42 }
43 detected_types
44 .first()
45 .copied()
46 .ok_or_else(|| anyhow!("object is not a btrfs object"))?
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: &Path,
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 (
84 PropertyObjectType::Filesystem | PropertyObjectType::Device,
85 "label",
86 ) => {
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 '{name}' is not applicable to object type {obj_type:?}"
98 );
99 }
100 }
101
102 Ok(())
103}
104
105fn get_compression_property(file: &File, path: &Path) -> 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(fd, xattr_name.as_ptr().cast(), std::ptr::null_mut(), 0)
115 };
116
117 if result < 0 {
118 let errno = nix::errno::Errno::last_raw();
119 if errno == ENODATA {
120 return Ok(());
122 }
123 return Err(anyhow::anyhow!(
124 "failed to get compression for '{}': {}",
125 path.display(),
126 nix::errno::Errno::from_raw(errno)
127 ));
128 }
129
130 #[allow(clippy::cast_sign_loss)] let len = result as usize;
132 let mut buf = vec![0u8; len];
133
134 let result = unsafe {
136 fgetxattr(fd, xattr_name.as_ptr().cast(), buf.as_mut_ptr().cast(), len)
137 };
138
139 if result < 0 {
140 return Err(anyhow::anyhow!(
141 "failed to get compression for '{}'",
142 path.display()
143 ));
144 }
145
146 let value = String::from_utf8_lossy(&buf);
147 println!("compression={value}");
148
149 Ok(())
150}