]> git.proxmox.com Git - proxmox-backup.git/blob - src/api2/config/changer.rs
a9f7622fa07bba4b8132629953f6356d576c2378
[proxmox-backup.git] / src / api2 / config / changer.rs
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 use pbs_config::CachedUserInfo;
19 use pbs_tape::linux_list_drives::{linux_tape_changer_list, check_drive_path};
20
21 #[api(
22 protected: true,
23 input: {
24 properties: {
25 config: {
26 type: ScsiTapeChanger,
27 flatten: true,
28 },
29 },
30 },
31 access: {
32 permission: &Permission::Privilege(&["tape", "device"], PRIV_TAPE_MODIFY, false),
33 },
34 )]
35 /// Create a new changer device
36 pub fn create_changer(config: ScsiTapeChanger) -> Result<(), Error> {
37
38 let _lock = pbs_config::drive::lock()?;
39
40 let (mut section_config, _digest) = pbs_config::drive::config()?;
41
42 let linux_changers = linux_tape_changer_list();
43
44 check_drive_path(&linux_changers, &config.path)?;
45
46 let existing: Vec<ScsiTapeChanger> = section_config.convert_to_typed_array("changer")?;
47
48 for changer in existing {
49 if changer.name == config.name {
50 bail!("Entry '{}' already exists", config.name);
51 }
52
53 if changer.path == config.path {
54 bail!("Path '{}' already in use by '{}'", config.path, changer.name);
55 }
56 }
57
58 section_config.set_data(&config.name, "changer", &config)?;
59
60 pbs_config::drive::save_config(&section_config)?;
61
62 Ok(())
63 }
64
65 #[api(
66 input: {
67 properties: {
68 name: {
69 schema: CHANGER_NAME_SCHEMA,
70 },
71 },
72 },
73 returns: {
74 type: ScsiTapeChanger,
75 },
76 access: {
77 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_AUDIT, false),
78 },
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
87 let (config, digest) = pbs_config::drive::config()?;
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: {
104 type: ScsiTapeChanger,
105 },
106 },
107 access: {
108 description: "List configured tape changer filtered by Tape.Audit privileges",
109 permission: &Permission::Anybody,
110 },
111 )]
112 /// List changers
113 pub fn list_changers(
114 _param: Value,
115 mut rpcenv: &mut dyn RpcEnvironment,
116 ) -> Result<Vec<ScsiTapeChanger>, Error> {
117 let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
118 let user_info = CachedUserInfo::new()?;
119
120 let (config, digest) = pbs_config::drive::config()?;
121
122 let list: Vec<ScsiTapeChanger> = config.convert_to_typed_array("changer")?;
123
124 let list = list
125 .into_iter()
126 .filter(|changer| {
127 let privs = user_info.lookup_privs(&auth_id, &["tape", "device", &changer.name]);
128 privs & PRIV_TAPE_AUDIT != 0
129 })
130 .collect();
131
132 rpcenv["digest"] = proxmox::tools::digest_to_hex(&digest).into();
133
134 Ok(list)
135 }
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 }
145
146 #[api(
147 protected: true,
148 input: {
149 properties: {
150 name: {
151 schema: CHANGER_NAME_SCHEMA,
152 },
153 update: {
154 type: ScsiTapeChangerUpdater,
155 flatten: true,
156 },
157 delete: {
158 description: "List of properties to delete.",
159 type: Array,
160 optional: true,
161 items: {
162 type: DeletableProperty,
163 },
164 },
165 digest: {
166 schema: PROXMOX_CONFIG_DIGEST_SCHEMA,
167 optional: true,
168 },
169 },
170 },
171 access: {
172 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
173 },
174 )]
175 /// Update a tape changer configuration
176 pub fn update_changer(
177 name: String,
178 update: ScsiTapeChangerUpdater,
179 delete: Option<Vec<DeletableProperty>>,
180 digest: Option<String>,
181 _param: Value,
182 ) -> Result<(), Error> {
183
184 let _lock = pbs_config::drive::lock()?;
185
186 let (mut config, expected_digest) = pbs_config::drive::config()?;
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 }
192
193 let mut data: ScsiTapeChanger = config.lookup("changer", &name)?;
194
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
205 if let Some(path) = update.path {
206 let changers = linux_tape_changer_list();
207 check_drive_path(&changers, &path)?;
208 data.path = path;
209 }
210
211 if let Some(export_slots) = update.export_slots {
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
231 config.set_data(&name, "changer", &data)?;
232
233 pbs_config::drive::save_config(&config)?;
234
235 Ok(())
236 }
237
238 #[api(
239 protected: true,
240 input: {
241 properties: {
242 name: {
243 schema: CHANGER_NAME_SCHEMA,
244 },
245 },
246 },
247 access: {
248 permission: &Permission::Privilege(&["tape", "device", "{name}"], PRIV_TAPE_MODIFY, false),
249 },
250 )]
251 /// Delete a tape changer configuration
252 pub fn delete_changer(name: String, _param: Value) -> Result<(), Error> {
253
254 let _lock = pbs_config::drive::lock()?;
255
256 let (mut config, _digest) = pbs_config::drive::config()?;
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
268 let drive_list: Vec<LtoTapeDrive> = config.convert_to_typed_array("lto")?;
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
277 pbs_config::drive::save_config(&config)?;
278
279 Ok(())
280 }
281
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);