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