]> git.proxmox.com Git - proxmox-backup.git/blame - src/api2/config/changer.rs
api: tape: don't allow overwriting of ids in changer/drive config
[proxmox-backup.git] / src / api2 / config / changer.rs
CommitLineData
38ae42b1 1use ::serde::{Deserialize, Serialize};
dc7a5b34 2use anyhow::Error;
25877d05 3use hex::FromHex;
dc7a5b34 4use serde_json::Value;
50bf10ad 5
dc7a5b34 6use proxmox_router::{http_bail, Permission, Router, RpcEnvironment};
8d6425aa 7use proxmox_schema::{api, param_bail};
50bf10ad 8
1ce8e905 9use 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 13use pbs_config::CachedUserInfo;
dc7a5b34 14use 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 31pub 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 35
a577114a
DC
36 if section_config.sections.get(&config.name).is_some() {
37 param_bail!("name", "Entry '{}' already exists", config.name);
38 }
39
50bf10ad
DM
40 let linux_changers = linux_tape_changer_list();
41
5af3bcf0 42 check_drive_path(&linux_changers, &config.path)?;
50bf10ad 43
5af3bcf0 44 let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?;
b03ec281
DC
45
46 for changer in existing {
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(&section_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
80pub 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
111pub 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
138pub enum DeletableProperty {
139 /// Delete export-slots.
a2055c38 140 ExportSlots,
c7321e2e
DC
141 /// Delete eject-before-unload.
142 EjectBeforeUnload,
38ae42b1 143}
50bf10ad
DM
144
145#[api(
314652a4 146 protected: true,
50bf10ad
DM
147 input: {
148 properties: {
149 name: {
7e1d4712 150 schema: CHANGER_NAME_SCHEMA,
50bf10ad 151 },
5af3bcf0
DM
152 update: {
153 type: ScsiTapeChangerUpdater,
154 flatten: true,
38ae42b1
DM
155 },
156 delete: {
157 description: "List of properties to delete.",
158 type: Array,
159 optional: true,
160 items: {
161 type: DeletableProperty,
162 },
163 },
5ba83ed0
DM
164 digest: {
165 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
166 optional: true,
167 },
168 },
50bf10ad 169 },
8cd63df0 170 access: {
ee33795b 171 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
8cd63df0 172 },
50bf10ad
DM
173)]
174/// Update a tape changer configuration
175pub fn update_changer(
176 name: String,
5af3bcf0 177 update: ScsiTapeChangerUpdater,
38ae42b1 178 delete: Option<Vec<DeletableProperty>>,
5ba83ed0 179 digest: Option<String>,
50bf10ad
DM
180 _param: Value,
181) -> Result<(), Error> {
1ce8e905 182 let _lock = pbs_config::drive::lock()?;
50bf10ad 183
1ce8e905 184 let (mut config, expected_digest) = pbs_config::drive::config()?;
5ba83ed0
DM
185
186 if let Some(ref digest) = digest {
25877d05 187 let digest = <[u8; 32]>::from_hex(digest)?;
5ba83ed0
DM
188 crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?;
189 }
50bf10ad
DM
190
191 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
192
38ae42b1
DM
193 if let Some(delete) = delete {
194 for delete_prop in delete {
195 match delete_prop {
a2055c38 196 DeletableProperty::ExportSlots => {
38ae42b1
DM
197 data.export_slots = None;
198 }
c7321e2e
DC
199 DeletableProperty::EjectBeforeUnload => {
200 data.eject_before_unload = None;
66402cdc 201 }
38ae42b1
DM
202 }
203 }
204 }
205
5af3bcf0 206 if let Some(path) = update.path {
50bf10ad
DM
207 let changers = linux_tape_changer_list();
208 check_drive_path(&changers, &path)?;
209 data.path = path;
210 }
211
5af3bcf0 212 if let Some(export_slots) = update.export_slots {
9fa3026a 213 let slots: Value = SLOT_ARRAY_SCHEMA.parse_property_string(&export_slots)?;
38ae42b1
DM
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
c7321e2e
DC
230 if let Some(eject_before_unload) = update.eject_before_unload {
231 data.eject_before_unload = Some(eject_before_unload);
66402cdc
DC
232 }
233
50bf10ad
DM
234 config.set_data(&name, "changer", &data)?;
235
1ce8e905 236 pbs_config::drive::save_config(&config)?;
50bf10ad
DM
237
238 Ok(())
239}
240
241#[api(
314652a4 242 protected: true,
50bf10ad
DM
243 input: {
244 properties: {
245 name: {
7e1d4712 246 schema: CHANGER_NAME_SCHEMA,
50bf10ad
DM
247 },
248 },
249 },
8cd63df0 250 access: {
ee33795b 251 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
8cd63df0 252 },
50bf10ad
DM
253)]
254/// Delete a tape changer configuration
255pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
1ce8e905 256 let _lock = pbs_config::drive::lock()?;
50bf10ad 257
1ce8e905 258 let (mut config, _digest) = pbs_config::drive::config()?;
50bf10ad
DM
259
260 match config.sections.get(&name) {
261 Some((section_type, _)) => {
262 if section_type != "changer" {
dc7a5b34
TL
263 param_bail!(
264 "name",
265 "Entry '{}' exists, but is not a changer device",
266 name
267 );
50bf10ad
DM
268 }
269 config.sections.remove(&name);
dc7a5b34
TL
270 }
271 None => http_bail!(
272 NOT_FOUND,
273 "Delete changer '{}' failed - no such entry",
274 name
275 ),
50bf10ad
DM
276 }
277
a79082a0 278 let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
43cfb3c3
DM
279 for drive in drive_list {
280 if let Some(changer) = drive.changer {
281 if changer == name {
dc7a5b34
TL
282 param_bail!(
283 "name",
284 "Delete changer '{}' failed - used by drive '{}'",
285 name,
286 drive.name
287 );
43cfb3c3
DM
288 }
289 }
290 }
291
1ce8e905 292 pbs_config::drive::save_config(&config)?;
50bf10ad
DM
293
294 Ok(())
295}
296
50bf10ad
DM
297const ITEM_ROUTER: Router = Router::new()
298 .get(&API_METHOD_GET_CONFIG)
299 .put(&API_METHOD_UPDATE_CHANGER)
300 .delete(&API_METHOD_DELETE_CHANGER);
301
50bf10ad
DM
302pub const ROUTER: Router = Router::new()
303 .get(&API_METHOD_LIST_CHANGERS)
304 .post(&API_METHOD_CREATE_CHANGER)
305 .match_all("name", &ITEM_ROUTER);