]>
Commit | Line | Data |
---|---|---|
a44c934b | 1 | use std::panic::UnwindSafe; |
7bb720cb | 2 | use std::path::Path; |
6dbad5b4 DM |
3 | use std::sync::Arc; |
4 | ||
a44c934b | 5 | use anyhow::{bail, format_err, Error}; |
5d908606 DM |
6 | use serde_json::Value; |
7 | ||
7bb720cb DM |
8 | use proxmox::{ |
9 | sortable, | |
10 | identity, | |
11 | list_subdirs_api_method, | |
12 | tools::Uuid, | |
13 | sys::error::SysError, | |
14 | api::{ | |
15 | api, | |
25aa55b5 | 16 | section_config::SectionConfigData, |
6dbad5b4 | 17 | RpcEnvironment, |
cb022525 | 18 | RpcEnvironmentType, |
7bb720cb DM |
19 | Router, |
20 | SubdirMap, | |
21 | }, | |
22 | }; | |
5d908606 DM |
23 | |
24 | use crate::{ | |
7b1bf4c0 | 25 | task_log, |
25aa55b5 | 26 | config, |
b017bbc4 DM |
27 | api2::{ |
28 | types::{ | |
29 | UPID_SCHEMA, | |
18262a88 | 30 | CHANGER_NAME_SCHEMA, |
b017bbc4 DM |
31 | DRIVE_NAME_SCHEMA, |
32 | MEDIA_LABEL_SCHEMA, | |
33 | MEDIA_POOL_NAME_SCHEMA, | |
34 | Authid, | |
5fdaecf6 | 35 | DriveListEntry, |
b017bbc4 | 36 | LinuxTapeDrive, |
b017bbc4 DM |
37 | MediaIdFlat, |
38 | LabelUuidMap, | |
39 | MamAttribute, | |
40 | LinuxDriveAndMediaStatus, | |
41 | }, | |
42 | tape::restore::restore_media, | |
5d908606 | 43 | }, |
6dbad5b4 | 44 | server::WorkerTask, |
5d908606 | 45 | tape::{ |
7bb720cb | 46 | TAPE_STATUS_DIR, |
b017bbc4 | 47 | MediaPool, |
7bb720cb | 48 | Inventory, |
34605654 | 49 | MediaCatalog, |
7bb720cb | 50 | MediaId, |
645a044b | 51 | linux_tape_device_list, |
b5b99a52 | 52 | lookup_device_identification, |
645a044b | 53 | file_formats::{ |
a78348ac | 54 | MediaLabel, |
7bb720cb DM |
55 | MediaSetLabel, |
56 | }, | |
37796ff7 DM |
57 | drive::{ |
58 | TapeDriver, | |
59 | LinuxTapeHandle, | |
5f34d69b | 60 | Lp17VolumeStatistics, |
37796ff7 | 61 | open_linux_tape_device, |
25aa55b5 | 62 | media_changer, |
37796ff7 DM |
63 | required_media_changer, |
64 | open_drive, | |
25aa55b5 | 65 | lock_tape_device, |
a44c934b | 66 | set_tape_device_state, |
37796ff7 DM |
67 | }, |
68 | changer::update_changer_online_status, | |
5d908606 DM |
69 | }, |
70 | }; | |
71 | ||
a44c934b DC |
72 | fn run_drive_worker<F>( |
73 | rpcenv: &dyn RpcEnvironment, | |
74 | drive: String, | |
75 | worker_type: &str, | |
76 | job_id: Option<String>, | |
77 | f: F, | |
78 | ) -> Result<String, Error> | |
79 | where | |
80 | F: Send | |
81 | + UnwindSafe | |
82 | + 'static | |
83 | + FnOnce(Arc<WorkerTask>, SectionConfigData) -> Result<(), Error>, | |
84 | { | |
85 | // early check/lock before starting worker | |
86 | let (config, _digest) = config::drive::config()?; | |
87 | let lock_guard = lock_tape_device(&config, &drive)?; | |
88 | ||
89 | let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; | |
90 | let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; | |
91 | ||
92 | WorkerTask::new_thread(worker_type, job_id, auth_id, to_stdout, move |worker| { | |
93 | let _lock_guard = lock_guard; | |
94 | set_tape_device_state(&drive, &worker.upid().to_string()) | |
95 | .map_err(|err| format_err!("could not set tape device state: {}", err))?; | |
96 | ||
97 | let result = f(worker, config); | |
98 | set_tape_device_state(&drive, "") | |
99 | .map_err(|err| format_err!("could not unset tape device state: {}", err))?; | |
100 | result | |
101 | }) | |
102 | } | |
103 | ||
54c77b3d DC |
104 | async fn run_drive_blocking_task<F, R>(drive: String, state: String, f: F) -> Result<R, Error> |
105 | where | |
106 | F: Send + 'static + FnOnce(SectionConfigData) -> Result<R, Error>, | |
107 | R: Send + 'static, | |
108 | { | |
109 | // early check/lock before starting worker | |
110 | let (config, _digest) = config::drive::config()?; | |
111 | let lock_guard = lock_tape_device(&config, &drive)?; | |
112 | tokio::task::spawn_blocking(move || { | |
113 | let _lock_guard = lock_guard; | |
114 | set_tape_device_state(&drive, &state) | |
115 | .map_err(|err| format_err!("could not set tape device state: {}", err))?; | |
116 | let result = f(config); | |
117 | set_tape_device_state(&drive, "") | |
118 | .map_err(|err| format_err!("could not unset tape device state: {}", err))?; | |
119 | result | |
120 | }) | |
121 | .await? | |
122 | } | |
123 | ||
5d908606 DM |
124 | #[api( |
125 | input: { | |
126 | properties: { | |
93829fc6 | 127 | drive: { |
49c965a4 | 128 | schema: DRIVE_NAME_SCHEMA, |
5d908606 | 129 | }, |
8446fbca | 130 | "label-text": { |
46a1863f | 131 | schema: MEDIA_LABEL_SCHEMA, |
5d908606 DM |
132 | }, |
133 | }, | |
134 | }, | |
d0647e5a DM |
135 | returns: { |
136 | schema: UPID_SCHEMA, | |
137 | }, | |
5d908606 | 138 | )] |
46a1863f DM |
139 | /// Load media with specified label |
140 | /// | |
141 | /// Issue a media load request to the associated changer device. | |
d0647e5a DM |
142 | pub fn load_media( |
143 | drive: String, | |
144 | label_text: String, | |
145 | rpcenv: &mut dyn RpcEnvironment, | |
146 | ) -> Result<Value, Error> { | |
d0647e5a DM |
147 | let job_id = format!("{}:{}", drive, label_text); |
148 | ||
a1c55753 DC |
149 | let upid_str = run_drive_worker( |
150 | rpcenv, | |
151 | drive.clone(), | |
d0647e5a DM |
152 | "load-media", |
153 | Some(job_id), | |
a1c55753 | 154 | move |worker, config| { |
d0647e5a DM |
155 | task_log!(worker, "loading media '{}' into drive '{}'", label_text, drive); |
156 | let (mut changer, _) = required_media_changer(&config, &drive)?; | |
157 | changer.load_media(&label_text)?; | |
158 | ||
159 | Ok(()) | |
a1c55753 | 160 | }, |
d0647e5a DM |
161 | )?; |
162 | ||
163 | Ok(upid_str.into()) | |
5d908606 | 164 | } |
483da89d | 165 | |
e49f0c03 DM |
166 | #[api( |
167 | input: { | |
168 | properties: { | |
169 | drive: { | |
49c965a4 | 170 | schema: DRIVE_NAME_SCHEMA, |
e49f0c03 | 171 | }, |
46a1863f DM |
172 | "source-slot": { |
173 | description: "Source slot number.", | |
174 | minimum: 1, | |
e49f0c03 DM |
175 | }, |
176 | }, | |
177 | }, | |
178 | )] | |
46a1863f | 179 | /// Load media from the specified slot |
e49f0c03 DM |
180 | /// |
181 | /// Issue a media load request to the associated changer device. | |
46a1863f | 182 | pub async fn load_slot(drive: String, source_slot: u64) -> Result<(), Error> { |
47a72414 DC |
183 | run_drive_blocking_task( |
184 | drive.clone(), | |
185 | format!("load from slot {}", source_slot), | |
186 | move |config| { | |
187 | let (mut changer, _) = required_media_changer(&config, &drive)?; | |
188 | changer.load_media_from_slot(source_slot) | |
189 | }, | |
190 | ) | |
191 | .await | |
e49f0c03 DM |
192 | } |
193 | ||
483da89d DM |
194 | #[api( |
195 | input: { | |
196 | properties: { | |
197 | drive: { | |
198 | schema: DRIVE_NAME_SCHEMA, | |
199 | }, | |
8446fbca | 200 | "label-text": { |
483da89d DM |
201 | schema: MEDIA_LABEL_SCHEMA, |
202 | }, | |
203 | }, | |
204 | }, | |
205 | returns: { | |
206 | description: "The import-export slot number the media was transfered to.", | |
207 | type: u64, | |
208 | minimum: 1, | |
209 | }, | |
210 | )] | |
211 | /// Export media with specified label | |
8446fbca | 212 | pub async fn export_media(drive: String, label_text: String) -> Result<u64, Error> { |
47a72414 DC |
213 | run_drive_blocking_task( |
214 | drive.clone(), | |
215 | format!("export media {}", label_text), | |
216 | move |config| { | |
217 | let (mut changer, changer_name) = required_media_changer(&config, &drive)?; | |
218 | match changer.export_media(&label_text)? { | |
219 | Some(slot) => Ok(slot), | |
220 | None => bail!( | |
221 | "media '{}' is not online (via changer '{}')", | |
222 | label_text, | |
223 | changer_name | |
224 | ), | |
225 | } | |
483da89d | 226 | } |
47a72414 DC |
227 | ) |
228 | .await | |
483da89d DM |
229 | } |
230 | ||
5d908606 DM |
231 | #[api( |
232 | input: { | |
233 | properties: { | |
a3c709ef | 234 | drive: { |
49c965a4 | 235 | schema: DRIVE_NAME_SCHEMA, |
5d908606 | 236 | }, |
46a1863f | 237 | "target-slot": { |
5d908606 DM |
238 | description: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.", |
239 | minimum: 1, | |
240 | optional: true, | |
241 | }, | |
242 | }, | |
243 | }, | |
d0647e5a DM |
244 | returns: { |
245 | schema: UPID_SCHEMA, | |
246 | }, | |
5d908606 DM |
247 | )] |
248 | /// Unload media via changer | |
d0647e5a | 249 | pub fn unload( |
a3c709ef | 250 | drive: String, |
46a1863f | 251 | target_slot: Option<u64>, |
d0647e5a DM |
252 | rpcenv: &mut dyn RpcEnvironment, |
253 | ) -> Result<Value, Error> { | |
a1c55753 DC |
254 | let upid_str = run_drive_worker( |
255 | rpcenv, | |
256 | drive.clone(), | |
d0647e5a DM |
257 | "unload-media", |
258 | Some(drive.clone()), | |
a1c55753 | 259 | move |worker, config| { |
d0647e5a DM |
260 | task_log!(worker, "unloading media from drive '{}'", drive); |
261 | ||
262 | let (mut changer, _) = required_media_changer(&config, &drive)?; | |
263 | changer.unload_media(target_slot)?; | |
264 | Ok(()) | |
a1c55753 | 265 | }, |
d0647e5a DM |
266 | )?; |
267 | ||
268 | Ok(upid_str.into()) | |
5d908606 DM |
269 | } |
270 | ||
583a68a4 DM |
271 | #[api( |
272 | input: { | |
273 | properties: { | |
274 | drive: { | |
49c965a4 | 275 | schema: DRIVE_NAME_SCHEMA, |
583a68a4 DM |
276 | }, |
277 | fast: { | |
278 | description: "Use fast erase.", | |
279 | type: bool, | |
280 | optional: true, | |
281 | default: true, | |
282 | }, | |
be61c56c DC |
283 | "label-text": { |
284 | schema: MEDIA_LABEL_SCHEMA, | |
285 | optional: true, | |
286 | }, | |
583a68a4 DM |
287 | }, |
288 | }, | |
663ef859 DM |
289 | returns: { |
290 | schema: UPID_SCHEMA, | |
291 | }, | |
583a68a4 | 292 | )] |
be61c56c | 293 | /// Erase media. Check for label-text if given (cancels if wrong media). |
663ef859 DM |
294 | pub fn erase_media( |
295 | drive: String, | |
296 | fast: Option<bool>, | |
be61c56c | 297 | label_text: Option<String>, |
663ef859 DM |
298 | rpcenv: &mut dyn RpcEnvironment, |
299 | ) -> Result<Value, Error> { | |
a1c55753 DC |
300 | let upid_str = run_drive_worker( |
301 | rpcenv, | |
302 | drive.clone(), | |
663ef859 DM |
303 | "erase-media", |
304 | Some(drive.clone()), | |
a1c55753 | 305 | move |worker, config| { |
3cdd1a34 DM |
306 | if let Some(ref label) = label_text { |
307 | task_log!(worker, "try to load media '{}'", label); | |
308 | if let Some((mut changer, _)) = media_changer(&config, &drive)? { | |
309 | changer.load_media(label)?; | |
310 | } | |
311 | } | |
312 | ||
313 | let mut handle = open_drive(&config, &drive)?; | |
7b1bf4c0 | 314 | |
3cdd1a34 | 315 | match handle.read_label() { |
7b1bf4c0 | 316 | Err(err) => { |
be61c56c DC |
317 | if let Some(label) = label_text { |
318 | bail!("expected label '{}', found unrelated data", label); | |
319 | } | |
7b1bf4c0 DM |
320 | /* assume drive contains no or unrelated data */ |
321 | task_log!(worker, "unable to read media label: {}", err); | |
322 | task_log!(worker, "erase anyways"); | |
3cdd1a34 | 323 | handle.erase_media(fast.unwrap_or(true))?; |
7b1bf4c0 DM |
324 | } |
325 | Ok((None, _)) => { | |
be61c56c DC |
326 | if let Some(label) = label_text { |
327 | bail!("expected label '{}', found empty tape", label); | |
328 | } | |
7b1bf4c0 | 329 | task_log!(worker, "found empty media - erase anyways"); |
3cdd1a34 | 330 | handle.erase_media(fast.unwrap_or(true))?; |
7b1bf4c0 DM |
331 | } |
332 | Ok((Some(media_id), _key_config)) => { | |
be61c56c DC |
333 | if let Some(label_text) = label_text { |
334 | if media_id.label.label_text != label_text { | |
335 | bail!( | |
336 | "expected label '{}', found '{}', aborting", | |
337 | label_text, | |
338 | media_id.label.label_text | |
339 | ); | |
340 | } | |
341 | } | |
342 | ||
7b1bf4c0 DM |
343 | task_log!( |
344 | worker, | |
345 | "found media '{}' with uuid '{}'", | |
346 | media_id.label.label_text, media_id.label.uuid, | |
347 | ); | |
348 | ||
349 | let status_path = Path::new(TAPE_STATUS_DIR); | |
350 | let mut inventory = Inventory::load(status_path)?; | |
351 | ||
352 | MediaCatalog::destroy(status_path, &media_id.label.uuid)?; | |
353 | inventory.remove_media(&media_id.label.uuid)?; | |
3cdd1a34 | 354 | handle.erase_media(fast.unwrap_or(true))?; |
7b1bf4c0 DM |
355 | } |
356 | } | |
357 | ||
663ef859 | 358 | Ok(()) |
a1c55753 | 359 | }, |
663ef859 DM |
360 | )?; |
361 | ||
362 | Ok(upid_str.into()) | |
583a68a4 DM |
363 | } |
364 | ||
5fb694e8 DM |
365 | #[api( |
366 | input: { | |
367 | properties: { | |
368 | drive: { | |
49c965a4 | 369 | schema: DRIVE_NAME_SCHEMA, |
5fb694e8 DM |
370 | }, |
371 | }, | |
372 | }, | |
663ef859 DM |
373 | returns: { |
374 | schema: UPID_SCHEMA, | |
375 | }, | |
5fb694e8 DM |
376 | )] |
377 | /// Rewind tape | |
663ef859 DM |
378 | pub fn rewind( |
379 | drive: String, | |
380 | rpcenv: &mut dyn RpcEnvironment, | |
381 | ) -> Result<Value, Error> { | |
a1c55753 DC |
382 | let upid_str = run_drive_worker( |
383 | rpcenv, | |
384 | drive.clone(), | |
663ef859 DM |
385 | "rewind-media", |
386 | Some(drive.clone()), | |
a1c55753 | 387 | move |_worker, config| { |
663ef859 DM |
388 | let mut drive = open_drive(&config, &drive)?; |
389 | drive.rewind()?; | |
390 | Ok(()) | |
a1c55753 | 391 | }, |
663ef859 DM |
392 | )?; |
393 | ||
394 | Ok(upid_str.into()) | |
5fb694e8 DM |
395 | } |
396 | ||
0098b712 DM |
397 | #[api( |
398 | input: { | |
399 | properties: { | |
400 | drive: { | |
49c965a4 | 401 | schema: DRIVE_NAME_SCHEMA, |
0098b712 DM |
402 | }, |
403 | }, | |
404 | }, | |
41dacd5d DM |
405 | returns: { |
406 | schema: UPID_SCHEMA, | |
407 | }, | |
0098b712 DM |
408 | )] |
409 | /// Eject/Unload drive media | |
41dacd5d DM |
410 | pub fn eject_media( |
411 | drive: String, | |
412 | rpcenv: &mut dyn RpcEnvironment, | |
413 | ) -> Result<Value, Error> { | |
a1c55753 DC |
414 | let upid_str = run_drive_worker( |
415 | rpcenv, | |
416 | drive.clone(), | |
41dacd5d DM |
417 | "eject-media", |
418 | Some(drive.clone()), | |
a1c55753 DC |
419 | move |_worker, config| { |
420 | if let Some((mut changer, _)) = media_changer(&config, &drive)? { | |
41dacd5d DM |
421 | changer.unload_media(None)?; |
422 | } else { | |
423 | let mut drive = open_drive(&config, &drive)?; | |
424 | drive.eject_media()?; | |
425 | } | |
426 | Ok(()) | |
a1c55753 DC |
427 | }, |
428 | )?; | |
41dacd5d DM |
429 | |
430 | Ok(upid_str.into()) | |
0098b712 DM |
431 | } |
432 | ||
7bb720cb DM |
433 | #[api( |
434 | input: { | |
435 | properties: { | |
436 | drive: { | |
49c965a4 | 437 | schema: DRIVE_NAME_SCHEMA, |
7bb720cb | 438 | }, |
8446fbca | 439 | "label-text": { |
7bb720cb DM |
440 | schema: MEDIA_LABEL_SCHEMA, |
441 | }, | |
442 | pool: { | |
443 | schema: MEDIA_POOL_NAME_SCHEMA, | |
444 | optional: true, | |
445 | }, | |
446 | }, | |
447 | }, | |
6dbad5b4 DM |
448 | returns: { |
449 | schema: UPID_SCHEMA, | |
450 | }, | |
7bb720cb DM |
451 | )] |
452 | /// Label media | |
453 | /// | |
454 | /// Write a new media label to the media in 'drive'. The media is | |
455 | /// assigned to the specified 'pool', or else to the free media pool. | |
456 | /// | |
457 | /// Note: The media need to be empty (you may want to erase it first). | |
458 | pub fn label_media( | |
459 | drive: String, | |
460 | pool: Option<String>, | |
8446fbca | 461 | label_text: String, |
6dbad5b4 DM |
462 | rpcenv: &mut dyn RpcEnvironment, |
463 | ) -> Result<Value, Error> { | |
7bb720cb DM |
464 | if let Some(ref pool) = pool { |
465 | let (pool_config, _digest) = config::media_pool::config()?; | |
466 | ||
467 | if pool_config.sections.get(pool).is_none() { | |
468 | bail!("no such pool ('{}')", pool); | |
469 | } | |
470 | } | |
a1c55753 DC |
471 | let upid_str = run_drive_worker( |
472 | rpcenv, | |
473 | drive.clone(), | |
6dbad5b4 DM |
474 | "label-media", |
475 | Some(drive.clone()), | |
a1c55753 | 476 | move |worker, config| { |
6dbad5b4 DM |
477 | let mut drive = open_drive(&config, &drive)?; |
478 | ||
479 | drive.rewind()?; | |
480 | ||
481 | match drive.read_next_file() { | |
482 | Ok(Some(_file)) => bail!("media is not empty (erase first)"), | |
483 | Ok(None) => { /* EOF mark at BOT, assume tape is empty */ }, | |
484 | Err(err) => { | |
485 | if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) { | |
486 | /* assume tape is empty */ | |
487 | } else { | |
488 | bail!("media read error - {}", err); | |
489 | } | |
490 | } | |
491 | } | |
7bb720cb | 492 | |
6dbad5b4 | 493 | let ctime = proxmox::tools::time::epoch_i64(); |
a78348ac | 494 | let label = MediaLabel { |
8446fbca | 495 | label_text: label_text.to_string(), |
6dbad5b4 DM |
496 | uuid: Uuid::generate(), |
497 | ctime, | |
498 | }; | |
7bb720cb | 499 | |
6dbad5b4 | 500 | write_media_label(worker, &mut drive, label, pool) |
a1c55753 | 501 | }, |
6dbad5b4 | 502 | )?; |
7bb720cb | 503 | |
6dbad5b4 | 504 | Ok(upid_str.into()) |
7bb720cb DM |
505 | } |
506 | ||
507 | fn write_media_label( | |
6dbad5b4 | 508 | worker: Arc<WorkerTask>, |
7bb720cb | 509 | drive: &mut Box<dyn TapeDriver>, |
a78348ac | 510 | label: MediaLabel, |
7bb720cb DM |
511 | pool: Option<String>, |
512 | ) -> Result<(), Error> { | |
513 | ||
514 | drive.label_tape(&label)?; | |
515 | ||
516 | let mut media_set_label = None; | |
517 | ||
518 | if let Some(ref pool) = pool { | |
519 | // assign media to pool by writing special media set label | |
8446fbca | 520 | worker.log(format!("Label media '{}' for pool '{}'", label.label_text, pool)); |
8a0046f5 | 521 | let set = MediaSetLabel::with_data(&pool, [0u8; 16].into(), 0, label.ctime, None); |
7bb720cb | 522 | |
feb1645f | 523 | drive.write_media_set_label(&set, None)?; |
7bb720cb DM |
524 | media_set_label = Some(set); |
525 | } else { | |
8446fbca | 526 | worker.log(format!("Label media '{}' (no pool assignment)", label.label_text)); |
7bb720cb DM |
527 | } |
528 | ||
529 | let media_id = MediaId { label, media_set_label }; | |
530 | ||
34605654 DM |
531 | let status_path = Path::new(TAPE_STATUS_DIR); |
532 | ||
533 | // Create the media catalog | |
534 | MediaCatalog::overwrite(status_path, &media_id, false)?; | |
535 | ||
536 | let mut inventory = Inventory::load(status_path)?; | |
cfae8f06 | 537 | inventory.store(media_id.clone(), false)?; |
7bb720cb DM |
538 | |
539 | drive.rewind()?; | |
540 | ||
541 | match drive.read_label() { | |
feb1645f | 542 | Ok((Some(info), _)) => { |
7bb720cb DM |
543 | if info.label.uuid != media_id.label.uuid { |
544 | bail!("verify label failed - got wrong label uuid"); | |
545 | } | |
546 | if let Some(ref pool) = pool { | |
547 | match info.media_set_label { | |
fe6c1938 | 548 | Some(set) => { |
7bb720cb DM |
549 | if set.uuid != [0u8; 16].into() { |
550 | bail!("verify media set label failed - got wrong set uuid"); | |
551 | } | |
552 | if &set.pool != pool { | |
553 | bail!("verify media set label failed - got wrong pool"); | |
554 | } | |
555 | } | |
556 | None => { | |
557 | bail!("verify media set label failed (missing set label)"); | |
558 | } | |
559 | } | |
560 | } | |
561 | }, | |
feb1645f | 562 | Ok((None, _)) => bail!("verify label failed (got empty media)"), |
7bb720cb DM |
563 | Err(err) => bail!("verify label failed - {}", err), |
564 | }; | |
565 | ||
566 | drive.rewind()?; | |
567 | ||
568 | Ok(()) | |
569 | } | |
570 | ||
4606f343 | 571 | #[api( |
feb1645f DM |
572 | protected: true, |
573 | input: { | |
574 | properties: { | |
575 | drive: { | |
576 | schema: DRIVE_NAME_SCHEMA, | |
577 | }, | |
578 | password: { | |
579 | description: "Encryption key password.", | |
580 | }, | |
581 | }, | |
582 | }, | |
583 | )] | |
584 | /// Try to restore a tape encryption key | |
585 | pub async fn restore_key( | |
586 | drive: String, | |
587 | password: String, | |
588 | ) -> Result<(), Error> { | |
47a72414 DC |
589 | run_drive_blocking_task( |
590 | drive.clone(), | |
591 | "restore key".to_string(), | |
592 | move |config| { | |
593 | let mut drive = open_drive(&config, &drive)?; | |
feb1645f | 594 | |
47a72414 | 595 | let (_media_id, key_config) = drive.read_label()?; |
feb1645f | 596 | |
47a72414 DC |
597 | if let Some(key_config) = key_config { |
598 | let password_fn = || { Ok(password.as_bytes().to_vec()) }; | |
599 | let (key, ..) = key_config.decrypt(&password_fn)?; | |
600 | config::tape_encryption_keys::insert_key(key, key_config, true)?; | |
601 | } else { | |
602 | bail!("media does not contain any encryption key configuration"); | |
603 | } | |
feb1645f | 604 | |
47a72414 | 605 | Ok(()) |
feb1645f | 606 | } |
47a72414 DC |
607 | ) |
608 | .await | |
feb1645f DM |
609 | } |
610 | ||
611 | #[api( | |
4606f343 DM |
612 | input: { |
613 | properties: { | |
614 | drive: { | |
49c965a4 | 615 | schema: DRIVE_NAME_SCHEMA, |
4606f343 | 616 | }, |
781da7f6 DM |
617 | inventorize: { |
618 | description: "Inventorize media", | |
619 | optional: true, | |
620 | }, | |
4606f343 DM |
621 | }, |
622 | }, | |
623 | returns: { | |
fe6c1938 | 624 | type: MediaIdFlat, |
4606f343 DM |
625 | }, |
626 | )] | |
feb1645f | 627 | /// Read media label (optionally inventorize media) |
781da7f6 DM |
628 | pub async fn read_label( |
629 | drive: String, | |
630 | inventorize: Option<bool>, | |
631 | ) -> Result<MediaIdFlat, Error> { | |
47a72414 DC |
632 | run_drive_blocking_task( |
633 | drive.clone(), | |
634 | "reading label".to_string(), | |
635 | move |config| { | |
636 | let mut drive = open_drive(&config, &drive)?; | |
4606f343 | 637 | |
47a72414 DC |
638 | let (media_id, _key_config) = drive.read_label()?; |
639 | ||
640 | let media_id = match media_id { | |
641 | Some(media_id) => { | |
642 | let mut flat = MediaIdFlat { | |
643 | uuid: media_id.label.uuid.clone(), | |
644 | label_text: media_id.label.label_text.clone(), | |
645 | ctime: media_id.label.ctime, | |
646 | media_set_ctime: None, | |
647 | media_set_uuid: None, | |
648 | encryption_key_fingerprint: None, | |
649 | pool: None, | |
650 | seq_nr: None, | |
651 | }; | |
652 | if let Some(ref set) = media_id.media_set_label { | |
653 | flat.pool = Some(set.pool.clone()); | |
654 | flat.seq_nr = Some(set.seq_nr); | |
655 | flat.media_set_uuid = Some(set.uuid.clone()); | |
656 | flat.media_set_ctime = Some(set.ctime); | |
657 | flat.encryption_key_fingerprint = set | |
658 | .encryption_key_fingerprint | |
659 | .as_ref() | |
660 | .map(|fp| crate::tools::format::as_fingerprint(fp.bytes())); | |
661 | ||
662 | let encrypt_fingerprint = set.encryption_key_fingerprint.clone() | |
663 | .map(|fp| (fp, set.uuid.clone())); | |
664 | ||
665 | if let Err(err) = drive.set_encryption(encrypt_fingerprint) { | |
666 | // try, but ignore errors. just log to stderr | |
667 | eprintln!("uable to load encryption key: {}", err); | |
668 | } | |
669 | } | |
25aa55b5 | 670 | |
47a72414 DC |
671 | if let Some(true) = inventorize { |
672 | let state_path = Path::new(TAPE_STATUS_DIR); | |
673 | let mut inventory = Inventory::load(state_path)?; | |
674 | inventory.store(media_id, false)?; | |
780bc4ca | 675 | } |
781da7f6 | 676 | |
47a72414 | 677 | flat |
781da7f6 | 678 | } |
47a72414 DC |
679 | None => { |
680 | bail!("Media is empty (no label)."); | |
681 | } | |
682 | }; | |
781da7f6 | 683 | |
47a72414 DC |
684 | Ok(media_id) |
685 | } | |
686 | ) | |
687 | .await | |
4606f343 DM |
688 | } |
689 | ||
df69a4fc DM |
690 | #[api( |
691 | input: { | |
692 | properties: { | |
693 | drive: { | |
694 | schema: DRIVE_NAME_SCHEMA, | |
695 | }, | |
696 | }, | |
697 | }, | |
698 | returns: { | |
699 | schema: UPID_SCHEMA, | |
700 | }, | |
701 | )] | |
702 | /// Clean drive | |
703 | pub fn clean_drive( | |
704 | drive: String, | |
705 | rpcenv: &mut dyn RpcEnvironment, | |
706 | ) -> Result<Value, Error> { | |
a1c55753 DC |
707 | let upid_str = run_drive_worker( |
708 | rpcenv, | |
709 | drive.clone(), | |
df69a4fc DM |
710 | "clean-drive", |
711 | Some(drive.clone()), | |
a1c55753 | 712 | move |worker, config| { |
df69a4fc DM |
713 | let (mut changer, _changer_name) = required_media_changer(&config, &drive)?; |
714 | ||
715 | worker.log("Starting drive clean"); | |
716 | ||
717 | changer.clean_drive()?; | |
718 | ||
719 | worker.log("Drive cleaned sucessfully"); | |
720 | ||
721 | Ok(()) | |
a1c55753 DC |
722 | }, |
723 | )?; | |
df69a4fc DM |
724 | |
725 | Ok(upid_str.into()) | |
726 | } | |
727 | ||
83abc749 DM |
728 | #[api( |
729 | input: { | |
730 | properties: { | |
731 | drive: { | |
49c965a4 | 732 | schema: DRIVE_NAME_SCHEMA, |
83abc749 | 733 | }, |
83abc749 DM |
734 | }, |
735 | }, | |
736 | returns: { | |
737 | description: "The list of media labels with associated media Uuid (if any).", | |
738 | type: Array, | |
739 | items: { | |
740 | type: LabelUuidMap, | |
741 | }, | |
742 | }, | |
743 | )] | |
e92c7581 | 744 | /// List known media labels (Changer Inventory) |
83abc749 DM |
745 | /// |
746 | /// Note: Only useful for drives with associated changer device. | |
747 | /// | |
e92c7581 DM |
748 | /// This method queries the changer to get a list of media labels. |
749 | /// | |
750 | /// Note: This updates the media online status. | |
66dbe563 | 751 | pub async fn inventory( |
83abc749 | 752 | drive: String, |
83abc749 | 753 | ) -> Result<Vec<LabelUuidMap>, Error> { |
47a72414 DC |
754 | run_drive_blocking_task( |
755 | drive.clone(), | |
756 | "inventorize".to_string(), | |
757 | move |config| { | |
758 | let (mut changer, changer_name) = required_media_changer(&config, &drive)?; | |
83abc749 | 759 | |
47a72414 | 760 | let label_text_list = changer.online_media_label_texts()?; |
25aa55b5 | 761 | |
47a72414 | 762 | let state_path = Path::new(TAPE_STATUS_DIR); |
83abc749 | 763 | |
47a72414 | 764 | let mut inventory = Inventory::load(state_path)?; |
83abc749 | 765 | |
47a72414 DC |
766 | update_changer_online_status( |
767 | &config, | |
768 | &mut inventory, | |
769 | &changer_name, | |
770 | &label_text_list, | |
771 | )?; | |
83abc749 | 772 | |
47a72414 | 773 | let mut list = Vec::new(); |
83abc749 | 774 | |
47a72414 DC |
775 | for label_text in label_text_list.iter() { |
776 | if label_text.starts_with("CLN") { | |
777 | // skip cleaning unit | |
778 | continue; | |
779 | } | |
83abc749 | 780 | |
47a72414 | 781 | let label_text = label_text.to_string(); |
83abc749 | 782 | |
47a72414 DC |
783 | if let Some(media_id) = inventory.find_media_by_label_text(&label_text) { |
784 | list.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }); | |
785 | } else { | |
786 | list.push(LabelUuidMap { label_text, uuid: None }); | |
787 | } | |
66dbe563 | 788 | } |
83abc749 | 789 | |
47a72414 | 790 | Ok(list) |
83abc749 | 791 | } |
47a72414 DC |
792 | ) |
793 | .await | |
e92c7581 | 794 | } |
83abc749 | 795 | |
e92c7581 DM |
796 | #[api( |
797 | input: { | |
798 | properties: { | |
799 | drive: { | |
49c965a4 | 800 | schema: DRIVE_NAME_SCHEMA, |
e92c7581 DM |
801 | }, |
802 | "read-all-labels": { | |
803 | description: "Load all tapes and try read labels (even if already inventoried)", | |
804 | type: bool, | |
805 | optional: true, | |
806 | }, | |
807 | }, | |
808 | }, | |
809 | returns: { | |
810 | schema: UPID_SCHEMA, | |
811 | }, | |
812 | )] | |
813 | /// Update inventory | |
814 | /// | |
815 | /// Note: Only useful for drives with associated changer device. | |
816 | /// | |
817 | /// This method queries the changer to get a list of media labels. It | |
818 | /// then loads any unknown media into the drive, reads the label, and | |
819 | /// store the result to the media database. | |
820 | /// | |
821 | /// Note: This updates the media online status. | |
822 | pub fn update_inventory( | |
823 | drive: String, | |
824 | read_all_labels: Option<bool>, | |
825 | rpcenv: &mut dyn RpcEnvironment, | |
826 | ) -> Result<Value, Error> { | |
a1c55753 DC |
827 | let upid_str = run_drive_worker( |
828 | rpcenv, | |
829 | drive.clone(), | |
e92c7581 DM |
830 | "inventory-update", |
831 | Some(drive.clone()), | |
a1c55753 | 832 | move |worker, config| { |
284eb5da | 833 | let (mut changer, changer_name) = required_media_changer(&config, &drive)?; |
83abc749 | 834 | |
8446fbca DM |
835 | let label_text_list = changer.online_media_label_texts()?; |
836 | if label_text_list.is_empty() { | |
3b82f3ee | 837 | worker.log("changer device does not list any media labels".to_string()); |
83abc749 | 838 | } |
e92c7581 DM |
839 | |
840 | let state_path = Path::new(TAPE_STATUS_DIR); | |
841 | ||
842 | let mut inventory = Inventory::load(state_path)?; | |
e92c7581 | 843 | |
8446fbca | 844 | update_changer_online_status(&config, &mut inventory, &changer_name, &label_text_list)?; |
e92c7581 | 845 | |
8446fbca DM |
846 | for label_text in label_text_list.iter() { |
847 | if label_text.starts_with("CLN") { | |
848 | worker.log(format!("skip cleaning unit '{}'", label_text)); | |
e92c7581 DM |
849 | continue; |
850 | } | |
851 | ||
8446fbca | 852 | let label_text = label_text.to_string(); |
e92c7581 | 853 | |
3984a5fd FG |
854 | if !read_all_labels.unwrap_or(false) && inventory.find_media_by_label_text(&label_text).is_some() { |
855 | worker.log(format!("media '{}' already inventoried", label_text)); | |
856 | continue; | |
e92c7581 DM |
857 | } |
858 | ||
8446fbca DM |
859 | if let Err(err) = changer.load_media(&label_text) { |
860 | worker.warn(format!("unable to load media '{}' - {}", label_text, err)); | |
83abc749 DM |
861 | continue; |
862 | } | |
e92c7581 DM |
863 | |
864 | let mut drive = open_drive(&config, &drive)?; | |
865 | match drive.read_label() { | |
866 | Err(err) => { | |
8446fbca | 867 | worker.warn(format!("unable to read label form media '{}' - {}", label_text, err)); |
e92c7581 | 868 | } |
feb1645f | 869 | Ok((None, _)) => { |
8446fbca | 870 | worker.log(format!("media '{}' is empty", label_text)); |
e92c7581 | 871 | } |
feb1645f | 872 | Ok((Some(media_id), _key_config)) => { |
8446fbca DM |
873 | if label_text != media_id.label.label_text { |
874 | worker.warn(format!("label text missmatch ({} != {})", label_text, media_id.label.label_text)); | |
e92c7581 DM |
875 | continue; |
876 | } | |
8446fbca | 877 | worker.log(format!("inventorize media '{}' with uuid '{}'", label_text, media_id.label.uuid)); |
cfae8f06 | 878 | inventory.store(media_id, false)?; |
e92c7581 DM |
879 | } |
880 | } | |
df69a4fc | 881 | changer.unload_media(None)?; |
83abc749 | 882 | } |
e92c7581 | 883 | Ok(()) |
a1c55753 | 884 | }, |
e92c7581 | 885 | )?; |
83abc749 | 886 | |
e92c7581 | 887 | Ok(upid_str.into()) |
83abc749 DM |
888 | } |
889 | ||
890 | ||
bff7e3f3 DM |
891 | #[api( |
892 | input: { | |
893 | properties: { | |
894 | drive: { | |
49c965a4 | 895 | schema: DRIVE_NAME_SCHEMA, |
bff7e3f3 DM |
896 | }, |
897 | pool: { | |
898 | schema: MEDIA_POOL_NAME_SCHEMA, | |
899 | optional: true, | |
900 | }, | |
901 | }, | |
902 | }, | |
6dbad5b4 DM |
903 | returns: { |
904 | schema: UPID_SCHEMA, | |
905 | }, | |
bff7e3f3 DM |
906 | )] |
907 | /// Label media with barcodes from changer device | |
908 | pub fn barcode_label_media( | |
909 | drive: String, | |
910 | pool: Option<String>, | |
6dbad5b4 DM |
911 | rpcenv: &mut dyn RpcEnvironment, |
912 | ) -> Result<Value, Error> { | |
bff7e3f3 DM |
913 | if let Some(ref pool) = pool { |
914 | let (pool_config, _digest) = config::media_pool::config()?; | |
915 | ||
916 | if pool_config.sections.get(pool).is_none() { | |
917 | bail!("no such pool ('{}')", pool); | |
918 | } | |
919 | } | |
920 | ||
a1c55753 DC |
921 | let upid_str = run_drive_worker( |
922 | rpcenv, | |
923 | drive.clone(), | |
6dbad5b4 DM |
924 | "barcode-label-media", |
925 | Some(drive.clone()), | |
a1c55753 | 926 | move |worker, config| barcode_label_media_worker(worker, drive, &config, pool), |
6dbad5b4 DM |
927 | )?; |
928 | ||
929 | Ok(upid_str.into()) | |
930 | } | |
931 | ||
932 | fn barcode_label_media_worker( | |
933 | worker: Arc<WorkerTask>, | |
934 | drive: String, | |
25aa55b5 | 935 | drive_config: &SectionConfigData, |
6dbad5b4 DM |
936 | pool: Option<String>, |
937 | ) -> Result<(), Error> { | |
25aa55b5 | 938 | let (mut changer, changer_name) = required_media_changer(drive_config, &drive)?; |
bff7e3f3 | 939 | |
8446fbca | 940 | let label_text_list = changer.online_media_label_texts()?; |
bff7e3f3 DM |
941 | |
942 | let state_path = Path::new(TAPE_STATUS_DIR); | |
943 | ||
944 | let mut inventory = Inventory::load(state_path)?; | |
bff7e3f3 | 945 | |
25aa55b5 | 946 | update_changer_online_status(drive_config, &mut inventory, &changer_name, &label_text_list)?; |
bff7e3f3 | 947 | |
8446fbca | 948 | if label_text_list.is_empty() { |
bff7e3f3 DM |
949 | bail!("changer device does not list any media labels"); |
950 | } | |
951 | ||
8446fbca DM |
952 | for label_text in label_text_list { |
953 | if label_text.starts_with("CLN") { continue; } | |
bff7e3f3 DM |
954 | |
955 | inventory.reload()?; | |
8446fbca DM |
956 | if inventory.find_media_by_label_text(&label_text).is_some() { |
957 | worker.log(format!("media '{}' already inventoried (already labeled)", label_text)); | |
bff7e3f3 DM |
958 | continue; |
959 | } | |
960 | ||
8446fbca | 961 | worker.log(format!("checking/loading media '{}'", label_text)); |
bff7e3f3 | 962 | |
8446fbca DM |
963 | if let Err(err) = changer.load_media(&label_text) { |
964 | worker.warn(format!("unable to load media '{}' - {}", label_text, err)); | |
bff7e3f3 DM |
965 | continue; |
966 | } | |
967 | ||
25aa55b5 | 968 | let mut drive = open_drive(drive_config, &drive)?; |
bff7e3f3 DM |
969 | drive.rewind()?; |
970 | ||
971 | match drive.read_next_file() { | |
972 | Ok(Some(_file)) => { | |
8446fbca | 973 | worker.log(format!("media '{}' is not empty (erase first)", label_text)); |
bff7e3f3 DM |
974 | continue; |
975 | } | |
976 | Ok(None) => { /* EOF mark at BOT, assume tape is empty */ }, | |
977 | Err(err) => { | |
978 | if err.is_errno(nix::errno::Errno::ENOSPC) || err.is_errno(nix::errno::Errno::EIO) { | |
979 | /* assume tape is empty */ | |
980 | } else { | |
8446fbca | 981 | worker.warn(format!("media '{}' read error (maybe not empty - erase first)", label_text)); |
bff7e3f3 DM |
982 | continue; |
983 | } | |
984 | } | |
985 | } | |
986 | ||
987 | let ctime = proxmox::tools::time::epoch_i64(); | |
a78348ac | 988 | let label = MediaLabel { |
8446fbca | 989 | label_text: label_text.to_string(), |
bff7e3f3 DM |
990 | uuid: Uuid::generate(), |
991 | ctime, | |
992 | }; | |
993 | ||
6dbad5b4 | 994 | write_media_label(worker.clone(), &mut drive, label, pool.clone())? |
bff7e3f3 DM |
995 | } |
996 | ||
997 | Ok(()) | |
998 | } | |
999 | ||
1e20f819 DM |
1000 | #[api( |
1001 | input: { | |
1002 | properties: { | |
1003 | drive: { | |
1004 | schema: DRIVE_NAME_SCHEMA, | |
1005 | }, | |
1006 | }, | |
1007 | }, | |
1008 | returns: { | |
1009 | description: "A List of medium auxiliary memory attributes.", | |
1010 | type: Array, | |
1011 | items: { | |
1012 | type: MamAttribute, | |
1013 | }, | |
1014 | }, | |
1015 | )] | |
ee01737e | 1016 | /// Read Cartridge Memory (Medium auxiliary memory attributes) |
41e66bfa DC |
1017 | pub async fn cartridge_memory(drive: String) -> Result<Vec<MamAttribute>, Error> { |
1018 | run_drive_blocking_task( | |
1019 | drive.clone(), | |
1020 | "reading cartridge memory".to_string(), | |
1021 | move |config| { | |
1022 | let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; | |
1023 | let mut handle = drive_config.open()?; | |
1e20f819 | 1024 | |
41e66bfa DC |
1025 | handle.cartridge_memory() |
1026 | } | |
1027 | ) | |
1028 | .await | |
1e20f819 DM |
1029 | } |
1030 | ||
5f34d69b DM |
1031 | #[api( |
1032 | input: { | |
1033 | properties: { | |
1034 | drive: { | |
1035 | schema: DRIVE_NAME_SCHEMA, | |
1036 | }, | |
1037 | }, | |
1038 | }, | |
1039 | returns: { | |
1040 | type: Lp17VolumeStatistics, | |
1041 | }, | |
1042 | )] | |
1043 | /// Read Volume Statistics (SCSI log page 17h) | |
41e66bfa DC |
1044 | pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Error> { |
1045 | run_drive_blocking_task( | |
1046 | drive.clone(), | |
1047 | "reading volume statistics".to_string(), | |
1048 | move |config| { | |
1049 | let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; | |
1050 | let mut handle = drive_config.open()?; | |
5f34d69b | 1051 | |
41e66bfa DC |
1052 | handle.volume_statistics() |
1053 | } | |
1054 | ) | |
1055 | .await | |
5f34d69b DM |
1056 | } |
1057 | ||
cb80d900 DM |
1058 | #[api( |
1059 | input: { | |
1060 | properties: { | |
1061 | drive: { | |
1062 | schema: DRIVE_NAME_SCHEMA, | |
1063 | }, | |
1064 | }, | |
1065 | }, | |
1066 | returns: { | |
5ae86dfa | 1067 | type: LinuxDriveAndMediaStatus, |
cb80d900 DM |
1068 | }, |
1069 | )] | |
5ae86dfa | 1070 | /// Get drive/media status |
41e66bfa DC |
1071 | pub async fn status(drive: String) -> Result<LinuxDriveAndMediaStatus, Error> { |
1072 | run_drive_blocking_task( | |
1073 | drive.clone(), | |
1074 | "reading drive status".to_string(), | |
1075 | move |config| { | |
1076 | let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; | |
cb80d900 | 1077 | |
41e66bfa DC |
1078 | // Note: use open_linux_tape_device, because this also works if no medium loaded |
1079 | let file = open_linux_tape_device(&drive_config.path)?; | |
cb80d900 | 1080 | |
41e66bfa | 1081 | let mut handle = LinuxTapeHandle::new(file); |
5ae86dfa | 1082 | |
41e66bfa DC |
1083 | handle.get_drive_and_media_status() |
1084 | } | |
1085 | ) | |
1086 | .await | |
cb80d900 DM |
1087 | } |
1088 | ||
b017bbc4 DM |
1089 | #[api( |
1090 | input: { | |
1091 | properties: { | |
1092 | drive: { | |
1093 | schema: DRIVE_NAME_SCHEMA, | |
1094 | }, | |
1095 | force: { | |
1096 | description: "Force overriding existing index.", | |
1097 | type: bool, | |
1098 | optional: true, | |
1099 | }, | |
1100 | verbose: { | |
1101 | description: "Verbose mode - log all found chunks.", | |
1102 | type: bool, | |
1103 | optional: true, | |
1104 | }, | |
1105 | }, | |
1106 | }, | |
1107 | returns: { | |
1108 | schema: UPID_SCHEMA, | |
1109 | }, | |
1110 | )] | |
1111 | /// Scan media and record content | |
1112 | pub fn catalog_media( | |
1113 | drive: String, | |
1114 | force: Option<bool>, | |
1115 | verbose: Option<bool>, | |
1116 | rpcenv: &mut dyn RpcEnvironment, | |
1117 | ) -> Result<Value, Error> { | |
b017bbc4 DM |
1118 | let verbose = verbose.unwrap_or(false); |
1119 | let force = force.unwrap_or(false); | |
1120 | ||
a1c55753 DC |
1121 | let upid_str = run_drive_worker( |
1122 | rpcenv, | |
1123 | drive.clone(), | |
b017bbc4 DM |
1124 | "catalog-media", |
1125 | Some(drive.clone()), | |
a1c55753 | 1126 | move |worker, config| { |
b017bbc4 DM |
1127 | let mut drive = open_drive(&config, &drive)?; |
1128 | ||
1129 | drive.rewind()?; | |
1130 | ||
1131 | let media_id = match drive.read_label()? { | |
feb1645f | 1132 | (Some(media_id), key_config) => { |
b017bbc4 DM |
1133 | worker.log(format!( |
1134 | "found media label: {}", | |
1135 | serde_json::to_string_pretty(&serde_json::to_value(&media_id)?)? | |
1136 | )); | |
feb1645f DM |
1137 | if key_config.is_some() { |
1138 | worker.log(format!( | |
1139 | "encryption key config: {}", | |
1140 | serde_json::to_string_pretty(&serde_json::to_value(&key_config)?)? | |
1141 | )); | |
1142 | } | |
b017bbc4 DM |
1143 | media_id |
1144 | }, | |
feb1645f | 1145 | (None, _) => bail!("media is empty (no media label found)"), |
b017bbc4 DM |
1146 | }; |
1147 | ||
1148 | let status_path = Path::new(TAPE_STATUS_DIR); | |
1149 | ||
1150 | let mut inventory = Inventory::load(status_path)?; | |
cfae8f06 | 1151 | inventory.store(media_id.clone(), false)?; |
b017bbc4 DM |
1152 | |
1153 | let pool = match media_id.media_set_label { | |
1154 | None => { | |
1155 | worker.log("media is empty"); | |
1156 | MediaCatalog::destroy(status_path, &media_id.label.uuid)?; | |
1157 | return Ok(()); | |
1158 | } | |
1159 | Some(ref set) => { | |
1160 | if set.uuid.as_ref() == [0u8;16] { // media is empty | |
1161 | worker.log("media is empty"); | |
1162 | MediaCatalog::destroy(status_path, &media_id.label.uuid)?; | |
1163 | return Ok(()); | |
1164 | } | |
2b191385 DM |
1165 | let encrypt_fingerprint = set.encryption_key_fingerprint.clone() |
1166 | .map(|fp| (fp, set.uuid.clone())); | |
1167 | ||
8a0046f5 DM |
1168 | drive.set_encryption(encrypt_fingerprint)?; |
1169 | ||
b017bbc4 DM |
1170 | set.pool.clone() |
1171 | } | |
1172 | }; | |
1173 | ||
1174 | let _lock = MediaPool::lock(status_path, &pool)?; | |
1175 | ||
6334bdc1 FG |
1176 | if MediaCatalog::exists(status_path, &media_id.label.uuid) && !force { |
1177 | bail!("media catalog exists (please use --force to overwrite)"); | |
b017bbc4 DM |
1178 | } |
1179 | ||
1180 | restore_media(&worker, &mut drive, &media_id, None, verbose)?; | |
1181 | ||
1182 | Ok(()) | |
a1c55753 | 1183 | }, |
b017bbc4 DM |
1184 | )?; |
1185 | ||
1186 | Ok(upid_str.into()) | |
1187 | } | |
1188 | ||
5fdaecf6 DC |
1189 | #[api( |
1190 | input: { | |
18262a88 DC |
1191 | properties: { |
1192 | changer: { | |
1193 | schema: CHANGER_NAME_SCHEMA, | |
1194 | optional: true, | |
1195 | }, | |
1196 | }, | |
5fdaecf6 DC |
1197 | }, |
1198 | returns: { | |
1199 | description: "The list of configured drives with model information.", | |
1200 | type: Array, | |
1201 | items: { | |
1202 | type: DriveListEntry, | |
1203 | }, | |
1204 | }, | |
1205 | )] | |
1206 | /// List drives | |
1207 | pub fn list_drives( | |
18262a88 | 1208 | changer: Option<String>, |
5fdaecf6 DC |
1209 | _param: Value, |
1210 | ) -> Result<Vec<DriveListEntry>, Error> { | |
1211 | ||
1212 | let (config, _) = config::drive::config()?; | |
1213 | ||
1214 | let linux_drives = linux_tape_device_list(); | |
1215 | ||
1216 | let drive_list: Vec<LinuxTapeDrive> = config.convert_to_typed_array("linux")?; | |
1217 | ||
1218 | let mut list = Vec::new(); | |
1219 | ||
1220 | for drive in drive_list { | |
18262a88 DC |
1221 | if changer.is_some() && drive.changer != changer { |
1222 | continue; | |
1223 | } | |
1224 | ||
b5b99a52 DM |
1225 | let info = lookup_device_identification(&linux_drives, &drive.path); |
1226 | let entry = DriveListEntry { config: drive, info }; | |
5fdaecf6 DC |
1227 | list.push(entry); |
1228 | } | |
1229 | ||
1230 | Ok(list) | |
1231 | } | |
1232 | ||
55118ca1 DM |
1233 | #[sortable] |
1234 | pub const SUBDIRS: SubdirMap = &sorted!([ | |
bff7e3f3 DM |
1235 | ( |
1236 | "barcode-label-media", | |
1237 | &Router::new() | |
c297835b | 1238 | .post(&API_METHOD_BARCODE_LABEL_MEDIA) |
bff7e3f3 | 1239 | ), |
b017bbc4 DM |
1240 | ( |
1241 | "catalog", | |
1242 | &Router::new() | |
c297835b | 1243 | .post(&API_METHOD_CATALOG_MEDIA) |
b017bbc4 | 1244 | ), |
df69a4fc DM |
1245 | ( |
1246 | "clean", | |
1247 | &Router::new() | |
1248 | .put(&API_METHOD_CLEAN_DRIVE) | |
1249 | ), | |
583a68a4 | 1250 | ( |
55118ca1 | 1251 | "eject-media", |
583a68a4 | 1252 | &Router::new() |
41dacd5d | 1253 | .post(&API_METHOD_EJECT_MEDIA) |
583a68a4 | 1254 | ), |
0098b712 | 1255 | ( |
55118ca1 | 1256 | "erase-media", |
0098b712 | 1257 | &Router::new() |
eb1dfb02 | 1258 | .post(&API_METHOD_ERASE_MEDIA) |
0098b712 | 1259 | ), |
5243df47 DM |
1260 | ( |
1261 | "export-media", | |
1262 | &Router::new() | |
1263 | .put(&API_METHOD_EXPORT_MEDIA) | |
1264 | ), | |
83abc749 DM |
1265 | ( |
1266 | "inventory", | |
1267 | &Router::new() | |
1268 | .get(&API_METHOD_INVENTORY) | |
e92c7581 | 1269 | .put(&API_METHOD_UPDATE_INVENTORY) |
83abc749 | 1270 | ), |
7bb720cb DM |
1271 | ( |
1272 | "label-media", | |
1273 | &Router::new() | |
5243df47 | 1274 | .post(&API_METHOD_LABEL_MEDIA) |
7bb720cb | 1275 | ), |
f45dceeb DC |
1276 | ( |
1277 | "load-media", | |
1278 | &Router::new() | |
d0647e5a | 1279 | .post(&API_METHOD_LOAD_MEDIA) |
f45dceeb | 1280 | ), |
5d908606 DM |
1281 | ( |
1282 | "load-slot", | |
1283 | &Router::new() | |
1284 | .put(&API_METHOD_LOAD_SLOT) | |
1285 | ), | |
1e20f819 | 1286 | ( |
ee01737e | 1287 | "cartridge-memory", |
1e20f819 | 1288 | &Router::new() |
cef4654f | 1289 | .get(&API_METHOD_CARTRIDGE_MEMORY) |
1e20f819 | 1290 | ), |
5f34d69b DM |
1291 | ( |
1292 | "volume-statistics", | |
1293 | &Router::new() | |
cef4654f | 1294 | .get(&API_METHOD_VOLUME_STATISTICS) |
5f34d69b | 1295 | ), |
83abc749 DM |
1296 | ( |
1297 | "read-label", | |
1298 | &Router::new() | |
1299 | .get(&API_METHOD_READ_LABEL) | |
1300 | ), | |
f70d8091 DM |
1301 | ( |
1302 | "rewind", | |
1303 | &Router::new() | |
eb1dfb02 | 1304 | .post(&API_METHOD_REWIND) |
f70d8091 | 1305 | ), |
cb80d900 DM |
1306 | ( |
1307 | "status", | |
1308 | &Router::new() | |
1309 | .get(&API_METHOD_STATUS) | |
1310 | ), | |
5d908606 DM |
1311 | ( |
1312 | "unload", | |
1313 | &Router::new() | |
d0647e5a | 1314 | .post(&API_METHOD_UNLOAD) |
5d908606 | 1315 | ), |
55118ca1 | 1316 | ]); |
5d908606 | 1317 | |
5fdaecf6 | 1318 | const ITEM_ROUTER: Router = Router::new() |
5d908606 | 1319 | .get(&list_subdirs_api_method!(SUBDIRS)) |
5fdaecf6 DC |
1320 | .subdirs(&SUBDIRS); |
1321 | ||
1322 | pub const ROUTER: Router = Router::new() | |
1323 | .get(&API_METHOD_LIST_DRIVES) | |
1324 | .match_all("drive", &ITEM_ROUTER); |