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 = if let Some(t) = self.object_type {
44 t
45 } else {
46 if detected_types.len() > 1 {
48 bail!(
49 "object type is ambiguous, please use option -t (detected: {detected_types:?})"
50 );
51 }
52 detected_types
53 .first()
54 .copied()
55 .ok_or_else(|| anyhow!("object is not a btrfs object"))?
56 };
57
58 set_property(
59 &file,
60 target_type,
61 &self.name,
62 &self.value,
63 self.force,
64 &self.object,
65 )?;
66
67 Ok(())
68 }
69}
70
71fn set_property(
72 file: &File,
73 obj_type: PropertyObjectType,
74 name: &str,
75 value: &str,
76 force: bool,
77 path: &Path,
78) -> Result<()> {
79 match (obj_type, name) {
80 (PropertyObjectType::Subvol, "ro") => {
81 set_readonly_property(file, value, force, path)?;
82 }
83 (
84 PropertyObjectType::Filesystem | PropertyObjectType::Device,
85 "label",
86 ) => {
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 '{name}' is not applicable to object type {obj_type:?}"
99 );
100 }
101 }
102
103 Ok(())
104}
105
106fn set_readonly_property(
107 file: &File,
108 value: &str,
109 force: bool,
110 path: &Path,
111) -> Result<()> {
112 let new_readonly = match value {
113 "true" => true,
114 "false" => false,
115 _ => bail!("invalid value for property: {value}"),
116 };
117
118 let current_flags =
119 subvolume_flags_get(file.as_fd()).with_context(|| {
120 format!("failed to get flags for '{}'", path.display())
121 })?;
122 let is_readonly = current_flags.contains(SubvolumeFlags::RDONLY);
123
124 if is_readonly == new_readonly {
126 return Ok(());
127 }
128
129 if is_readonly && !new_readonly {
131 let info = btrfs_uapi::subvolume::subvolume_info(file.as_fd())
132 .with_context(|| {
133 format!("failed to get subvolume info for '{}'", path.display())
134 })?;
135
136 if !info.received_uuid.is_nil() && !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 let mut new_flags = current_flags;
145 if new_readonly {
146 new_flags |= SubvolumeFlags::RDONLY;
147 } else {
148 new_flags &= !SubvolumeFlags::RDONLY;
149 }
150
151 subvolume_flags_set(file.as_fd(), new_flags).with_context(|| {
152 format!("failed to set flags for '{}'", path.display())
153 })?;
154
155 if is_readonly && !new_readonly && force {
160 let info = btrfs_uapi::subvolume::subvolume_info(file.as_fd()).ok();
161 if let Some(info) = info
162 && !info.received_uuid.is_nil()
163 {
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 Ok(())
182}
183
184fn set_compression_property(
185 file: &File,
186 value: &str,
187 path: &Path,
188) -> Result<()> {
189 use nix::libc::fsetxattr;
190 use std::os::unix::io::AsRawFd;
191
192 let fd = file.as_raw_fd();
193 let xattr_name = "btrfs.compression\0";
194
195 let result = unsafe {
197 fsetxattr(
198 fd,
199 xattr_name.as_ptr() as *const i8,
200 value.as_ptr() as *const std::ffi::c_void,
201 value.len(),
202 0,
203 )
204 };
205
206 if result < 0 {
207 return Err(anyhow::anyhow!(
208 "failed to set compression for '{}': {}",
209 path.display(),
210 nix::errno::Errno::last()
211 ));
212 }
213
214 Ok(())
215}