1 use std
::collections
::HashMap
;
2 use std
::panic
::UnwindSafe
;
6 use anyhow
::{bail, format_err, Error}
;
10 list_subdirs_api_method
, Permission
, Router
, RpcEnvironment
, RpcEnvironmentType
, SubdirMap
,
12 use proxmox_schema
::api
;
13 use proxmox_section_config
::SectionConfigData
;
14 use proxmox_sys
::sortable
;
15 use proxmox_sys
::{task_log, task_warn}
;
16 use proxmox_uuid
::Uuid
;
19 Authid
, DriveListEntry
, LabelUuidMap
, Lp17VolumeStatistics
, LtoDriveAndMediaStatus
,
20 LtoTapeDrive
, MamAttribute
, MediaIdFlat
, CHANGER_NAME_SCHEMA
, DRIVE_NAME_SCHEMA
,
21 MEDIA_LABEL_SCHEMA
, MEDIA_POOL_NAME_SCHEMA
, UPID_SCHEMA
,
24 use pbs_api_types
::{PRIV_TAPE_AUDIT, PRIV_TAPE_READ, PRIV_TAPE_WRITE}
;
26 use pbs_config
::tape_encryption_keys
::insert_key
;
27 use pbs_config
::CachedUserInfo
;
29 linux_list_drives
::{lookup_device_identification, lto_tape_device_list, open_lto_tape_device}
,
30 sg_tape
::tape_alert_flags_critical
,
33 use proxmox_rest_server
::WorkerTask
;
36 api2
::tape
::restore
::{fast_catalog_restore, restore_media}
,
38 changer
::update_changer_online_status
,
40 get_tape_device_state
, lock_tape_device
, media_changer
, open_drive
,
41 open_lto_tape_drive
, required_media_changer
, set_tape_device_state
, LtoTapeHandle
,
44 file_formats
::{MediaLabel, MediaSetLabel}
,
45 lock_media_pool
, lock_media_set
, lock_unassigned_media_pool
, Inventory
, MediaCatalog
,
46 MediaId
, TAPE_STATUS_DIR
,
50 fn run_drive_worker
<F
>(
51 rpcenv
: &dyn RpcEnvironment
,
54 job_id
: Option
<String
>,
56 ) -> Result
<String
, Error
>
61 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
63 // early check/lock before starting worker
64 let (config
, _digest
) = pbs_config
::drive
::config()?
;
65 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
67 let auth_id
= rpcenv
.get_auth_id().unwrap();
68 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
70 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
71 let _lock_guard
= lock_guard
;
72 set_tape_device_state(&drive
, &worker
.upid().to_string())
73 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
75 let result
= f(worker
, config
);
76 set_tape_device_state(&drive
, "")
77 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
82 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
84 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
87 // early check/lock before starting worker
88 let (config
, _digest
) = pbs_config
::drive
::config()?
;
89 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
90 tokio
::task
::spawn_blocking(move || {
91 let _lock_guard
= lock_guard
;
92 set_tape_device_state(&drive
, &state
)
93 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
94 let result
= f(config
);
95 set_tape_device_state(&drive
, "")
96 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
106 schema
: DRIVE_NAME_SCHEMA
,
109 schema
: MEDIA_LABEL_SCHEMA
,
117 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
120 /// Load media with specified label
122 /// Issue a media load request to the associated changer device.
126 rpcenv
: &mut dyn RpcEnvironment
,
127 ) -> Result
<Value
, Error
> {
128 let job_id
= format
!("{}:{}", drive
, label_text
);
130 let upid_str
= run_drive_worker(
135 move |worker
, config
| {
138 "loading media '{}' into drive '{}'",
142 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
143 changer
.load_media(&label_text
)?
;
155 schema
: DRIVE_NAME_SCHEMA
,
158 description
: "Source slot number.",
164 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
167 /// Load media from the specified slot
169 /// Issue a media load request to the associated changer device.
170 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
171 run_drive_blocking_task(
173 format
!("load from slot {}", source_slot
),
175 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
176 changer
.load_media_from_slot(source_slot
)?
;
187 schema
: DRIVE_NAME_SCHEMA
,
190 schema
: MEDIA_LABEL_SCHEMA
,
195 description
: "The import-export slot number the media was transferred to.",
200 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
203 /// Export media with specified label
204 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
205 run_drive_blocking_task(
207 format
!("export media {}", label_text
),
209 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
210 match changer
.export_media(&label_text
)?
{
211 Some(slot
) => Ok(slot
),
213 "media '{}' is not online (via changer '{}')",
227 schema
: DRIVE_NAME_SCHEMA
,
230 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
240 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
243 /// Unload media via changer
246 target_slot
: Option
<u64>,
247 rpcenv
: &mut dyn RpcEnvironment
,
248 ) -> Result
<Value
, Error
> {
249 let upid_str
= run_drive_worker(
254 move |worker
, config
| {
255 task_log
!(worker
, "unloading media from drive '{}'", drive
);
257 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
258 changer
.unload_media(target_slot
)?
;
270 schema
: DRIVE_NAME_SCHEMA
,
273 description
: "Use fast erase.",
279 schema
: MEDIA_LABEL_SCHEMA
,
288 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
291 /// Format media. Check for label-text if given (cancels if wrong media).
295 label_text
: Option
<String
>,
296 rpcenv
: &mut dyn RpcEnvironment
,
297 ) -> Result
<Value
, Error
> {
298 let upid_str
= run_drive_worker(
303 move |worker
, config
| {
304 if let Some(ref label
) = label_text
{
305 task_log
!(worker
, "try to load media '{}'", label
);
306 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
307 changer
.load_media(label
)?
;
311 let mut handle
= open_drive(&config
, &drive
)?
;
313 match handle
.read_label() {
315 if let Some(label
) = label_text
{
316 bail
!("expected label '{}', found unrelated data", label
);
318 /* assume drive contains no or unrelated data */
319 task_log
!(worker
, "unable to read media label: {}", err
);
320 task_log
!(worker
, "format anyways");
321 handle
.format_media(fast
.unwrap_or(true))?
;
324 if let Some(label
) = label_text
{
325 bail
!("expected label '{}', found empty tape", label
);
327 task_log
!(worker
, "found empty media - format anyways");
328 handle
.format_media(fast
.unwrap_or(true))?
;
330 Ok((Some(media_id
), _key_config
)) => {
331 if let Some(label_text
) = label_text
{
332 if media_id
.label
.label_text
!= label_text
{
334 "expected label '{}', found '{}', aborting",
336 media_id
.label
.label_text
343 "found media '{}' with uuid '{}'",
344 media_id
.label
.label_text
,
348 let status_path
= Path
::new(TAPE_STATUS_DIR
);
349 let mut inventory
= Inventory
::new(status_path
);
351 if let Some(MediaSetLabel
{
352 ref pool
, ref uuid
, ..
353 }) = media_id
.media_set_label
355 let _pool_lock
= lock_media_pool(status_path
, pool
)?
;
356 let _media_set_lock
= lock_media_set(status_path
, uuid
, None
)?
;
357 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
358 inventory
.remove_media(&media_id
.label
.uuid
)?
;
360 let _lock
= lock_unassigned_media_pool(status_path
)?
;
361 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
362 inventory
.remove_media(&media_id
.label
.uuid
)?
;
365 handle
.format_media(fast
.unwrap_or(true))?
;
380 schema
: DRIVE_NAME_SCHEMA
,
388 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
392 pub fn rewind(drive
: String
, rpcenv
: &mut dyn RpcEnvironment
) -> Result
<Value
, Error
> {
393 let upid_str
= run_drive_worker(
398 move |_worker
, config
| {
399 let mut drive
= open_drive(&config
, &drive
)?
;
412 schema
: DRIVE_NAME_SCHEMA
,
420 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
423 /// Eject/Unload drive media
424 pub fn eject_media(drive
: String
, rpcenv
: &mut dyn RpcEnvironment
) -> Result
<Value
, Error
> {
425 let upid_str
= run_drive_worker(
430 move |_worker
, config
| {
431 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
432 changer
.unload_media(None
)?
;
434 let mut drive
= open_drive(&config
, &drive
)?
;
435 drive
.eject_media()?
;
448 schema
: DRIVE_NAME_SCHEMA
,
451 schema
: MEDIA_LABEL_SCHEMA
,
454 schema
: MEDIA_POOL_NAME_SCHEMA
,
463 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
468 /// Write a new media label to the media in 'drive'. The media is
469 /// assigned to the specified 'pool', or else to the free media pool.
471 /// Note: The media need to be empty (you may want to format it first).
474 pool
: Option
<String
>,
476 rpcenv
: &mut dyn RpcEnvironment
,
477 ) -> Result
<Value
, Error
> {
478 if let Some(ref pool
) = pool
{
479 let (pool_config
, _digest
) = pbs_config
::media_pool
::config()?
;
481 if pool_config
.sections
.get(pool
).is_none() {
482 bail
!("no such pool ('{}')", pool
);
485 let upid_str
= run_drive_worker(
490 move |worker
, config
| {
491 let mut drive
= open_drive(&config
, &drive
)?
;
495 match drive
.read_next_file() {
496 Ok(_reader
) => bail
!("media is not empty (format it first)"),
497 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
498 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
500 bail
!("media read error - {}", err
);
504 let ctime
= proxmox_time
::epoch_i64();
505 let label
= MediaLabel
{
506 label_text
: label_text
.to_string(),
507 uuid
: Uuid
::generate(),
511 write_media_label(worker
, &mut drive
, label
, pool
)
518 fn write_media_label(
519 worker
: Arc
<WorkerTask
>,
520 drive
: &mut Box
<dyn TapeDriver
>,
522 pool
: Option
<String
>,
523 ) -> Result
<(), Error
> {
524 drive
.label_tape(&label
)?
;
526 let status_path
= Path
::new(TAPE_STATUS_DIR
);
528 let media_id
= if let Some(ref pool
) = pool
{
529 // assign media to pool by writing special media set label
532 "Label media '{}' for pool '{}'",
536 let set
= MediaSetLabel
::with_data(pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
538 drive
.write_media_set_label(&set
, None
)?
;
540 let media_id
= MediaId
{
542 media_set_label
: Some(set
),
545 // Create the media catalog
546 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
548 let mut inventory
= Inventory
::new(status_path
);
549 inventory
.store(media_id
.clone(), false)?
;
555 "Label media '{}' (no pool assignment)",
559 let media_id
= MediaId
{
561 media_set_label
: None
,
564 // Create the media catalog
565 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
567 let mut inventory
= Inventory
::new(status_path
);
568 inventory
.store(media_id
.clone(), false)?
;
575 match drive
.read_label() {
576 Ok((Some(info
), _
)) => {
577 if info
.label
.uuid
!= media_id
.label
.uuid
{
578 bail
!("verify label failed - got wrong label uuid");
580 if let Some(ref pool
) = pool
{
581 match info
.media_set_label
{
583 if set
.uuid
!= [0u8; 16].into() {
584 bail
!("verify media set label failed - got wrong set uuid");
586 if &set
.pool
!= pool
{
587 bail
!("verify media set label failed - got wrong pool");
591 bail
!("verify media set label failed (missing set label)");
596 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
597 Err(err
) => bail
!("verify label failed - {}", err
),
610 schema
: DRIVE_NAME_SCHEMA
,
611 //description: "Restore the key from this drive the (encrypted) key was saved on.",
614 description
: "The password the key was encrypted with.",
619 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
622 /// Try to restore a tape encryption key
623 pub async
fn restore_key(
626 ) -> Result
<(), Error
> {
628 run_drive_blocking_task(drive
.clone(), "restore key".to_string(), move |config
| {
629 let mut drive
= open_drive(&config
, &drive
)?
;
631 let (_media_id
, key_config
) = drive
.read_label()?
;
633 if let Some(key_config
) = key_config
{
634 let password_fn
= || Ok(password
.as_bytes().to_vec());
635 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
636 insert_key(key
, key_config
, true)?
;
638 bail
!("media does not contain any encryption key configuration");
652 schema
: DRIVE_NAME_SCHEMA
,
655 description
: "Inventorize media",
664 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
667 /// Read media label (optionally inventorize media)
668 pub async
fn read_label(drive
: String
, inventorize
: Option
<bool
>) -> Result
<MediaIdFlat
, Error
> {
669 run_drive_blocking_task(drive
.clone(), "reading label".to_string(), move |config
| {
670 let mut drive
= open_drive(&config
, &drive
)?
;
672 let (media_id
, _key_config
) = drive
.read_label()?
;
673 let media_id
= media_id
.ok_or(format_err
!("Media is empty (no label)."))?
;
675 let label
= if let Some(ref set
) = media_id
.media_set_label
{
676 let key
= &set
.encryption_key_fingerprint
;
678 if let Err(err
) = drive
.set_encryption(key
.clone().map(|fp
| (fp
, set
.uuid
.clone()))) {
679 eprintln
!("unable to load encryption key: {}", err
); // best-effort only
682 ctime
: media_id
.label
.ctime
,
683 encryption_key_fingerprint
: key
.as_ref().map(|fp
| fp
.signature()),
684 label_text
: media_id
.label
.label_text
.clone(),
685 media_set_ctime
: Some(set
.ctime
),
686 media_set_uuid
: Some(set
.uuid
.clone()),
687 pool
: Some(set
.pool
.clone()),
688 seq_nr
: Some(set
.seq_nr
),
689 uuid
: media_id
.label
.uuid
.clone(),
693 ctime
: media_id
.label
.ctime
,
694 encryption_key_fingerprint
: None
,
695 label_text
: media_id
.label
.label_text
.clone(),
696 media_set_ctime
: None
,
697 media_set_uuid
: None
,
700 uuid
: media_id
.label
.uuid
.clone(),
704 if let Some(true) = inventorize
{
705 let state_path
= Path
::new(TAPE_STATUS_DIR
);
706 let mut inventory
= Inventory
::new(state_path
);
708 if let Some(MediaSetLabel
{
709 ref pool
, ref uuid
, ..
710 }) = media_id
.media_set_label
712 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
713 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
714 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
715 inventory
.store(media_id
, false)?
;
717 let _lock
= lock_unassigned_media_pool(state_path
)?
;
718 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
719 inventory
.store(media_id
, false)?
;
732 schema
: DRIVE_NAME_SCHEMA
,
740 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
744 pub fn clean_drive(drive
: String
, rpcenv
: &mut dyn RpcEnvironment
) -> Result
<Value
, Error
> {
745 let upid_str
= run_drive_worker(
750 move |worker
, config
| {
751 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
753 task_log
!(worker
, "Starting drive clean");
755 changer
.clean_drive()?
;
757 if let Ok(drive_config
) = config
.lookup
::<LtoTapeDrive
>("lto", &drive
) {
758 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
759 let mut handle
= LtoTapeHandle
::new(open_lto_tape_device(&drive_config
.path
)?
)?
;
761 // test for critical tape alert flags
762 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
763 if !alert_flags
.is_empty() {
764 task_log
!(worker
, "TapeAlertFlags: {:?}", alert_flags
);
765 if tape_alert_flags_critical(alert_flags
) {
766 bail
!("found critical tape alert flags: {:?}", alert_flags
);
771 // test wearout (max. 50 mounts)
772 if let Ok(volume_stats
) = handle
.volume_statistics() {
773 task_log
!(worker
, "Volume mounts: {}", volume_stats
.volume_mounts
);
774 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
775 task_log
!(worker
, "Cleaning tape wearout: {}%", wearout
);
779 task_log
!(worker
, "Drive cleaned successfully");
792 schema
: DRIVE_NAME_SCHEMA
,
797 description
: "The list of media labels with associated media Uuid (if any).",
804 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
807 /// List known media labels (Changer Inventory)
809 /// Note: Only useful for drives with associated changer device.
811 /// This method queries the changer to get a list of media labels.
813 /// Note: This updates the media online status.
814 pub async
fn inventory(drive
: String
) -> Result
<Vec
<LabelUuidMap
>, Error
> {
815 run_drive_blocking_task(drive
.clone(), "inventorize".to_string(), move |config
| {
816 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
818 let label_text_list
= changer
.online_media_label_texts()?
;
820 let state_path
= Path
::new(TAPE_STATUS_DIR
);
822 let mut inventory
= Inventory
::load(state_path
)?
;
824 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
826 let mut list
= Vec
::new();
828 for label_text
in label_text_list
.iter() {
829 if label_text
.starts_with("CLN") {
830 // skip cleaning unit
834 let label_text
= label_text
.to_string();
836 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
837 list
.push(LabelUuidMap
{
839 uuid
: Some(media_id
.label
.uuid
.clone()),
842 list
.push(LabelUuidMap
{
858 schema
: DRIVE_NAME_SCHEMA
,
861 description
: "Load all tapes and try read labels (even if already inventoried)",
871 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
876 /// Note: Only useful for drives with associated changer device.
878 /// This method queries the changer to get a list of media labels. It
879 /// then loads any unknown media into the drive, reads the label, and
880 /// store the result to the media database.
882 /// Note: This updates the media online status.
883 pub fn update_inventory(
885 read_all_labels
: Option
<bool
>,
886 rpcenv
: &mut dyn RpcEnvironment
,
887 ) -> Result
<Value
, Error
> {
888 let upid_str
= run_drive_worker(
893 move |worker
, config
| {
894 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
896 let label_text_list
= changer
.online_media_label_texts()?
;
897 if label_text_list
.is_empty() {
898 task_log
!(worker
, "changer device does not list any media labels");
901 let state_path
= Path
::new(TAPE_STATUS_DIR
);
903 let mut inventory
= Inventory
::load(state_path
)?
;
905 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
907 for label_text
in label_text_list
.iter() {
908 if label_text
.starts_with("CLN") {
909 task_log
!(worker
, "skip cleaning unit '{}'", label_text
);
913 let label_text
= label_text
.to_string();
915 if !read_all_labels
.unwrap_or(false)
916 && inventory
.find_media_by_label_text(&label_text
).is_some()
918 task_log
!(worker
, "media '{}' already inventoried", label_text
);
922 if let Err(err
) = changer
.load_media(&label_text
) {
923 task_warn
!(worker
, "unable to load media '{}' - {}", label_text
, err
);
927 let mut drive
= open_drive(&config
, &drive
)?
;
928 match drive
.read_label() {
932 "unable to read label form media '{}' - {}",
938 task_log
!(worker
, "media '{}' is empty", label_text
);
940 Ok((Some(media_id
), _key_config
)) => {
941 if label_text
!= media_id
.label
.label_text
{
944 "label text mismatch ({} != {})",
946 media_id
.label
.label_text
952 "inventorize media '{}' with uuid '{}'",
957 if let Some(MediaSetLabel
{
958 ref pool
, ref uuid
, ..
959 }) = media_id
.media_set_label
961 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
962 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
963 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
964 inventory
.store(media_id
, false)?
;
966 let _lock
= lock_unassigned_media_pool(state_path
)?
;
967 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
968 inventory
.store(media_id
, false)?
;
972 changer
.unload_media(None
)?
;
985 schema
: DRIVE_NAME_SCHEMA
,
988 schema
: MEDIA_POOL_NAME_SCHEMA
,
997 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
1000 /// Label media with barcodes from changer device
1001 pub fn barcode_label_media(
1003 pool
: Option
<String
>,
1004 rpcenv
: &mut dyn RpcEnvironment
,
1005 ) -> Result
<Value
, Error
> {
1006 if let Some(ref pool
) = pool
{
1007 let (pool_config
, _digest
) = pbs_config
::media_pool
::config()?
;
1009 if pool_config
.sections
.get(pool
).is_none() {
1010 bail
!("no such pool ('{}')", pool
);
1014 let upid_str
= run_drive_worker(
1017 "barcode-label-media",
1018 Some(drive
.clone()),
1019 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1025 fn barcode_label_media_worker(
1026 worker
: Arc
<WorkerTask
>,
1028 drive_config
: &SectionConfigData
,
1029 pool
: Option
<String
>,
1030 ) -> Result
<(), Error
> {
1031 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1033 let mut label_text_list
= changer
.online_media_label_texts()?
;
1035 // make sure we label them in the right order
1036 label_text_list
.sort();
1038 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1040 let mut inventory
= Inventory
::load(state_path
)?
;
1042 update_changer_online_status(
1049 if label_text_list
.is_empty() {
1050 bail
!("changer device does not list any media labels");
1053 for label_text
in label_text_list
{
1054 if label_text
.starts_with("CLN") {
1058 inventory
.reload()?
;
1059 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1062 "media '{}' already inventoried (already labeled)",
1068 task_log
!(worker
, "checking/loading media '{}'", label_text
);
1070 if let Err(err
) = changer
.load_media(&label_text
) {
1071 task_warn
!(worker
, "unable to load media '{}' - {}", label_text
, err
);
1075 let mut drive
= open_drive(drive_config
, &drive
)?
;
1078 match drive
.read_next_file() {
1082 "media '{}' is not empty (format it first)",
1087 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
1088 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
1092 "media '{}' read error (maybe not empty - format it first)",
1099 let ctime
= proxmox_time
::epoch_i64();
1100 let label
= MediaLabel
{
1101 label_text
: label_text
.to_string(),
1102 uuid
: Uuid
::generate(),
1106 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1116 schema
: DRIVE_NAME_SCHEMA
,
1121 description
: "A List of medium auxiliary memory attributes.",
1128 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1131 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1132 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1133 run_drive_blocking_task(
1135 "reading cartridge memory".to_string(),
1137 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1138 let mut handle
= open_lto_tape_drive(&drive_config
)?
;
1140 handle
.cartridge_memory()
1150 schema
: DRIVE_NAME_SCHEMA
,
1155 type: Lp17VolumeStatistics
,
1158 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1161 /// Read Volume Statistics (SCSI log page 17h)
1162 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1163 run_drive_blocking_task(
1165 "reading volume statistics".to_string(),
1167 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1168 let mut handle
= open_lto_tape_drive(&drive_config
)?
;
1170 handle
.volume_statistics()
1180 schema
: DRIVE_NAME_SCHEMA
,
1185 type: LtoDriveAndMediaStatus
,
1188 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1191 /// Get drive/media status
1192 pub async
fn status(drive
: String
) -> Result
<LtoDriveAndMediaStatus
, Error
> {
1193 run_drive_blocking_task(
1195 "reading drive status".to_string(),
1197 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1199 // Note: use open_lto_tape_device, because this also works if no medium loaded
1200 let file
= open_lto_tape_device(&drive_config
.path
)?
;
1202 let mut handle
= LtoTapeHandle
::new(file
)?
;
1204 handle
.get_drive_and_media_status()
1214 schema
: DRIVE_NAME_SCHEMA
,
1217 description
: "Force overriding existing index.",
1222 description
: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
1227 description
: "Verbose mode - log all found chunks.",
1234 schema
: UPID_SCHEMA
,
1237 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1240 /// Scan media and record content
1241 pub fn catalog_media(
1243 force
: Option
<bool
>,
1245 verbose
: Option
<bool
>,
1246 rpcenv
: &mut dyn RpcEnvironment
,
1247 ) -> Result
<Value
, Error
> {
1248 let verbose
= verbose
.unwrap_or(false);
1249 let force
= force
.unwrap_or(false);
1250 let scan
= scan
.unwrap_or(false);
1252 let upid_str
= run_drive_worker(
1256 Some(drive
.clone()),
1257 move |worker
, config
| {
1258 let mut drive
= open_drive(&config
, &drive
)?
;
1262 let media_id
= match drive
.read_label()?
{
1263 (Some(media_id
), key_config
) => {
1266 "found media label: {}",
1267 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1269 if key_config
.is_some() {
1272 "encryption key config: {}",
1273 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1278 (None
, _
) => bail
!("media is empty (no media label found)"),
1281 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1283 let mut inventory
= Inventory
::new(status_path
);
1285 let (_media_set_lock
, media_set_uuid
) = match media_id
.media_set_label
{
1287 task_log
!(worker
, "media is empty");
1288 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1289 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1290 inventory
.store(media_id
.clone(), false)?
;
1294 if set
.uuid
.as_ref() == [0u8; 16] {
1296 task_log
!(worker
, "media is empty");
1297 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1298 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1299 inventory
.store(media_id
.clone(), false)?
;
1302 let encrypt_fingerprint
= set
1303 .encryption_key_fingerprint
1305 .map(|fp
| (fp
, set
.uuid
.clone()));
1307 drive
.set_encryption(encrypt_fingerprint
)?
;
1309 let _pool_lock
= lock_media_pool(status_path
, &set
.pool
)?
;
1310 let media_set_lock
= lock_media_set(status_path
, &set
.uuid
, None
)?
;
1312 MediaCatalog
::destroy_unrelated_catalog(status_path
, &media_id
)?
;
1314 inventory
.store(media_id
.clone(), false)?
;
1316 (media_set_lock
, &set
.uuid
)
1320 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1321 bail
!("media catalog exists (please use --force to overwrite)");
1325 let media_set
= inventory
.compute_media_set_members(media_set_uuid
)?
;
1327 if fast_catalog_restore(&worker
, &mut drive
, &media_set
, &media_id
.label
.uuid
)?
{
1331 task_log
!(worker
, "no catalog found");
1334 task_log
!(worker
, "scanning entire media to reconstruct catalog");
1337 drive
.read_label()?
; // skip over labels - we already read them above
1339 let mut checked_chunks
= HashMap
::new();
1345 &mut checked_chunks
,
1360 schema
: CHANGER_NAME_SCHEMA
,
1366 description
: "The list of configured drives with model information.",
1369 type: DriveListEntry
,
1373 description
: "List configured tape drives filtered by Tape.Audit privileges",
1374 permission
: &Permission
::Anybody
,
1379 changer
: Option
<String
>,
1381 rpcenv
: &mut dyn RpcEnvironment
,
1382 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1383 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1384 let user_info
= CachedUserInfo
::new()?
;
1386 let (config
, _
) = pbs_config
::drive
::config()?
;
1388 let lto_drives
= lto_tape_device_list();
1390 let drive_list
: Vec
<LtoTapeDrive
> = config
.convert_to_typed_array("lto")?
;
1392 let mut list
= Vec
::new();
1394 for drive
in drive_list
{
1395 if changer
.is_some() && drive
.changer
!= changer
{
1399 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1400 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1404 let info
= lookup_device_identification(<o_drives
, &drive
.path
);
1405 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1406 let entry
= DriveListEntry
{
1418 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1420 "barcode-label-media",
1421 &Router
::new().post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1423 ("catalog", &Router
::new().post(&API_METHOD_CATALOG_MEDIA
)),
1424 ("clean", &Router
::new().put(&API_METHOD_CLEAN_DRIVE
)),
1425 ("eject-media", &Router
::new().post(&API_METHOD_EJECT_MEDIA
)),
1428 &Router
::new().post(&API_METHOD_FORMAT_MEDIA
)
1430 ("export-media", &Router
::new().put(&API_METHOD_EXPORT_MEDIA
)),
1434 .get(&API_METHOD_INVENTORY
)
1435 .put(&API_METHOD_UPDATE_INVENTORY
)
1437 ("label-media", &Router
::new().post(&API_METHOD_LABEL_MEDIA
)),
1438 ("load-media", &Router
::new().post(&API_METHOD_LOAD_MEDIA
)),
1439 ("load-slot", &Router
::new().post(&API_METHOD_LOAD_SLOT
)),
1442 &Router
::new().get(&API_METHOD_CARTRIDGE_MEMORY
)
1445 "volume-statistics",
1446 &Router
::new().get(&API_METHOD_VOLUME_STATISTICS
)
1448 ("read-label", &Router
::new().get(&API_METHOD_READ_LABEL
)),
1449 ("restore-key", &Router
::new().post(&API_METHOD_RESTORE_KEY
)),
1450 ("rewind", &Router
::new().post(&API_METHOD_REWIND
)),
1451 ("status", &Router
::new().get(&API_METHOD_STATUS
)),
1452 ("unload", &Router
::new().post(&API_METHOD_UNLOAD
)),
1455 const ITEM_ROUTER
: Router
= Router
::new()
1456 .get(&list_subdirs_api_method
!(SUBDIRS
))
1459 pub const ROUTER
: Router
= Router
::new()
1460 .get(&API_METHOD_LIST_DRIVES
)
1461 .match_all("drive", &ITEM_ROUTER
);