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