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