]>
Commit | Line | Data |
---|---|---|
50bf10ad | 1 | use anyhow::{bail, Error}; |
38ae42b1 | 2 | use ::serde::{Deserialize, Serialize}; |
50bf10ad DM |
3 | use serde_json::Value; |
4 | ||
38ae42b1 DM |
5 | use proxmox::api::{ |
6 | api, | |
7 | Router, | |
8 | RpcEnvironment, | |
8cd63df0 | 9 | Permission, |
38ae42b1 DM |
10 | schema::parse_property_string, |
11 | }; | |
50bf10ad | 12 | |
1ce8e905 | 13 | use pbs_api_types::{ |
8cc3760e DM |
14 | Authid, ScsiTapeChanger, ScsiTapeChangerUpdater, LtoTapeDrive, |
15 | PROXMOX_CONFIG_DIGEST_SCHEMA, CHANGER_NAME_SCHEMA, SLOT_ARRAY_SCHEMA, | |
16 | PRIV_TAPE_AUDIT, PRIV_TAPE_MODIFY, | |
1ce8e905 DM |
17 | }; |
18 | ||
50bf10ad | 19 | use crate::{ |
8cc3760e | 20 | config::cached_user_info::CachedUserInfo, |
645a044b | 21 | tape::{ |
50bf10ad DM |
22 | linux_tape_changer_list, |
23 | check_drive_path, | |
50bf10ad DM |
24 | }, |
25 | }; | |
26 | ||
27 | #[api( | |
314652a4 | 28 | protected: true, |
50bf10ad DM |
29 | input: { |
30 | properties: { | |
5af3bcf0 DM |
31 | config: { |
32 | type: ScsiTapeChanger, | |
33 | flatten: true, | |
38ae42b1 | 34 | }, |
50bf10ad DM |
35 | }, |
36 | }, | |
8cd63df0 | 37 | access: { |
ee33795b | 38 | permission: &Permission::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY, false), |
8cd63df0 | 39 | }, |
50bf10ad DM |
40 | )] |
41 | /// Create a new changer device | |
5af3bcf0 | 42 | pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> { |
50bf10ad | 43 | |
1ce8e905 | 44 | let _lock = pbs_config::drive::lock()?; |
50bf10ad | 45 | |
5af3bcf0 | 46 | let (mut section_config, _digest) = pbs_config::drive::config()?; |
50bf10ad DM |
47 | |
48 | let linux_changers = linux_tape_changer_list(); | |
49 | ||
5af3bcf0 | 50 | check_drive_path(&linux_changers, &config.path)?; |
50bf10ad | 51 | |
5af3bcf0 | 52 | let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?; |
b03ec281 DC |
53 | |
54 | for changer in existing { | |
5af3bcf0 DM |
55 | if changer.name == config.name { |
56 | bail!("Entry '{}' already exists", config.name); | |
b03ec281 DC |
57 | } |
58 | ||
5af3bcf0 DM |
59 | if changer.path == config.path { |
60 | bail!("Path '{}' already in use by '{}'", config.path, changer.name); | |
b03ec281 | 61 | } |
50bf10ad DM |
62 | } |
63 | ||
5af3bcf0 | 64 | section_config.set_data(&config.name, "changer", &config)?; |
50bf10ad | 65 | |
5af3bcf0 | 66 | pbs_config::drive::save_config(§ion_config)?; |
50bf10ad DM |
67 | |
68 | Ok(()) | |
69 | } | |
70 | ||
71 | #[api( | |
72 | input: { | |
73 | properties: { | |
74 | name: { | |
7e1d4712 | 75 | schema: CHANGER_NAME_SCHEMA, |
50bf10ad DM |
76 | }, |
77 | }, | |
78 | }, | |
79 | returns: { | |
80 | type: ScsiTapeChanger, | |
81 | }, | |
8cd63df0 | 82 | access: { |
ee33795b | 83 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false), |
8cd63df0 | 84 | }, |
50bf10ad DM |
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 | ||
1ce8e905 | 93 | let (config, digest) = pbs_config::drive::config()?; |
50bf10ad DM |
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: { | |
b5b99a52 | 110 | type: ScsiTapeChanger, |
50bf10ad DM |
111 | }, |
112 | }, | |
8cd63df0 DM |
113 | access: { |
114 | description: "List configured tape changer filtered by Tape.Audit privileges", | |
115 | permission: &Permission::Anybody, | |
116 | }, | |
50bf10ad DM |
117 | )] |
118 | /// List changers | |
119 | pub fn list_changers( | |
120 | _param: Value, | |
121 | mut rpcenv: &mut dyn RpcEnvironment, | |
b5b99a52 | 122 | ) -> Result<Vec<ScsiTapeChanger>, Error> { |
8cd63df0 DM |
123 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; |
124 | let user_info = CachedUserInfo::new()?; | |
50bf10ad | 125 | |
1ce8e905 | 126 | let (config, digest) = pbs_config::drive::config()?; |
50bf10ad | 127 | |
b5b99a52 | 128 | let list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?; |
50bf10ad | 129 | |
8cd63df0 DM |
130 | let list = list |
131 | .into_iter() | |
132 | .filter(|changer| { | |
ee33795b | 133 | let privs = user_info.lookup_privs(&auth_id, &["tape", "device", &changer.name]); |
8cd63df0 DM |
134 | privs & PRIV_TAPE_AUDIT != 0 |
135 | }) | |
136 | .collect(); | |
137 | ||
50bf10ad | 138 | rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into(); |
b5b99a52 | 139 | |
50bf10ad DM |
140 | Ok(list) |
141 | } | |
38ae42b1 DM |
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 | } | |
50bf10ad DM |
151 | |
152 | #[api( | |
314652a4 | 153 | protected: true, |
50bf10ad DM |
154 | input: { |
155 | properties: { | |
156 | name: { | |
7e1d4712 | 157 | schema: CHANGER_NAME_SCHEMA, |
50bf10ad | 158 | }, |
5af3bcf0 DM |
159 | update: { |
160 | type: ScsiTapeChangerUpdater, | |
161 | flatten: true, | |
38ae42b1 DM |
162 | }, |
163 | delete: { | |
164 | description: "List of properties to delete.", | |
165 | type: Array, | |
166 | optional: true, | |
167 | items: { | |
168 | type: DeletableProperty, | |
169 | }, | |
170 | }, | |
5ba83ed0 DM |
171 | digest: { |
172 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
173 | optional: true, | |
174 | }, | |
175 | }, | |
50bf10ad | 176 | }, |
8cd63df0 | 177 | access: { |
ee33795b | 178 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false), |
8cd63df0 | 179 | }, |
50bf10ad DM |
180 | )] |
181 | /// Update a tape changer configuration | |
182 | pub fn update_changer( | |
183 | name: String, | |
5af3bcf0 | 184 | update: ScsiTapeChangerUpdater, |
38ae42b1 | 185 | delete: Option<Vec<DeletableProperty>>, |
5ba83ed0 | 186 | digest: Option<String>, |
50bf10ad DM |
187 | _param: Value, |
188 | ) -> Result<(), Error> { | |
189 | ||
1ce8e905 | 190 | let _lock = pbs_config::drive::lock()?; |
50bf10ad | 191 | |
1ce8e905 | 192 | let (mut config, expected_digest) = pbs_config::drive::config()?; |
5ba83ed0 DM |
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 | } | |
50bf10ad DM |
198 | |
199 | let mut data: ScsiTapeChanger = config.lookup("changer", &name)?; | |
200 | ||
38ae42b1 DM |
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 | ||
5af3bcf0 | 211 | if let Some(path) = update.path { |
50bf10ad DM |
212 | let changers = linux_tape_changer_list(); |
213 | check_drive_path(&changers, &path)?; | |
214 | data.path = path; | |
215 | } | |
216 | ||
5af3bcf0 | 217 | if let Some(export_slots) = update.export_slots { |
38ae42b1 DM |
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 | ||
50bf10ad DM |
237 | config.set_data(&name, "changer", &data)?; |
238 | ||
1ce8e905 | 239 | pbs_config::drive::save_config(&config)?; |
50bf10ad DM |
240 | |
241 | Ok(()) | |
242 | } | |
243 | ||
244 | #[api( | |
314652a4 | 245 | protected: true, |
50bf10ad DM |
246 | input: { |
247 | properties: { | |
248 | name: { | |
7e1d4712 | 249 | schema: CHANGER_NAME_SCHEMA, |
50bf10ad DM |
250 | }, |
251 | }, | |
252 | }, | |
8cd63df0 | 253 | access: { |
ee33795b | 254 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false), |
8cd63df0 | 255 | }, |
50bf10ad DM |
256 | )] |
257 | /// Delete a tape changer configuration | |
258 | pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> { | |
259 | ||
1ce8e905 | 260 | let _lock = pbs_config::drive::lock()?; |
50bf10ad | 261 | |
1ce8e905 | 262 | let (mut config, _digest) = pbs_config::drive::config()?; |
50bf10ad DM |
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 | ||
a79082a0 | 274 | let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?; |
43cfb3c3 DM |
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 | ||
1ce8e905 | 283 | pbs_config::drive::save_config(&config)?; |
50bf10ad DM |
284 | |
285 | Ok(()) | |
286 | } | |
287 | ||
50bf10ad DM |
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); |