1 use std
::panic
::UnwindSafe
;
4 use std
::collections
::HashMap
;
6 use anyhow
::{bail, format_err, Error}
;
12 list_subdirs_api_method
,
16 section_config
::SectionConfigData
,
29 cached_user_info
::CachedUserInfo
,
42 MEDIA_POOL_NAME_SCHEMA
,
49 LtoDriveAndMediaStatus
,
66 lock_unassigned_media_pool
,
68 lookup_device_identification
,
78 required_media_changer
,
81 set_tape_device_state
,
82 get_tape_device_state
,
83 tape_alert_flags_critical
,
85 changer
::update_changer_online_status
,
89 fn run_drive_worker
<F
>(
90 rpcenv
: &dyn RpcEnvironment
,
93 job_id
: Option
<String
>,
95 ) -> Result
<String
, Error
>
100 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
102 // early check/lock before starting worker
103 let (config
, _digest
) = config
::drive
::config()?
;
104 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
106 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
107 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
109 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
110 let _lock_guard
= lock_guard
;
111 set_tape_device_state(&drive
, &worker
.upid().to_string())
112 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
114 let result
= f(worker
, config
);
115 set_tape_device_state(&drive
, "")
116 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
121 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
123 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
126 // early check/lock before starting worker
127 let (config
, _digest
) = config
::drive
::config()?
;
128 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
129 tokio
::task
::spawn_blocking(move || {
130 let _lock_guard
= lock_guard
;
131 set_tape_device_state(&drive
, &state
)
132 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
133 let result
= f(config
);
134 set_tape_device_state(&drive
, "")
135 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
145 schema
: DRIVE_NAME_SCHEMA
,
148 schema
: MEDIA_LABEL_SCHEMA
,
156 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
159 /// Load media with specified label
161 /// Issue a media load request to the associated changer device.
165 rpcenv
: &mut dyn RpcEnvironment
,
166 ) -> Result
<Value
, Error
> {
167 let job_id
= format
!("{}:{}", drive
, label_text
);
169 let upid_str
= run_drive_worker(
174 move |worker
, config
| {
175 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
176 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
177 changer
.load_media(&label_text
)?
;
189 schema
: DRIVE_NAME_SCHEMA
,
192 description
: "Source slot number.",
198 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
201 /// Load media from the specified slot
203 /// Issue a media load request to the associated changer device.
204 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
205 run_drive_blocking_task(
207 format
!("load from slot {}", source_slot
),
209 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
210 changer
.load_media_from_slot(source_slot
)?
;
221 schema
: DRIVE_NAME_SCHEMA
,
224 schema
: MEDIA_LABEL_SCHEMA
,
229 description
: "The import-export slot number the media was transferred to.",
234 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
237 /// Export media with specified label
238 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
239 run_drive_blocking_task(
241 format
!("export media {}", label_text
),
243 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
244 match changer
.export_media(&label_text
)?
{
245 Some(slot
) => Ok(slot
),
247 "media '{}' is not online (via changer '{}')",
261 schema
: DRIVE_NAME_SCHEMA
,
264 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
274 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
277 /// Unload media via changer
280 target_slot
: Option
<u64>,
281 rpcenv
: &mut dyn RpcEnvironment
,
282 ) -> Result
<Value
, Error
> {
283 let upid_str
= run_drive_worker(
288 move |worker
, config
| {
289 task_log
!(worker
, "unloading media from drive '{}'", drive
);
291 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
292 changer
.unload_media(target_slot
)?
;
304 schema
: DRIVE_NAME_SCHEMA
,
307 description
: "Use fast erase.",
313 schema
: MEDIA_LABEL_SCHEMA
,
322 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
325 /// Format media. Check for label-text if given (cancels if wrong media).
329 label_text
: Option
<String
>,
330 rpcenv
: &mut dyn RpcEnvironment
,
331 ) -> Result
<Value
, Error
> {
332 let upid_str
= run_drive_worker(
337 move |worker
, config
| {
338 if let Some(ref label
) = label_text
{
339 task_log
!(worker
, "try to load media '{}'", label
);
340 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
341 changer
.load_media(label
)?
;
345 let mut handle
= open_drive(&config
, &drive
)?
;
347 match handle
.read_label() {
349 if let Some(label
) = label_text
{
350 bail
!("expected label '{}', found unrelated data", label
);
352 /* assume drive contains no or unrelated data */
353 task_log
!(worker
, "unable to read media label: {}", err
);
354 task_log
!(worker
, "format anyways");
355 handle
.format_media(fast
.unwrap_or(true))?
;
358 if let Some(label
) = label_text
{
359 bail
!("expected label '{}', found empty tape", label
);
361 task_log
!(worker
, "found empty media - format anyways");
362 handle
.format_media(fast
.unwrap_or(true))?
;
364 Ok((Some(media_id
), _key_config
)) => {
365 if let Some(label_text
) = label_text
{
366 if media_id
.label
.label_text
!= label_text
{
368 "expected label '{}', found '{}', aborting",
370 media_id
.label
.label_text
377 "found media '{}' with uuid '{}'",
378 media_id
.label
.label_text
, media_id
.label
.uuid
,
381 let status_path
= Path
::new(TAPE_STATUS_DIR
);
382 let mut inventory
= Inventory
::new(status_path
);
384 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
385 let _pool_lock
= lock_media_pool(status_path
, pool
)?
;
386 let _media_set_lock
= lock_media_set(status_path
, uuid
, None
)?
;
387 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
388 inventory
.remove_media(&media_id
.label
.uuid
)?
;
390 let _lock
= lock_unassigned_media_pool(status_path
)?
;
391 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
392 inventory
.remove_media(&media_id
.label
.uuid
)?
;
395 handle
.format_media(fast
.unwrap_or(true))?
;
410 schema
: DRIVE_NAME_SCHEMA
,
418 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
424 rpcenv
: &mut dyn RpcEnvironment
,
425 ) -> Result
<Value
, Error
> {
426 let upid_str
= run_drive_worker(
431 move |_worker
, config
| {
432 let mut drive
= open_drive(&config
, &drive
)?
;
445 schema
: DRIVE_NAME_SCHEMA
,
453 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
456 /// Eject/Unload drive media
459 rpcenv
: &mut dyn RpcEnvironment
,
460 ) -> Result
<Value
, Error
> {
461 let upid_str
= run_drive_worker(
466 move |_worker
, config
| {
467 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
468 changer
.unload_media(None
)?
;
470 let mut drive
= open_drive(&config
, &drive
)?
;
471 drive
.eject_media()?
;
484 schema
: DRIVE_NAME_SCHEMA
,
487 schema
: MEDIA_LABEL_SCHEMA
,
490 schema
: MEDIA_POOL_NAME_SCHEMA
,
499 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
504 /// Write a new media label to the media in 'drive'. The media is
505 /// assigned to the specified 'pool', or else to the free media pool.
507 /// Note: The media need to be empty (you may want to format it first).
510 pool
: Option
<String
>,
512 rpcenv
: &mut dyn RpcEnvironment
,
513 ) -> Result
<Value
, Error
> {
514 if let Some(ref pool
) = pool
{
515 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
517 if pool_config
.sections
.get(pool
).is_none() {
518 bail
!("no such pool ('{}')", pool
);
521 let upid_str
= run_drive_worker(
526 move |worker
, config
| {
527 let mut drive
= open_drive(&config
, &drive
)?
;
531 match drive
.read_next_file() {
532 Ok(_reader
) => bail
!("media is not empty (format it first)"),
533 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
,
534 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
,
536 bail
!("media read error - {}", err
);
540 let ctime
= proxmox
::tools
::time
::epoch_i64();
541 let label
= MediaLabel
{
542 label_text
: label_text
.to_string(),
543 uuid
: Uuid
::generate(),
547 write_media_label(worker
, &mut drive
, label
, pool
)
554 fn write_media_label(
555 worker
: Arc
<WorkerTask
>,
556 drive
: &mut Box
<dyn TapeDriver
>,
558 pool
: Option
<String
>,
559 ) -> Result
<(), Error
> {
561 drive
.label_tape(&label
)?
;
563 let status_path
= Path
::new(TAPE_STATUS_DIR
);
565 let media_id
= if let Some(ref pool
) = pool
{
566 // assign media to pool by writing special media set label
567 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
568 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
570 drive
.write_media_set_label(&set
, None
)?
;
572 let media_id
= MediaId { label, media_set_label: Some(set) }
;
574 // Create the media catalog
575 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
577 let mut inventory
= Inventory
::new(status_path
);
578 inventory
.store(media_id
.clone(), false)?
;
582 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
584 let media_id
= MediaId { label, media_set_label: None }
;
586 // Create the media catalog
587 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
589 let mut inventory
= Inventory
::new(status_path
);
590 inventory
.store(media_id
.clone(), false)?
;
597 match drive
.read_label() {
598 Ok((Some(info
), _
)) => {
599 if info
.label
.uuid
!= media_id
.label
.uuid
{
600 bail
!("verify label failed - got wrong label uuid");
602 if let Some(ref pool
) = pool
{
603 match info
.media_set_label
{
605 if set
.uuid
!= [0u8; 16].into() {
606 bail
!("verify media set label failed - got wrong set uuid");
608 if &set
.pool
!= pool
{
609 bail
!("verify media set label failed - got wrong pool");
613 bail
!("verify media set label failed (missing set label)");
618 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
619 Err(err
) => bail
!("verify label failed - {}", err
),
632 schema
: DRIVE_NAME_SCHEMA
,
635 description
: "Encryption key password.",
640 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
643 /// Try to restore a tape encryption key
644 pub async
fn restore_key(
647 ) -> Result
<(), Error
> {
648 run_drive_blocking_task(
650 "restore key".to_string(),
652 let mut drive
= open_drive(&config
, &drive
)?
;
654 let (_media_id
, key_config
) = drive
.read_label()?
;
656 if let Some(key_config
) = key_config
{
657 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
658 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
659 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
661 bail
!("media does not contain any encryption key configuration");
674 schema
: DRIVE_NAME_SCHEMA
,
677 description
: "Inventorize media",
686 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
689 /// Read media label (optionally inventorize media)
690 pub async
fn read_label(
692 inventorize
: Option
<bool
>,
693 ) -> Result
<MediaIdFlat
, Error
> {
694 run_drive_blocking_task(
696 "reading label".to_string(),
698 let mut drive
= open_drive(&config
, &drive
)?
;
700 let (media_id
, _key_config
) = drive
.read_label()?
;
702 let media_id
= match media_id
{
704 let mut flat
= MediaIdFlat
{
705 uuid
: media_id
.label
.uuid
.clone(),
706 label_text
: media_id
.label
.label_text
.clone(),
707 ctime
: media_id
.label
.ctime
,
708 media_set_ctime
: None
,
709 media_set_uuid
: None
,
710 encryption_key_fingerprint
: None
,
714 if let Some(ref set
) = media_id
.media_set_label
{
715 flat
.pool
= Some(set
.pool
.clone());
716 flat
.seq_nr
= Some(set
.seq_nr
);
717 flat
.media_set_uuid
= Some(set
.uuid
.clone());
718 flat
.media_set_ctime
= Some(set
.ctime
);
719 flat
.encryption_key_fingerprint
= set
720 .encryption_key_fingerprint
722 .map(|fp
| pbs_tools
::format
::as_fingerprint(fp
.bytes()));
724 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
725 .map(|fp
| (fp
, set
.uuid
.clone()));
727 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
728 // try, but ignore errors. just log to stderr
729 eprintln
!("unable to load encryption key: {}", err
);
733 if let Some(true) = inventorize
{
734 let state_path
= Path
::new(TAPE_STATUS_DIR
);
735 let mut inventory
= Inventory
::new(state_path
);
737 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
738 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
739 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
740 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
741 inventory
.store(media_id
, false)?
;
743 let _lock
= lock_unassigned_media_pool(state_path
)?
;
744 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
745 inventory
.store(media_id
, false)?
;
752 bail
!("Media is empty (no label).");
766 schema
: DRIVE_NAME_SCHEMA
,
774 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
780 rpcenv
: &mut dyn RpcEnvironment
,
781 ) -> Result
<Value
, Error
> {
782 let upid_str
= run_drive_worker(
787 move |worker
, config
| {
788 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
790 worker
.log("Starting drive clean");
792 changer
.clean_drive()?
;
794 if let Ok(drive_config
) = config
.lookup
::<LtoTapeDrive
>("lto", &drive
) {
795 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
796 let mut handle
= LtoTapeHandle
::new(open_lto_tape_device(&drive_config
.path
)?
)?
;
798 // test for critical tape alert flags
799 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
800 if !alert_flags
.is_empty() {
801 worker
.log(format
!("TapeAlertFlags: {:?}", alert_flags
));
802 if tape_alert_flags_critical(alert_flags
) {
803 bail
!("found critical tape alert flags: {:?}", alert_flags
);
808 // test wearout (max. 50 mounts)
809 if let Ok(volume_stats
) = handle
.volume_statistics() {
810 worker
.log(format
!("Volume mounts: {}", volume_stats
.volume_mounts
));
811 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
812 worker
.log(format
!("Cleaning tape wearout: {}%", wearout
));
816 worker
.log("Drive cleaned successfully");
829 schema
: DRIVE_NAME_SCHEMA
,
834 description
: "The list of media labels with associated media Uuid (if any).",
841 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
844 /// List known media labels (Changer Inventory)
846 /// Note: Only useful for drives with associated changer device.
848 /// This method queries the changer to get a list of media labels.
850 /// Note: This updates the media online status.
851 pub async
fn inventory(
853 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
854 run_drive_blocking_task(
856 "inventorize".to_string(),
858 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
860 let label_text_list
= changer
.online_media_label_texts()?
;
862 let state_path
= Path
::new(TAPE_STATUS_DIR
);
864 let mut inventory
= Inventory
::load(state_path
)?
;
866 update_changer_online_status(
873 let mut list
= Vec
::new();
875 for label_text
in label_text_list
.iter() {
876 if label_text
.starts_with("CLN") {
877 // skip cleaning unit
881 let label_text
= label_text
.to_string();
883 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
884 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
886 list
.push(LabelUuidMap { label_text, uuid: None }
);
900 schema
: DRIVE_NAME_SCHEMA
,
903 description
: "Load all tapes and try read labels (even if already inventoried)",
913 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
918 /// Note: Only useful for drives with associated changer device.
920 /// This method queries the changer to get a list of media labels. It
921 /// then loads any unknown media into the drive, reads the label, and
922 /// store the result to the media database.
924 /// Note: This updates the media online status.
925 pub fn update_inventory(
927 read_all_labels
: Option
<bool
>,
928 rpcenv
: &mut dyn RpcEnvironment
,
929 ) -> Result
<Value
, Error
> {
930 let upid_str
= run_drive_worker(
935 move |worker
, config
| {
936 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
938 let label_text_list
= changer
.online_media_label_texts()?
;
939 if label_text_list
.is_empty() {
940 worker
.log("changer device does not list any media labels".to_string());
943 let state_path
= Path
::new(TAPE_STATUS_DIR
);
945 let mut inventory
= Inventory
::load(state_path
)?
;
947 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
949 for label_text
in label_text_list
.iter() {
950 if label_text
.starts_with("CLN") {
951 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
955 let label_text
= label_text
.to_string();
957 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
958 worker
.log(format
!("media '{}' already inventoried", label_text
));
962 if let Err(err
) = changer
.load_media(&label_text
) {
963 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
967 let mut drive
= open_drive(&config
, &drive
)?
;
968 match drive
.read_label() {
970 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
973 worker
.log(format
!("media '{}' is empty", label_text
));
975 Ok((Some(media_id
), _key_config
)) => {
976 if label_text
!= media_id
.label
.label_text
{
977 worker
.warn(format
!("label text mismatch ({} != {})", label_text
, media_id
.label
.label_text
));
980 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
982 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
983 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
984 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
985 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
986 inventory
.store(media_id
, false)?
;
988 let _lock
= lock_unassigned_media_pool(state_path
)?
;
989 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
990 inventory
.store(media_id
, false)?
;
994 changer
.unload_media(None
)?
;
1008 schema
: DRIVE_NAME_SCHEMA
,
1011 schema
: MEDIA_POOL_NAME_SCHEMA
,
1017 schema
: UPID_SCHEMA
,
1020 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
1023 /// Label media with barcodes from changer device
1024 pub fn barcode_label_media(
1026 pool
: Option
<String
>,
1027 rpcenv
: &mut dyn RpcEnvironment
,
1028 ) -> Result
<Value
, Error
> {
1029 if let Some(ref pool
) = pool
{
1030 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
1032 if pool_config
.sections
.get(pool
).is_none() {
1033 bail
!("no such pool ('{}')", pool
);
1037 let upid_str
= run_drive_worker(
1040 "barcode-label-media",
1041 Some(drive
.clone()),
1042 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1048 fn barcode_label_media_worker(
1049 worker
: Arc
<WorkerTask
>,
1051 drive_config
: &SectionConfigData
,
1052 pool
: Option
<String
>,
1053 ) -> Result
<(), Error
> {
1054 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1056 let mut label_text_list
= changer
.online_media_label_texts()?
;
1058 // make sure we label them in the right order
1059 label_text_list
.sort();
1061 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1063 let mut inventory
= Inventory
::load(state_path
)?
;
1065 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
1067 if label_text_list
.is_empty() {
1068 bail
!("changer device does not list any media labels");
1071 for label_text
in label_text_list
{
1072 if label_text
.starts_with("CLN") { continue; }
1074 inventory
.reload()?
;
1075 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1076 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
1080 worker
.log(format
!("checking/loading media '{}'", label_text
));
1082 if let Err(err
) = changer
.load_media(&label_text
) {
1083 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
1087 let mut drive
= open_drive(drive_config
, &drive
)?
;
1090 match drive
.read_next_file() {
1092 worker
.log(format
!("media '{}' is not empty (format it first)", label_text
));
1095 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
,
1096 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
,
1098 worker
.warn(format
!("media '{}' read error (maybe not empty - format it first)", label_text
));
1103 let ctime
= proxmox
::tools
::time
::epoch_i64();
1104 let label
= MediaLabel
{
1105 label_text
: label_text
.to_string(),
1106 uuid
: Uuid
::generate(),
1110 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1120 schema
: DRIVE_NAME_SCHEMA
,
1125 description
: "A List of medium auxiliary memory attributes.",
1132 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1135 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1136 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1137 run_drive_blocking_task(
1139 "reading cartridge memory".to_string(),
1141 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1142 let mut handle
= drive_config
.open()?
;
1144 handle
.cartridge_memory()
1154 schema
: DRIVE_NAME_SCHEMA
,
1159 type: Lp17VolumeStatistics
,
1162 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1165 /// Read Volume Statistics (SCSI log page 17h)
1166 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1167 run_drive_blocking_task(
1169 "reading volume statistics".to_string(),
1171 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1172 let mut handle
= drive_config
.open()?
;
1174 handle
.volume_statistics()
1184 schema
: DRIVE_NAME_SCHEMA
,
1189 type: LtoDriveAndMediaStatus
,
1192 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1195 /// Get drive/media status
1196 pub async
fn status(drive
: String
) -> Result
<LtoDriveAndMediaStatus
, Error
> {
1197 run_drive_blocking_task(
1199 "reading drive status".to_string(),
1201 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1203 // Note: use open_lto_tape_device, because this also works if no medium loaded
1204 let file
= open_lto_tape_device(&drive_config
.path
)?
;
1206 let mut handle
= LtoTapeHandle
::new(file
)?
;
1208 handle
.get_drive_and_media_status()
1218 schema
: DRIVE_NAME_SCHEMA
,
1221 description
: "Force overriding existing index.",
1226 description
: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
1231 description
: "Verbose mode - log all found chunks.",
1238 schema
: UPID_SCHEMA
,
1241 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1244 /// Scan media and record content
1245 pub fn catalog_media(
1247 force
: Option
<bool
>,
1249 verbose
: Option
<bool
>,
1250 rpcenv
: &mut dyn RpcEnvironment
,
1251 ) -> Result
<Value
, Error
> {
1252 let verbose
= verbose
.unwrap_or(false);
1253 let force
= force
.unwrap_or(false);
1254 let scan
= scan
.unwrap_or(false);
1256 let upid_str
= run_drive_worker(
1260 Some(drive
.clone()),
1261 move |worker
, config
| {
1262 let mut drive
= open_drive(&config
, &drive
)?
;
1266 let media_id
= match drive
.read_label()?
{
1267 (Some(media_id
), key_config
) => {
1269 "found media label: {}",
1270 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1272 if key_config
.is_some() {
1274 "encryption key config: {}",
1275 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1280 (None
, _
) => bail
!("media is empty (no media label found)"),
1283 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1285 let mut inventory
= Inventory
::new(status_path
);
1287 let (_media_set_lock
, media_set_uuid
) = match media_id
.media_set_label
{
1289 worker
.log("media is empty");
1290 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1291 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1292 inventory
.store(media_id
.clone(), false)?
;
1296 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1297 worker
.log("media is empty");
1298 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1299 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1300 inventory
.store(media_id
.clone(), false)?
;
1303 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1304 .map(|fp
| (fp
, set
.uuid
.clone()));
1306 drive
.set_encryption(encrypt_fingerprint
)?
;
1308 let _pool_lock
= lock_media_pool(status_path
, &set
.pool
)?
;
1309 let media_set_lock
= lock_media_set(status_path
, &set
.uuid
, None
)?
;
1311 MediaCatalog
::destroy_unrelated_catalog(status_path
, &media_id
)?
;
1313 inventory
.store(media_id
.clone(), false)?
;
1315 (media_set_lock
, &set
.uuid
)
1319 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1320 bail
!("media catalog exists (please use --force to overwrite)");
1324 let media_set
= inventory
.compute_media_set_members(media_set_uuid
)?
;
1326 if fast_catalog_restore(&worker
, &mut drive
, &media_set
, &media_id
.label
.uuid
)?
{
1330 task_log
!(worker
, "no catalog found");
1333 task_log
!(worker
, "scanning entire media to reconstruct catalog");
1336 drive
.read_label()?
; // skip over labels - we already read them above
1338 let mut checked_chunks
= HashMap
::new();
1339 restore_media(worker
, &mut drive
, &media_id
, None
, &mut checked_chunks
, verbose
)?
;
1352 schema
: CHANGER_NAME_SCHEMA
,
1358 description
: "The list of configured drives with model information.",
1361 type: DriveListEntry
,
1365 description
: "List configured tape drives filtered by Tape.Audit privileges",
1366 permission
: &Permission
::Anybody
,
1371 changer
: Option
<String
>,
1373 rpcenv
: &mut dyn RpcEnvironment
,
1374 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1375 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1376 let user_info
= CachedUserInfo
::new()?
;
1378 let (config
, _
) = config
::drive
::config()?
;
1380 let lto_drives
= lto_tape_device_list();
1382 let drive_list
: Vec
<LtoTapeDrive
> = config
.convert_to_typed_array("lto")?
;
1384 let mut list
= Vec
::new();
1386 for drive
in drive_list
{
1387 if changer
.is_some() && drive
.changer
!= changer
{
1391 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1392 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1396 let info
= lookup_device_identification(<o_drives
, &drive
.path
);
1397 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1398 let entry
= DriveListEntry { config: drive, info, state }
;
1406 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1408 "barcode-label-media",
1410 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1415 .post(&API_METHOD_CATALOG_MEDIA
)
1420 .put(&API_METHOD_CLEAN_DRIVE
)
1425 .post(&API_METHOD_EJECT_MEDIA
)
1430 .post(&API_METHOD_FORMAT_MEDIA
)
1435 .put(&API_METHOD_EXPORT_MEDIA
)
1440 .get(&API_METHOD_INVENTORY
)
1441 .put(&API_METHOD_UPDATE_INVENTORY
)
1446 .post(&API_METHOD_LABEL_MEDIA
)
1451 .post(&API_METHOD_LOAD_MEDIA
)
1456 .post(&API_METHOD_LOAD_SLOT
)
1461 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1464 "volume-statistics",
1466 .get(&API_METHOD_VOLUME_STATISTICS
)
1471 .get(&API_METHOD_READ_LABEL
)
1476 .post(&API_METHOD_RESTORE_KEY
)
1481 .post(&API_METHOD_REWIND
)
1486 .get(&API_METHOD_STATUS
)
1491 .post(&API_METHOD_UNLOAD
)
1495 const ITEM_ROUTER
: Router
= Router
::new()
1496 .get(&list_subdirs_api_method
!(SUBDIRS
))
1499 pub const ROUTER
: Router
= Router
::new()
1500 .get(&API_METHOD_LIST_DRIVES
)
1501 .match_all("drive", &ITEM_ROUTER
);