]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/changer.rs
clippy 1.65 fixes
[proxmox-backup.git] / src / api2 / config / changer.rs
1 use ::serde::{Deserialize, Serialize};
2 use anyhow::Error;
3 use hex::FromHex;
4 use serde_json::Value;
5
6 use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
7 use proxmox_schema::{api, param_bail};
8
9 use pbs_api_types::{
10 Authid, LtoTapeDrive, ScsiTapeChanger, ScsiTapeChangerUpdater, CHANGER_NAME_SCHEMA,
11 PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, SLOT_ARRAY_SCHEMA,
12 };
13 use pbs_config::CachedUserInfo;
14 use pbs_tape::linux_list_drives::{check_drive_path, linux_tape_changer_list};
15
16 #[api(
17 protected: true,
18 input: {
19 properties: {
20 config: {
21 type: ScsiTapeChanger,
22 flatten: true,
23 },
24 },
25 },
26 access: {
27 permission: &Permission::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY, false),
28 },
29 )]
30 /// Create a new changer device
31 pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
32 let _lock = pbs_config::drive::lock()?;
33
34 let (mut section_config, _digest) = pbs_config::drive::config()?;
35
36 let linux_changers = linux_tape_changer_list();
37
38 check_drive_path(&linux_changers, &config.path)?;
39
40 let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?;
41
42 for changer in existing {
43 if changer.name == config.name {
44 param_bail!("name", "Entry '{}' already exists", config.name);
45 }
46
47 if changer.path == config.path {
48 param_bail!(
49 "path",
50 "Path '{}' already in use by '{}'",
51 config.path,
52 changer.name
53 );
54 }
55 }
56
57 section_config.set_data(&config.name, "changer", &config)?;
58
59 pbs_config::drive::save_config(&section_config)?;
60
61 Ok(())
62 }
63
64 #[api(
65 input: {
66 properties: {
67 name: {
68 schema: CHANGER_NAME_SCHEMA,
69 },
70 },
71 },
72 returns: {
73 type: ScsiTapeChanger,
74 },
75 access: {
76 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
77 },
78 )]
79 /// Get tape changer configuration
80 pub fn get_config(
81 name: String,
82 _param: Value,
83 rpcenv: &mut dyn RpcEnvironment,
84 ) -> Result<ScsiTapeChanger, Error> {
85 let (config, digest) = pbs_config::drive::config()?;
86
87 let data: ScsiTapeChanger = config.lookup("changer", &name)?;
88
89 rpcenv["digest"] = hex::encode(digest).into();
90
91 Ok(data)
92 }
93
94 #[api(
95 input: {
96 properties: {},
97 },
98 returns: {
99 description: "The list of configured changers (with config digest).",
100 type: Array,
101 items: {
102 type: ScsiTapeChanger,
103 },
104 },
105 access: {
106 description: "List configured tape changer filtered by Tape.Audit privileges",
107 permission: &Permission::Anybody,
108 },
109 )]
110 /// List changers
111 pub fn list_changers(
112 _param: Value,
113 rpcenv: &mut dyn RpcEnvironment,
114 ) -> Result<Vec<ScsiTapeChanger>, Error> {
115 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
116 let user_info = CachedUserInfo::new()?;
117
118 let (config, digest) = pbs_config::drive::config()?;
119
120 let list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
121
122 let list = list
123 .into_iter()
124 .filter(|changer| {
125 let privs = user_info.lookup_privs(&auth_id, &["tape", "device", &changer.name]);
126 privs & PRIV_TAPE_AUDIT != 0
127 })
128 .collect();
129
130 rpcenv["digest"] = hex::encode(digest).into();
131
132 Ok(list)
133 }
134 #[api()]
135 #[derive(Serialize, Deserialize)]
136 #[allow(non_camel_case_types)]
137 #[serde(rename_all = "kebab-case")]
138 /// Deletable property name
139 pub enum DeletableProperty {
140 /// Delete export-slots.
141 export_slots,
142 }
143
144 #[api(
145 protected: true,
146 input: {
147 properties: {
148 name: {
149 schema: CHANGER_NAME_SCHEMA,
150 },
151 update: {
152 type: ScsiTapeChangerUpdater,
153 flatten: true,
154 },
155 delete: {
156 description: "List of properties to delete.",
157 type: Array,
158 optional: true,
159 items: {
160 type: DeletableProperty,
161 },
162 },
163 digest: {
164 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
165 optional: true,
166 },
167 },
168 },
169 access: {
170 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
171 },
172 )]
173 /// Update a tape changer configuration
174 pub fn update_changer(
175 name: String,
176 update: ScsiTapeChangerUpdater,
177 delete: Option<Vec<DeletableProperty>>,
178 digest: Option<String>,
179 _param: Value,
180 ) -> Result<(), Error> {
181 let _lock = pbs_config::drive::lock()?;
182
183 let (mut config, expected_digest) = pbs_config::drive::config()?;
184
185 if let Some(ref digest) = digest {
186 let digest = <[u8; 32]>::from_hex(digest)?;
187 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
188 }
189
190 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
191
192 if let Some(delete) = delete {
193 for delete_prop in delete {
194 match delete_prop {
195 DeletableProperty::export_slots => {
196 data.export_slots = None;
197 }
198 }
199 }
200 }
201
202 if let Some(path) = update.path {
203 let changers = linux_tape_changer_list();
204 check_drive_path(&changers, &path)?;
205 data.path = path;
206 }
207
208 if let Some(export_slots) = update.export_slots {
209 let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(&export_slots)?;
210 let mut slots: Vec<String> = slots
211 .as_array()
212 .unwrap()
213 .iter()
214 .map(|v| v.to_string())
215 .collect();
216 slots.sort();
217
218 if slots.is_empty() {
219 data.export_slots = None;
220 } else {
221 let slots = slots.join(",");
222 data.export_slots = Some(slots);
223 }
224 }
225
226 config.set_data(&name, "changer", &data)?;
227
228 pbs_config::drive::save_config(&config)?;
229
230 Ok(())
231 }
232
233 #[api(
234 protected: true,
235 input: {
236 properties: {
237 name: {
238 schema: CHANGER_NAME_SCHEMA,
239 },
240 },
241 },
242 access: {
243 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
244 },
245 )]
246 /// Delete a tape changer configuration
247 pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
248 let _lock = pbs_config::drive::lock()?;
249
250 let (mut config, _digest) = pbs_config::drive::config()?;
251
252 match config.sections.get(&name) {
253 Some((section_type, _)) => {
254 if section_type != "changer" {
255 param_bail!(
256 "name",
257 "Entry '{}' exists, but is not a changer device",
258 name
259 );
260 }
261 config.sections.remove(&name);
262 }
263 None => http_bail!(
264 NOT_FOUND,
265 "Delete changer '{}' failed - no such entry",
266 name
267 ),
268 }
269
270 let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
271 for drive in drive_list {
272 if let Some(changer) = drive.changer {
273 if changer == name {
274 param_bail!(
275 "name",
276 "Delete changer '{}' failed - used by drive '{}'",
277 name,
278 drive.name
279 );
280 }
281 }
282 }
283
284 pbs_config::drive::save_config(&config)?;
285
286 Ok(())
287 }
288
289 const ITEM_ROUTER: Router = Router::new()
290 .get(&API_METHOD_GET_CONFIG)
291 .put(&API_METHOD_UPDATE_CHANGER)
292 .delete(&API_METHOD_DELETE_CHANGER);
293
294 pub const ROUTER: Router = Router::new()
295 .get(&API_METHOD_LIST_CHANGERS)
296 .post(&API_METHOD_CREATE_CHANGER)
297 .match_all("name", &ITEM_ROUTER);