btrfs_cli/property/
set.rs1use super::{PropertyObjectType, detect_object_types};
2use crate::{Format, Runnable};
3use anyhow::{Context, Result, anyhow, bail};
4use btrfs_uapi::{
5 filesystem::label_set,
6 subvolume::{SubvolumeFlags, subvolume_flags_get, subvolume_flags_set},
7};
8use clap::Parser;
9use std::{ffi::CString, fs::File, os::unix::io::AsFd, path::PathBuf};
10
11#[derive(Parser, Debug)]
13pub struct PropertySetCommand {
14 pub object: PathBuf,
16
17 pub name: String,
19
20 pub value: String,
22
23 #[clap(short = 't', long = "type")]
25 pub object_type: Option<PropertyObjectType>,
26
27 #[clap(short = 'f', long)]
29 pub force: bool,
30}
31
32impl Runnable for PropertySetCommand {
33 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
34 let file = File::open(&self.object).with_context(|| {
35 format!("failed to open '{}'", self.object.display())
36 })?;
37
38 let detected_types = detect_object_types(&self.object);
40 let target_type = match self.object_type {
41 Some(t) => t,
42 None => {
43 if detected_types.len() > 1 {
45 bail!(
46 "object type is ambiguous, please use option -t (detected: {:?})",
47 detected_types
48 );
49 }
50 detected_types
51 .first()
52 .copied()
53 .ok_or_else(|| anyhow!("object is not a btrfs object"))?
54 }
55 };
56
57 set_property(
58 &file,
59 target_type,
60 &self.name,
61 &self.value,
62 self.force,
63 &self.object,
64 )?;
65
66 Ok(())
67 }
68}
69
70fn set_property(
71 file: &File,
72 obj_type: PropertyObjectType,
73 name: &str,
74 value: &str,
75 force: bool,
76 path: &PathBuf,
77) -> Result<()> {
78 match (obj_type, name) {
79 (PropertyObjectType::Subvol, "ro") => {
80 set_readonly_property(file, value, force, path)?;
81 }
82 (PropertyObjectType::Filesystem, "label")
83 | (PropertyObjectType::Device, "label") => {
84 let cstring = CString::new(value.as_bytes())
85 .context("label must not contain null bytes")?;
86 label_set(file.as_fd(), &cstring).with_context(|| {
87 format!("failed to set label for '{}'", path.display())
88 })?;
89 }
90 (PropertyObjectType::Inode, "compression") => {
91 set_compression_property(file, value, 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 set_readonly_property(
106 file: &File,
107 value: &str,
108 force: bool,
109 path: &PathBuf,
110) -> Result<()> {
111 let new_readonly = match value {
112 "true" => true,
113 "false" => false,
114 _ => bail!("invalid value for property: {}", value),
115 };
116
117 let current_flags =
118 subvolume_flags_get(file.as_fd()).with_context(|| {
119 format!("failed to get flags for '{}'", path.display())
120 })?;
121 let is_readonly = current_flags.contains(SubvolumeFlags::RDONLY);
122
123 if is_readonly == new_readonly {
125 return Ok(());
126 }
127
128 if is_readonly && !new_readonly {
130 let info = btrfs_uapi::subvolume::subvolume_info(file.as_fd())
131 .with_context(|| {
132 format!("failed to get subvolume info for '{}'", path.display())
133 })?;
134
135 if !info.received_uuid.is_nil() {
136 if !force {
137 bail!(
138 "cannot flip ro->rw with received_uuid set, use force option -f if you really want to unset the read-only status. \
139 The value of received_uuid is used for incremental send, consider making a snapshot instead."
140 );
141 }
142 }
143 }
144
145 let mut new_flags = current_flags;
146 if new_readonly {
147 new_flags |= SubvolumeFlags::RDONLY;
148 } else {
149 new_flags &= !SubvolumeFlags::RDONLY;
150 }
151
152 subvolume_flags_set(file.as_fd(), new_flags).with_context(|| {
153 format!("failed to set flags for '{}'", path.display())
154 })?;
155
156 if is_readonly && !new_readonly && force {
161 let info = btrfs_uapi::subvolume::subvolume_info(file.as_fd()).ok();
162 if let Some(info) = info {
163 if !info.received_uuid.is_nil() {
164 eprintln!(
165 "clearing received_uuid (was {})",
166 info.received_uuid.as_hyphenated()
167 );
168 if let Err(e) = btrfs_uapi::send_receive::received_subvol_set(
169 file.as_fd(),
170 &uuid::Uuid::nil(),
171 0,
172 ) {
173 eprintln!(
174 "WARNING: failed to clear received_uuid on '{}': {e}",
175 path.display()
176 );
177 }
178 }
179 }
180 }
181
182 Ok(())
183}
184
185fn set_compression_property(
186 file: &File,
187 value: &str,
188 path: &PathBuf,
189) -> Result<()> {
190 use nix::libc::fsetxattr;
191 use std::os::unix::io::AsRawFd;
192
193 let fd = file.as_raw_fd();
194 let xattr_name = "btrfs.compression\0";
195
196 let result = unsafe {
198 fsetxattr(
199 fd,
200 xattr_name.as_ptr() as *const i8,
201 value.as_ptr() as *const std::ffi::c_void,
202 value.len(),
203 0,
204 )
205 };
206
207 if result < 0 {
208 return Err(anyhow::anyhow!(
209 "failed to set compression for '{}': {}",
210 path.display(),
211 nix::errno::Errno::last()
212 ));
213 }
214
215 Ok(())
216}