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