use super::{PropertyObjectType, detect_object_types, property_names};
use crate::{RunContext, Runnable, util::open_path};
use anyhow::{Context, Result, anyhow, bail};
use btrfs_uapi::{filesystem::label_get, subvolume::subvolume_flags_get};
use clap::Parser;
use std::{
fs::File,
os::unix::io::AsFd,
path::{Path, PathBuf},
};
#[derive(Parser, Debug)]
pub struct PropertyGetCommand {
#[clap(short = 't', long = "type")]
pub object_type: Option<PropertyObjectType>,
pub object: PathBuf,
pub name: Option<String>,
}
impl Runnable for PropertyGetCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
let file = open_path(&self.object)?;
let detected_types = detect_object_types(&self.object);
let target_type = if let Some(t) = self.object_type {
t
} else {
if detected_types.len() > 1 {
bail!(
"object type is ambiguous, please use option -t (detected: {detected_types:?})"
);
}
detected_types
.first()
.copied()
.ok_or_else(|| anyhow!("object is not a btrfs object"))?
};
if let Some(name) = &self.name {
get_property(&file, target_type, name, &self.object)?;
} else {
for name in property_names(target_type) {
let _ = get_property(&file, target_type, name, &self.object);
}
}
Ok(())
}
}
fn get_property(
file: &File,
obj_type: PropertyObjectType,
name: &str,
path: &Path,
) -> Result<()> {
match (obj_type, name) {
(PropertyObjectType::Subvol, "ro") => {
let flags =
subvolume_flags_get(file.as_fd()).with_context(|| {
format!(
"failed to get read-only flag for '{}'",
path.display()
)
})?;
let is_readonly =
flags.contains(btrfs_uapi::subvolume::SubvolumeFlags::RDONLY);
println!("ro={}", if is_readonly { "true" } else { "false" });
}
(
PropertyObjectType::Filesystem | PropertyObjectType::Device,
"label",
) => {
let label = label_get(file.as_fd()).with_context(|| {
format!("failed to get label for '{}'", path.display())
})?;
println!("label={}", label.to_bytes().escape_ascii());
}
(PropertyObjectType::Inode, "compression") => {
get_compression_property(file, path)?;
}
_ => {
bail!(
"property '{name}' is not applicable to object type {obj_type:?}"
);
}
}
Ok(())
}
fn get_compression_property(file: &File, path: &Path) -> Result<()> {
use nix::libc::{ENODATA, fgetxattr};
use std::os::unix::io::AsRawFd;
let fd = file.as_raw_fd();
let xattr_name = "btrfs.compression\0";
let result = unsafe {
fgetxattr(fd, xattr_name.as_ptr().cast(), std::ptr::null_mut(), 0)
};
if result < 0 {
let errno = nix::errno::Errno::last_raw();
if errno == ENODATA {
return Ok(());
}
return Err(anyhow::anyhow!(
"failed to get compression for '{}': {}",
path.display(),
nix::errno::Errno::from_raw(errno)
));
}
#[allow(clippy::cast_sign_loss)] let len = result as usize;
let mut buf = vec![0u8; len];
let result = unsafe {
fgetxattr(fd, xattr_name.as_ptr().cast(), buf.as_mut_ptr().cast(), len)
};
if result < 0 {
return Err(anyhow::anyhow!(
"failed to get compression for '{}'",
path.display()
));
}
let value = String::from_utf8_lossy(&buf);
println!("compression={value}");
Ok(())
}