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