]>
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 DM |
35 | |
36 | let linux_changers = linux_tape_changer_list(); | |
37 | ||
5af3bcf0 | 38 | check_drive_path(&linux_changers, &config.path)?; |
50bf10ad | 39 | |
5af3bcf0 | 40 | let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?; |
b03ec281 DC |
41 | |
42 | for changer in existing { | |
5af3bcf0 | 43 | if changer.name == config.name { |
8d6425aa | 44 | param_bail!("name", "Entry '{}' already exists", config.name); |
b03ec281 DC |
45 | } |
46 | ||
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, |
38ae42b1 | 141 | } |
50bf10ad DM |
142 | |
143 | #[api( | |
314652a4 | 144 | protected: true, |
50bf10ad DM |
145 | input: { |
146 | properties: { | |
147 | name: { | |
7e1d4712 | 148 | schema: CHANGER_NAME_SCHEMA, |
50bf10ad | 149 | }, |
5af3bcf0 DM |
150 | update: { |
151 | type: ScsiTapeChangerUpdater, | |
152 | flatten: true, | |
38ae42b1 DM |
153 | }, |
154 | delete: { | |
155 | description: "List of properties to delete.", | |
156 | type: Array, | |
157 | optional: true, | |
158 | items: { | |
159 | type: DeletableProperty, | |
160 | }, | |
161 | }, | |
5ba83ed0 DM |
162 | digest: { |
163 | schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | |
164 | optional: true, | |
165 | }, | |
166 | }, | |
50bf10ad | 167 | }, |
8cd63df0 | 168 | access: { |
ee33795b | 169 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false), |
8cd63df0 | 170 | }, |
50bf10ad DM |
171 | )] |
172 | /// Update a tape changer configuration | |
173 | pub fn update_changer( | |
174 | name: String, | |
5af3bcf0 | 175 | update: ScsiTapeChangerUpdater, |
38ae42b1 | 176 | delete: Option<Vec<DeletableProperty>>, |
5ba83ed0 | 177 | digest: Option<String>, |
50bf10ad DM |
178 | _param: Value, |
179 | ) -> Result<(), Error> { | |
1ce8e905 | 180 | let _lock = pbs_config::drive::lock()?; |
50bf10ad | 181 | |
1ce8e905 | 182 | let (mut config, expected_digest) = pbs_config::drive::config()?; |
5ba83ed0 DM |
183 | |
184 | if let Some(ref digest) = digest { | |
25877d05 | 185 | let digest = <[u8; 32]>::from_hex(digest)?; |
5ba83ed0 DM |
186 | crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; |
187 | } | |
50bf10ad DM |
188 | |
189 | let mut data: ScsiTapeChanger = config.lookup("changer", &name)?; | |
190 | ||
38ae42b1 DM |
191 | if let Some(delete) = delete { |
192 | for delete_prop in delete { | |
193 | match delete_prop { | |
a2055c38 | 194 | DeletableProperty::ExportSlots => { |
38ae42b1 DM |
195 | data.export_slots = None; |
196 | } | |
197 | } | |
198 | } | |
199 | } | |
200 | ||
5af3bcf0 | 201 | if let Some(path) = update.path { |
50bf10ad DM |
202 | let changers = linux_tape_changer_list(); |
203 | check_drive_path(&changers, &path)?; | |
204 | data.path = path; | |
205 | } | |
206 | ||
5af3bcf0 | 207 | if let Some(export_slots) = update.export_slots { |
9fa3026a | 208 | let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(&export_slots)?; |
38ae42b1 DM |
209 | let mut slots: Vec<String> = slots |
210 | .as_array() | |
211 | .unwrap() | |
212 | .iter() | |
213 | .map(|v| v.to_string()) | |
214 | .collect(); | |
215 | slots.sort(); | |
216 | ||
217 | if slots.is_empty() { | |
218 | data.export_slots = None; | |
219 | } else { | |
220 | let slots = slots.join(","); | |
221 | data.export_slots = Some(slots); | |
222 | } | |
223 | } | |
224 | ||
50bf10ad DM |
225 | config.set_data(&name, "changer", &data)?; |
226 | ||
1ce8e905 | 227 | pbs_config::drive::save_config(&config)?; |
50bf10ad DM |
228 | |
229 | Ok(()) | |
230 | } | |
231 | ||
232 | #[api( | |
314652a4 | 233 | protected: true, |
50bf10ad DM |
234 | input: { |
235 | properties: { | |
236 | name: { | |
7e1d4712 | 237 | schema: CHANGER_NAME_SCHEMA, |
50bf10ad DM |
238 | }, |
239 | }, | |
240 | }, | |
8cd63df0 | 241 | access: { |
ee33795b | 242 | permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false), |
8cd63df0 | 243 | }, |
50bf10ad DM |
244 | )] |
245 | /// Delete a tape changer configuration | |
246 | pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> { | |
1ce8e905 | 247 | let _lock = pbs_config::drive::lock()?; |
50bf10ad | 248 | |
1ce8e905 | 249 | let (mut config, _digest) = pbs_config::drive::config()?; |
50bf10ad DM |
250 | |
251 | match config.sections.get(&name) { | |
252 | Some((section_type, _)) => { | |
253 | if section_type != "changer" { | |
dc7a5b34 TL |
254 | param_bail!( |
255 | "name", | |
256 | "Entry '{}' exists, but is not a changer device", | |
257 | name | |
258 | ); | |
50bf10ad DM |
259 | } |
260 | config.sections.remove(&name); | |
dc7a5b34 TL |
261 | } |
262 | None => http_bail!( | |
263 | NOT_FOUND, | |
264 | "Delete changer '{}' failed - no such entry", | |
265 | name | |
266 | ), | |
50bf10ad DM |
267 | } |
268 | ||
a79082a0 | 269 | let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?; |
43cfb3c3 DM |
270 | for drive in drive_list { |
271 | if let Some(changer) = drive.changer { | |
272 | if changer == name { | |
dc7a5b34 TL |
273 | param_bail!( |
274 | "name", | |
275 | "Delete changer '{}' failed - used by drive '{}'", | |
276 | name, | |
277 | drive.name | |
278 | ); | |
43cfb3c3 DM |
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 | ||
50bf10ad DM |
293 | pub const ROUTER: Router = Router::new() |
294 | .get(&API_METHOD_LIST_CHANGERS) | |
295 | .post(&API_METHOD_CREATE_CHANGER) | |
296 | .match_all("name", &ITEM_ROUTER); |