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