]>
Commit | Line | Data |
---|---|---|
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(§ion_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); |