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