1 use std
::panic
::UnwindSafe
;
5 use anyhow
::{bail, format_err, Error}
;
11 list_subdirs_api_method
,
16 section_config
::SectionConfigData
,
29 cached_user_info
::CachedUserInfo
,
42 MEDIA_POOL_NAME_SCHEMA
,
49 LinuxDriveAndMediaStatus
,
51 tape
::restore
::restore_media
,
61 lock_unassigned_media_pool
,
62 linux_tape_device_list
,
63 lookup_device_identification
,
72 open_linux_tape_device
,
74 required_media_changer
,
77 set_tape_device_state
,
78 get_tape_device_state
,
79 tape_alert_flags_critical
,
81 changer
::update_changer_online_status
,
85 fn run_drive_worker
<F
>(
86 rpcenv
: &dyn RpcEnvironment
,
89 job_id
: Option
<String
>,
91 ) -> Result
<String
, Error
>
96 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
98 // early check/lock before starting worker
99 let (config
, _digest
) = config
::drive
::config()?
;
100 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
102 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
103 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
105 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
106 let _lock_guard
= lock_guard
;
107 set_tape_device_state(&drive
, &worker
.upid().to_string())
108 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
110 let result
= f(worker
, config
);
111 set_tape_device_state(&drive
, "")
112 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
117 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
119 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
122 // early check/lock before starting worker
123 let (config
, _digest
) = config
::drive
::config()?
;
124 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
125 tokio
::task
::spawn_blocking(move || {
126 let _lock_guard
= lock_guard
;
127 set_tape_device_state(&drive
, &state
)
128 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
129 let result
= f(config
);
130 set_tape_device_state(&drive
, "")
131 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
141 schema
: DRIVE_NAME_SCHEMA
,
144 schema
: MEDIA_LABEL_SCHEMA
,
152 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
155 /// Load media with specified label
157 /// Issue a media load request to the associated changer device.
161 rpcenv
: &mut dyn RpcEnvironment
,
162 ) -> Result
<Value
, Error
> {
163 let job_id
= format
!("{}:{}", drive
, label_text
);
165 let upid_str
= run_drive_worker(
170 move |worker
, config
| {
171 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
172 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
173 changer
.load_media(&label_text
)?
;
185 schema
: DRIVE_NAME_SCHEMA
,
188 description
: "Source slot number.",
194 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
197 /// Load media from the specified slot
199 /// Issue a media load request to the associated changer device.
200 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
201 run_drive_blocking_task(
203 format
!("load from slot {}", source_slot
),
205 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
206 changer
.load_media_from_slot(source_slot
)?
;
217 schema
: DRIVE_NAME_SCHEMA
,
220 schema
: MEDIA_LABEL_SCHEMA
,
225 description
: "The import-export slot number the media was transferred to.",
230 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
233 /// Export media with specified label
234 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
235 run_drive_blocking_task(
237 format
!("export media {}", label_text
),
239 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
240 match changer
.export_media(&label_text
)?
{
241 Some(slot
) => Ok(slot
),
243 "media '{}' is not online (via changer '{}')",
257 schema
: DRIVE_NAME_SCHEMA
,
260 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
270 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
273 /// Unload media via changer
276 target_slot
: Option
<u64>,
277 rpcenv
: &mut dyn RpcEnvironment
,
278 ) -> Result
<Value
, Error
> {
279 let upid_str
= run_drive_worker(
284 move |worker
, config
| {
285 task_log
!(worker
, "unloading media from drive '{}'", drive
);
287 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
288 changer
.unload_media(target_slot
)?
;
300 schema
: DRIVE_NAME_SCHEMA
,
303 description
: "Use fast erase.",
309 schema
: MEDIA_LABEL_SCHEMA
,
318 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
321 /// Erase media. Check for label-text if given (cancels if wrong media).
325 label_text
: Option
<String
>,
326 rpcenv
: &mut dyn RpcEnvironment
,
327 ) -> Result
<Value
, Error
> {
328 let upid_str
= run_drive_worker(
333 move |worker
, config
| {
334 if let Some(ref label
) = label_text
{
335 task_log
!(worker
, "try to load media '{}'", label
);
336 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
337 changer
.load_media(label
)?
;
341 let mut handle
= open_drive(&config
, &drive
)?
;
343 match handle
.read_label() {
345 if let Some(label
) = label_text
{
346 bail
!("expected label '{}', found unrelated data", label
);
348 /* assume drive contains no or unrelated data */
349 task_log
!(worker
, "unable to read media label: {}", err
);
350 task_log
!(worker
, "erase anyways");
351 handle
.erase_media(fast
.unwrap_or(true))?
;
354 if let Some(label
) = label_text
{
355 bail
!("expected label '{}', found empty tape", label
);
357 task_log
!(worker
, "found empty media - erase anyways");
358 handle
.erase_media(fast
.unwrap_or(true))?
;
360 Ok((Some(media_id
), _key_config
)) => {
361 if let Some(label_text
) = label_text
{
362 if media_id
.label
.label_text
!= label_text
{
364 "expected label '{}', found '{}', aborting",
366 media_id
.label
.label_text
373 "found media '{}' with uuid '{}'",
374 media_id
.label
.label_text
, media_id
.label
.uuid
,
377 let status_path
= Path
::new(TAPE_STATUS_DIR
);
378 let mut inventory
= Inventory
::new(status_path
);
380 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
381 let _pool_lock
= lock_media_pool(status_path
, pool
)?
;
382 let _media_set_lock
= lock_media_set(status_path
, uuid
, None
)?
;
383 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
384 inventory
.remove_media(&media_id
.label
.uuid
)?
;
386 let _lock
= lock_unassigned_media_pool(status_path
)?
;
387 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
388 inventory
.remove_media(&media_id
.label
.uuid
)?
;
391 handle
.erase_media(fast
.unwrap_or(true))?
;
406 schema
: DRIVE_NAME_SCHEMA
,
414 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
420 rpcenv
: &mut dyn RpcEnvironment
,
421 ) -> Result
<Value
, Error
> {
422 let upid_str
= run_drive_worker(
427 move |_worker
, config
| {
428 let mut drive
= open_drive(&config
, &drive
)?
;
441 schema
: DRIVE_NAME_SCHEMA
,
449 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
452 /// Eject/Unload drive media
455 rpcenv
: &mut dyn RpcEnvironment
,
456 ) -> Result
<Value
, Error
> {
457 let upid_str
= run_drive_worker(
462 move |_worker
, config
| {
463 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
464 changer
.unload_media(None
)?
;
466 let mut drive
= open_drive(&config
, &drive
)?
;
467 drive
.eject_media()?
;
480 schema
: DRIVE_NAME_SCHEMA
,
483 schema
: MEDIA_LABEL_SCHEMA
,
486 schema
: MEDIA_POOL_NAME_SCHEMA
,
495 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
500 /// Write a new media label to the media in 'drive'. The media is
501 /// assigned to the specified 'pool', or else to the free media pool.
503 /// Note: The media need to be empty (you may want to erase it first).
506 pool
: Option
<String
>,
508 rpcenv
: &mut dyn RpcEnvironment
,
509 ) -> Result
<Value
, Error
> {
510 if let Some(ref pool
) = pool
{
511 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
513 if pool_config
.sections
.get(pool
).is_none() {
514 bail
!("no such pool ('{}')", pool
);
517 let upid_str
= run_drive_worker(
522 move |worker
, config
| {
523 let mut drive
= open_drive(&config
, &drive
)?
;
527 match drive
.read_next_file() {
528 Ok(Some(_file
)) => bail
!("media is not empty (erase first)"),
529 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
531 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
532 /* assume tape is empty */
534 bail
!("media read error - {}", err
);
539 let ctime
= proxmox
::tools
::time
::epoch_i64();
540 let label
= MediaLabel
{
541 label_text
: label_text
.to_string(),
542 uuid
: Uuid
::generate(),
546 write_media_label(worker
, &mut drive
, label
, pool
)
553 fn write_media_label(
554 worker
: Arc
<WorkerTask
>,
555 drive
: &mut Box
<dyn TapeDriver
>,
557 pool
: Option
<String
>,
558 ) -> Result
<(), Error
> {
560 drive
.label_tape(&label
)?
;
562 let status_path
= Path
::new(TAPE_STATUS_DIR
);
564 let media_id
= if let Some(ref pool
) = pool
{
565 // assign media to pool by writing special media set label
566 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
567 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
569 drive
.write_media_set_label(&set
, None
)?
;
571 let media_id
= MediaId { label, media_set_label: Some(set) }
;
573 // Create the media catalog
574 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
576 let mut inventory
= Inventory
::new(status_path
);
577 inventory
.store(media_id
.clone(), false)?
;
581 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
583 let media_id
= MediaId { label, media_set_label: None }
;
585 // Create the media catalog
586 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
588 let mut inventory
= Inventory
::new(status_path
);
589 inventory
.store(media_id
.clone(), false)?
;
596 match drive
.read_label() {
597 Ok((Some(info
), _
)) => {
598 if info
.label
.uuid
!= media_id
.label
.uuid
{
599 bail
!("verify label failed - got wrong label uuid");
601 if let Some(ref pool
) = pool
{
602 match info
.media_set_label
{
604 if set
.uuid
!= [0u8; 16].into() {
605 bail
!("verify media set label failed - got wrong set uuid");
607 if &set
.pool
!= pool
{
608 bail
!("verify media set label failed - got wrong pool");
612 bail
!("verify media set label failed (missing set label)");
617 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
618 Err(err
) => bail
!("verify label failed - {}", err
),
631 schema
: DRIVE_NAME_SCHEMA
,
634 description
: "Encryption key password.",
639 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
642 /// Try to restore a tape encryption key
643 pub async
fn restore_key(
646 ) -> Result
<(), Error
> {
647 run_drive_blocking_task(
649 "restore key".to_string(),
651 let mut drive
= open_drive(&config
, &drive
)?
;
653 let (_media_id
, key_config
) = drive
.read_label()?
;
655 if let Some(key_config
) = key_config
{
656 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
657 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
658 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
660 bail
!("media does not contain any encryption key configuration");
673 schema
: DRIVE_NAME_SCHEMA
,
676 description
: "Inventorize media",
685 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
688 /// Read media label (optionally inventorize media)
689 pub async
fn read_label(
691 inventorize
: Option
<bool
>,
692 ) -> Result
<MediaIdFlat
, Error
> {
693 run_drive_blocking_task(
695 "reading label".to_string(),
697 let mut drive
= open_drive(&config
, &drive
)?
;
699 let (media_id
, _key_config
) = drive
.read_label()?
;
701 let media_id
= match media_id
{
703 let mut flat
= MediaIdFlat
{
704 uuid
: media_id
.label
.uuid
.clone(),
705 label_text
: media_id
.label
.label_text
.clone(),
706 ctime
: media_id
.label
.ctime
,
707 media_set_ctime
: None
,
708 media_set_uuid
: None
,
709 encryption_key_fingerprint
: None
,
713 if let Some(ref set
) = media_id
.media_set_label
{
714 flat
.pool
= Some(set
.pool
.clone());
715 flat
.seq_nr
= Some(set
.seq_nr
);
716 flat
.media_set_uuid
= Some(set
.uuid
.clone());
717 flat
.media_set_ctime
= Some(set
.ctime
);
718 flat
.encryption_key_fingerprint
= set
719 .encryption_key_fingerprint
721 .map(|fp
| crate::tools
::format
::as_fingerprint(fp
.bytes()));
723 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
724 .map(|fp
| (fp
, set
.uuid
.clone()));
726 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
727 // try, but ignore errors. just log to stderr
728 eprintln
!("unable to load encryption key: {}", err
);
732 if let Some(true) = inventorize
{
733 let state_path
= Path
::new(TAPE_STATUS_DIR
);
734 let mut inventory
= Inventory
::new(state_path
);
736 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
737 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
738 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
739 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
740 inventory
.store(media_id
, false)?
;
742 let _lock
= lock_unassigned_media_pool(state_path
)?
;
743 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
744 inventory
.store(media_id
, false)?
;
751 bail
!("Media is empty (no label).");
765 schema
: DRIVE_NAME_SCHEMA
,
773 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
779 rpcenv
: &mut dyn RpcEnvironment
,
780 ) -> Result
<Value
, Error
> {
781 let upid_str
= run_drive_worker(
786 move |worker
, config
| {
787 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
789 worker
.log("Starting drive clean");
791 changer
.clean_drive()?
;
793 if let Ok(drive_config
) = config
.lookup
::<LinuxTapeDrive
>("linux", &drive
) {
794 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
795 let mut handle
= LinuxTapeHandle
::new(open_linux_tape_device(&drive_config
.path
)?
);
797 // test for critical tape alert flags
798 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
799 if !alert_flags
.is_empty() {
800 worker
.log(format
!("TapeAlertFlags: {:?}", alert_flags
));
801 if tape_alert_flags_critical(alert_flags
) {
802 bail
!("found critical tape alert flags: {:?}", alert_flags
);
807 // test wearout (max. 50 mounts)
808 if let Ok(volume_stats
) = handle
.volume_statistics() {
809 worker
.log(format
!("Volume mounts: {}", volume_stats
.volume_mounts
));
810 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
811 worker
.log(format
!("Cleaning tape wearout: {}%", wearout
));
815 worker
.log("Drive cleaned successfully");
828 schema
: DRIVE_NAME_SCHEMA
,
833 description
: "The list of media labels with associated media Uuid (if any).",
840 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
843 /// List known media labels (Changer Inventory)
845 /// Note: Only useful for drives with associated changer device.
847 /// This method queries the changer to get a list of media labels.
849 /// Note: This updates the media online status.
850 pub async
fn inventory(
852 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
853 run_drive_blocking_task(
855 "inventorize".to_string(),
857 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
859 let label_text_list
= changer
.online_media_label_texts()?
;
861 let state_path
= Path
::new(TAPE_STATUS_DIR
);
863 let mut inventory
= Inventory
::load(state_path
)?
;
865 update_changer_online_status(
872 let mut list
= Vec
::new();
874 for label_text
in label_text_list
.iter() {
875 if label_text
.starts_with("CLN") {
876 // skip cleaning unit
880 let label_text
= label_text
.to_string();
882 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
883 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
885 list
.push(LabelUuidMap { label_text, uuid: None }
);
899 schema
: DRIVE_NAME_SCHEMA
,
902 description
: "Load all tapes and try read labels (even if already inventoried)",
912 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
917 /// Note: Only useful for drives with associated changer device.
919 /// This method queries the changer to get a list of media labels. It
920 /// then loads any unknown media into the drive, reads the label, and
921 /// store the result to the media database.
923 /// Note: This updates the media online status.
924 pub fn update_inventory(
926 read_all_labels
: Option
<bool
>,
927 rpcenv
: &mut dyn RpcEnvironment
,
928 ) -> Result
<Value
, Error
> {
929 let upid_str
= run_drive_worker(
934 move |worker
, config
| {
935 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
937 let label_text_list
= changer
.online_media_label_texts()?
;
938 if label_text_list
.is_empty() {
939 worker
.log("changer device does not list any media labels".to_string());
942 let state_path
= Path
::new(TAPE_STATUS_DIR
);
944 let mut inventory
= Inventory
::load(state_path
)?
;
946 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
948 for label_text
in label_text_list
.iter() {
949 if label_text
.starts_with("CLN") {
950 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
954 let label_text
= label_text
.to_string();
956 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
957 worker
.log(format
!("media '{}' already inventoried", label_text
));
961 if let Err(err
) = changer
.load_media(&label_text
) {
962 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
966 let mut drive
= open_drive(&config
, &drive
)?
;
967 match drive
.read_label() {
969 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
972 worker
.log(format
!("media '{}' is empty", label_text
));
974 Ok((Some(media_id
), _key_config
)) => {
975 if label_text
!= media_id
.label
.label_text
{
976 worker
.warn(format
!("label text mismatch ({} != {})", label_text
, media_id
.label
.label_text
));
979 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
981 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
982 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
983 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
984 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
985 inventory
.store(media_id
, false)?
;
987 let _lock
= lock_unassigned_media_pool(state_path
)?
;
988 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
989 inventory
.store(media_id
, false)?
;
993 changer
.unload_media(None
)?
;
1007 schema
: DRIVE_NAME_SCHEMA
,
1010 schema
: MEDIA_POOL_NAME_SCHEMA
,
1016 schema
: UPID_SCHEMA
,
1019 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
1022 /// Label media with barcodes from changer device
1023 pub fn barcode_label_media(
1025 pool
: Option
<String
>,
1026 rpcenv
: &mut dyn RpcEnvironment
,
1027 ) -> Result
<Value
, Error
> {
1028 if let Some(ref pool
) = pool
{
1029 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
1031 if pool_config
.sections
.get(pool
).is_none() {
1032 bail
!("no such pool ('{}')", pool
);
1036 let upid_str
= run_drive_worker(
1039 "barcode-label-media",
1040 Some(drive
.clone()),
1041 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1047 fn barcode_label_media_worker(
1048 worker
: Arc
<WorkerTask
>,
1050 drive_config
: &SectionConfigData
,
1051 pool
: Option
<String
>,
1052 ) -> Result
<(), Error
> {
1053 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1055 let mut label_text_list
= changer
.online_media_label_texts()?
;
1057 // make sure we label them in the right order
1058 label_text_list
.sort();
1060 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1062 let mut inventory
= Inventory
::load(state_path
)?
;
1064 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
1066 if label_text_list
.is_empty() {
1067 bail
!("changer device does not list any media labels");
1070 for label_text
in label_text_list
{
1071 if label_text
.starts_with("CLN") { continue; }
1073 inventory
.reload()?
;
1074 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1075 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
1079 worker
.log(format
!("checking/loading media '{}'", label_text
));
1081 if let Err(err
) = changer
.load_media(&label_text
) {
1082 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
1086 let mut drive
= open_drive(drive_config
, &drive
)?
;
1089 match drive
.read_next_file() {
1090 Ok(Some(_file
)) => {
1091 worker
.log(format
!("media '{}' is not empty (erase first)", label_text
));
1094 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
1096 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
1097 /* assume tape is empty */
1099 worker
.warn(format
!("media '{}' read error (maybe not empty - erase first)", label_text
));
1105 let ctime
= proxmox
::tools
::time
::epoch_i64();
1106 let label
= MediaLabel
{
1107 label_text
: label_text
.to_string(),
1108 uuid
: Uuid
::generate(),
1112 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1122 schema
: DRIVE_NAME_SCHEMA
,
1127 description
: "A List of medium auxiliary memory attributes.",
1134 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1137 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1138 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1139 run_drive_blocking_task(
1141 "reading cartridge memory".to_string(),
1143 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1144 let mut handle
= drive_config
.open()?
;
1146 handle
.cartridge_memory()
1156 schema
: DRIVE_NAME_SCHEMA
,
1161 type: Lp17VolumeStatistics
,
1164 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1167 /// Read Volume Statistics (SCSI log page 17h)
1168 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1169 run_drive_blocking_task(
1171 "reading volume statistics".to_string(),
1173 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1174 let mut handle
= drive_config
.open()?
;
1176 handle
.volume_statistics()
1186 schema
: DRIVE_NAME_SCHEMA
,
1191 type: LinuxDriveAndMediaStatus
,
1194 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1197 /// Get drive/media status
1198 pub async
fn status(drive
: String
) -> Result
<LinuxDriveAndMediaStatus
, Error
> {
1199 run_drive_blocking_task(
1201 "reading drive status".to_string(),
1203 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1205 // Note: use open_linux_tape_device, because this also works if no medium loaded
1206 let file
= open_linux_tape_device(&drive_config
.path
)?
;
1208 let mut handle
= LinuxTapeHandle
::new(file
);
1210 handle
.get_drive_and_media_status()
1220 schema
: DRIVE_NAME_SCHEMA
,
1223 description
: "Force overriding existing index.",
1228 description
: "Verbose mode - log all found chunks.",
1235 schema
: UPID_SCHEMA
,
1238 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1241 /// Scan media and record content
1242 pub fn catalog_media(
1244 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);
1251 let upid_str
= run_drive_worker(
1255 Some(drive
.clone()),
1256 move |worker
, config
| {
1257 let mut drive
= open_drive(&config
, &drive
)?
;
1261 let media_id
= match drive
.read_label()?
{
1262 (Some(media_id
), key_config
) => {
1264 "found media label: {}",
1265 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1267 if key_config
.is_some() {
1269 "encryption key config: {}",
1270 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1275 (None
, _
) => bail
!("media is empty (no media label found)"),
1278 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1280 let mut inventory
= Inventory
::new(status_path
);
1282 let _media_set_lock
= match media_id
.media_set_label
{
1284 worker
.log("media is empty");
1285 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1286 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1287 inventory
.store(media_id
.clone(), false)?
;
1291 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1292 worker
.log("media is empty");
1293 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1294 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1295 inventory
.store(media_id
.clone(), false)?
;
1298 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1299 .map(|fp
| (fp
, set
.uuid
.clone()));
1301 drive
.set_encryption(encrypt_fingerprint
)?
;
1303 let _pool_lock
= lock_media_pool(status_path
, &set
.pool
)?
;
1304 let media_set_lock
= lock_media_set(status_path
, &set
.uuid
, None
)?
;
1306 MediaCatalog
::destroy_unrelated_catalog(status_path
, &media_id
)?
;
1308 inventory
.store(media_id
.clone(), false)?
;
1314 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1315 bail
!("media catalog exists (please use --force to overwrite)");
1318 // fixme: implement fast catalog restore
1319 restore_media(&worker
, &mut drive
, &media_id
, None
, verbose
)?
;
1332 schema
: CHANGER_NAME_SCHEMA
,
1338 description
: "The list of configured drives with model information.",
1341 type: DriveListEntry
,
1345 description
: "List configured tape drives filtered by Tape.Audit privileges",
1346 permission
: &Permission
::Anybody
,
1351 changer
: Option
<String
>,
1353 rpcenv
: &mut dyn RpcEnvironment
,
1354 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1355 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1356 let user_info
= CachedUserInfo
::new()?
;
1358 let (config
, _
) = config
::drive
::config()?
;
1360 let linux_drives
= linux_tape_device_list();
1362 let drive_list
: Vec
<LinuxTapeDrive
> = config
.convert_to_typed_array("linux")?
;
1364 let mut list
= Vec
::new();
1366 for drive
in drive_list
{
1367 if changer
.is_some() && drive
.changer
!= changer
{
1371 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1372 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1376 let info
= lookup_device_identification(&linux_drives
, &drive
.path
);
1377 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1378 let entry
= DriveListEntry { config: drive, info, state }
;
1386 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1388 "barcode-label-media",
1390 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1395 .post(&API_METHOD_CATALOG_MEDIA
)
1400 .put(&API_METHOD_CLEAN_DRIVE
)
1405 .post(&API_METHOD_EJECT_MEDIA
)
1410 .post(&API_METHOD_ERASE_MEDIA
)
1415 .put(&API_METHOD_EXPORT_MEDIA
)
1420 .get(&API_METHOD_INVENTORY
)
1421 .put(&API_METHOD_UPDATE_INVENTORY
)
1426 .post(&API_METHOD_LABEL_MEDIA
)
1431 .post(&API_METHOD_LOAD_MEDIA
)
1436 .put(&API_METHOD_LOAD_SLOT
)
1441 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1444 "volume-statistics",
1446 .get(&API_METHOD_VOLUME_STATISTICS
)
1451 .get(&API_METHOD_READ_LABEL
)
1456 .post(&API_METHOD_RESTORE_KEY
)
1461 .post(&API_METHOD_REWIND
)
1466 .get(&API_METHOD_STATUS
)
1471 .post(&API_METHOD_UNLOAD
)
1475 const ITEM_ROUTER
: Router
= Router
::new()
1476 .get(&list_subdirs_api_method
!(SUBDIRS
))
1479 pub const ROUTER
: Router
= Router
::new()
1480 .get(&API_METHOD_LIST_DRIVES
)
1481 .match_all("drive", &ITEM_ROUTER
);