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
,
64 lock_unassigned_media_pool
,
65 linux_tape_device_list
,
66 lookup_device_identification
,
75 open_linux_tape_device
,
77 required_media_changer
,
80 set_tape_device_state
,
81 get_tape_device_state
,
82 tape_alert_flags_critical
,
84 changer
::update_changer_online_status
,
88 fn run_drive_worker
<F
>(
89 rpcenv
: &dyn RpcEnvironment
,
92 job_id
: Option
<String
>,
94 ) -> Result
<String
, Error
>
99 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
101 // early check/lock before starting worker
102 let (config
, _digest
) = config
::drive
::config()?
;
103 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
105 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
106 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
108 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
109 let _lock_guard
= lock_guard
;
110 set_tape_device_state(&drive
, &worker
.upid().to_string())
111 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
113 let result
= f(worker
, config
);
114 set_tape_device_state(&drive
, "")
115 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
120 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
122 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
125 // early check/lock before starting worker
126 let (config
, _digest
) = config
::drive
::config()?
;
127 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
128 tokio
::task
::spawn_blocking(move || {
129 let _lock_guard
= lock_guard
;
130 set_tape_device_state(&drive
, &state
)
131 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
132 let result
= f(config
);
133 set_tape_device_state(&drive
, "")
134 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
144 schema
: DRIVE_NAME_SCHEMA
,
147 schema
: MEDIA_LABEL_SCHEMA
,
155 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
158 /// Load media with specified label
160 /// Issue a media load request to the associated changer device.
164 rpcenv
: &mut dyn RpcEnvironment
,
165 ) -> Result
<Value
, Error
> {
166 let job_id
= format
!("{}:{}", drive
, label_text
);
168 let upid_str
= run_drive_worker(
173 move |worker
, config
| {
174 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
175 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
176 changer
.load_media(&label_text
)?
;
188 schema
: DRIVE_NAME_SCHEMA
,
191 description
: "Source slot number.",
197 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
200 /// Load media from the specified slot
202 /// Issue a media load request to the associated changer device.
203 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
204 run_drive_blocking_task(
206 format
!("load from slot {}", source_slot
),
208 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
209 changer
.load_media_from_slot(source_slot
)?
;
220 schema
: DRIVE_NAME_SCHEMA
,
223 schema
: MEDIA_LABEL_SCHEMA
,
228 description
: "The import-export slot number the media was transferred to.",
233 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
236 /// Export media with specified label
237 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
238 run_drive_blocking_task(
240 format
!("export media {}", label_text
),
242 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
243 match changer
.export_media(&label_text
)?
{
244 Some(slot
) => Ok(slot
),
246 "media '{}' is not online (via changer '{}')",
260 schema
: DRIVE_NAME_SCHEMA
,
263 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
273 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
276 /// Unload media via changer
279 target_slot
: Option
<u64>,
280 rpcenv
: &mut dyn RpcEnvironment
,
281 ) -> Result
<Value
, Error
> {
282 let upid_str
= run_drive_worker(
287 move |worker
, config
| {
288 task_log
!(worker
, "unloading media from drive '{}'", drive
);
290 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
291 changer
.unload_media(target_slot
)?
;
303 schema
: DRIVE_NAME_SCHEMA
,
306 description
: "Use fast erase.",
312 schema
: MEDIA_LABEL_SCHEMA
,
321 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
324 /// Erase media. Check for label-text if given (cancels if wrong media).
328 label_text
: Option
<String
>,
329 rpcenv
: &mut dyn RpcEnvironment
,
330 ) -> Result
<Value
, Error
> {
331 let upid_str
= run_drive_worker(
336 move |worker
, config
| {
337 if let Some(ref label
) = label_text
{
338 task_log
!(worker
, "try to load media '{}'", label
);
339 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
340 changer
.load_media(label
)?
;
344 let mut handle
= open_drive(&config
, &drive
)?
;
346 match handle
.read_label() {
348 if let Some(label
) = label_text
{
349 bail
!("expected label '{}', found unrelated data", label
);
351 /* assume drive contains no or unrelated data */
352 task_log
!(worker
, "unable to read media label: {}", err
);
353 task_log
!(worker
, "erase anyways");
354 handle
.erase_media(fast
.unwrap_or(true))?
;
357 if let Some(label
) = label_text
{
358 bail
!("expected label '{}', found empty tape", label
);
360 task_log
!(worker
, "found empty media - erase anyways");
361 handle
.erase_media(fast
.unwrap_or(true))?
;
363 Ok((Some(media_id
), _key_config
)) => {
364 if let Some(label_text
) = label_text
{
365 if media_id
.label
.label_text
!= label_text
{
367 "expected label '{}', found '{}', aborting",
369 media_id
.label
.label_text
376 "found media '{}' with uuid '{}'",
377 media_id
.label
.label_text
, media_id
.label
.uuid
,
380 let status_path
= Path
::new(TAPE_STATUS_DIR
);
381 let mut inventory
= Inventory
::new(status_path
);
383 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
384 let _pool_lock
= lock_media_pool(status_path
, pool
)?
;
385 let _media_set_lock
= lock_media_set(status_path
, uuid
, None
)?
;
386 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
387 inventory
.remove_media(&media_id
.label
.uuid
)?
;
389 let _lock
= lock_unassigned_media_pool(status_path
)?
;
390 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
391 inventory
.remove_media(&media_id
.label
.uuid
)?
;
394 handle
.erase_media(fast
.unwrap_or(true))?
;
409 schema
: DRIVE_NAME_SCHEMA
,
417 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
423 rpcenv
: &mut dyn RpcEnvironment
,
424 ) -> Result
<Value
, Error
> {
425 let upid_str
= run_drive_worker(
430 move |_worker
, config
| {
431 let mut drive
= open_drive(&config
, &drive
)?
;
444 schema
: DRIVE_NAME_SCHEMA
,
452 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
455 /// Eject/Unload drive media
458 rpcenv
: &mut dyn RpcEnvironment
,
459 ) -> Result
<Value
, Error
> {
460 let upid_str
= run_drive_worker(
465 move |_worker
, config
| {
466 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
467 changer
.unload_media(None
)?
;
469 let mut drive
= open_drive(&config
, &drive
)?
;
470 drive
.eject_media()?
;
483 schema
: DRIVE_NAME_SCHEMA
,
486 schema
: MEDIA_LABEL_SCHEMA
,
489 schema
: MEDIA_POOL_NAME_SCHEMA
,
498 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
503 /// Write a new media label to the media in 'drive'. The media is
504 /// assigned to the specified 'pool', or else to the free media pool.
506 /// Note: The media need to be empty (you may want to erase it first).
509 pool
: Option
<String
>,
511 rpcenv
: &mut dyn RpcEnvironment
,
512 ) -> Result
<Value
, Error
> {
513 if let Some(ref pool
) = pool
{
514 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
516 if pool_config
.sections
.get(pool
).is_none() {
517 bail
!("no such pool ('{}')", pool
);
520 let upid_str
= run_drive_worker(
525 move |worker
, config
| {
526 let mut drive
= open_drive(&config
, &drive
)?
;
530 match drive
.read_next_file() {
531 Ok(Some(_file
)) => bail
!("media is not empty (erase first)"),
532 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
534 println
!("TEST {:?}", err
);
535 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
536 /* assume tape is empty */
538 bail
!("media read error - {}", err
);
543 let ctime
= proxmox
::tools
::time
::epoch_i64();
544 let label
= MediaLabel
{
545 label_text
: label_text
.to_string(),
546 uuid
: Uuid
::generate(),
550 write_media_label(worker
, &mut drive
, label
, pool
)
557 fn write_media_label(
558 worker
: Arc
<WorkerTask
>,
559 drive
: &mut Box
<dyn TapeDriver
>,
561 pool
: Option
<String
>,
562 ) -> Result
<(), Error
> {
564 drive
.label_tape(&label
)?
;
566 let status_path
= Path
::new(TAPE_STATUS_DIR
);
568 let media_id
= if let Some(ref pool
) = pool
{
569 // assign media to pool by writing special media set label
570 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
571 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
573 drive
.write_media_set_label(&set
, None
)?
;
575 let media_id
= MediaId { label, media_set_label: Some(set) }
;
577 // Create the media catalog
578 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
580 let mut inventory
= Inventory
::new(status_path
);
581 inventory
.store(media_id
.clone(), false)?
;
585 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
587 let media_id
= MediaId { label, media_set_label: None }
;
589 // Create the media catalog
590 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
592 let mut inventory
= Inventory
::new(status_path
);
593 inventory
.store(media_id
.clone(), false)?
;
600 match drive
.read_label() {
601 Ok((Some(info
), _
)) => {
602 if info
.label
.uuid
!= media_id
.label
.uuid
{
603 bail
!("verify label failed - got wrong label uuid");
605 if let Some(ref pool
) = pool
{
606 match info
.media_set_label
{
608 if set
.uuid
!= [0u8; 16].into() {
609 bail
!("verify media set label failed - got wrong set uuid");
611 if &set
.pool
!= pool
{
612 bail
!("verify media set label failed - got wrong pool");
616 bail
!("verify media set label failed (missing set label)");
621 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
622 Err(err
) => bail
!("verify label failed - {}", err
),
635 schema
: DRIVE_NAME_SCHEMA
,
638 description
: "Encryption key password.",
643 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
646 /// Try to restore a tape encryption key
647 pub async
fn restore_key(
650 ) -> Result
<(), Error
> {
651 run_drive_blocking_task(
653 "restore key".to_string(),
655 let mut drive
= open_drive(&config
, &drive
)?
;
657 let (_media_id
, key_config
) = drive
.read_label()?
;
659 if let Some(key_config
) = key_config
{
660 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
661 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
662 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
664 bail
!("media does not contain any encryption key configuration");
677 schema
: DRIVE_NAME_SCHEMA
,
680 description
: "Inventorize media",
689 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
692 /// Read media label (optionally inventorize media)
693 pub async
fn read_label(
695 inventorize
: Option
<bool
>,
696 ) -> Result
<MediaIdFlat
, Error
> {
697 run_drive_blocking_task(
699 "reading label".to_string(),
701 let mut drive
= open_drive(&config
, &drive
)?
;
703 let (media_id
, _key_config
) = drive
.read_label()?
;
705 let media_id
= match media_id
{
707 let mut flat
= MediaIdFlat
{
708 uuid
: media_id
.label
.uuid
.clone(),
709 label_text
: media_id
.label
.label_text
.clone(),
710 ctime
: media_id
.label
.ctime
,
711 media_set_ctime
: None
,
712 media_set_uuid
: None
,
713 encryption_key_fingerprint
: None
,
717 if let Some(ref set
) = media_id
.media_set_label
{
718 flat
.pool
= Some(set
.pool
.clone());
719 flat
.seq_nr
= Some(set
.seq_nr
);
720 flat
.media_set_uuid
= Some(set
.uuid
.clone());
721 flat
.media_set_ctime
= Some(set
.ctime
);
722 flat
.encryption_key_fingerprint
= set
723 .encryption_key_fingerprint
725 .map(|fp
| crate::tools
::format
::as_fingerprint(fp
.bytes()));
727 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
728 .map(|fp
| (fp
, set
.uuid
.clone()));
730 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
731 // try, but ignore errors. just log to stderr
732 eprintln
!("unable to load encryption key: {}", err
);
736 if let Some(true) = inventorize
{
737 let state_path
= Path
::new(TAPE_STATUS_DIR
);
738 let mut inventory
= Inventory
::new(state_path
);
740 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
741 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
742 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
743 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
744 inventory
.store(media_id
, false)?
;
746 let _lock
= lock_unassigned_media_pool(state_path
)?
;
747 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
748 inventory
.store(media_id
, false)?
;
755 bail
!("Media is empty (no label).");
769 schema
: DRIVE_NAME_SCHEMA
,
777 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
783 rpcenv
: &mut dyn RpcEnvironment
,
784 ) -> Result
<Value
, Error
> {
785 let upid_str
= run_drive_worker(
790 move |worker
, config
| {
791 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
793 worker
.log("Starting drive clean");
795 changer
.clean_drive()?
;
797 if let Ok(drive_config
) = config
.lookup
::<LinuxTapeDrive
>("linux", &drive
) {
798 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
799 let mut handle
= LinuxTapeHandle
::new(open_linux_tape_device(&drive_config
.path
)?
);
801 // test for critical tape alert flags
802 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
803 if !alert_flags
.is_empty() {
804 worker
.log(format
!("TapeAlertFlags: {:?}", alert_flags
));
805 if tape_alert_flags_critical(alert_flags
) {
806 bail
!("found critical tape alert flags: {:?}", alert_flags
);
811 // test wearout (max. 50 mounts)
812 if let Ok(volume_stats
) = handle
.volume_statistics() {
813 worker
.log(format
!("Volume mounts: {}", volume_stats
.volume_mounts
));
814 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
815 worker
.log(format
!("Cleaning tape wearout: {}%", wearout
));
819 worker
.log("Drive cleaned successfully");
832 schema
: DRIVE_NAME_SCHEMA
,
837 description
: "The list of media labels with associated media Uuid (if any).",
844 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
847 /// List known media labels (Changer Inventory)
849 /// Note: Only useful for drives with associated changer device.
851 /// This method queries the changer to get a list of media labels.
853 /// Note: This updates the media online status.
854 pub async
fn inventory(
856 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
857 run_drive_blocking_task(
859 "inventorize".to_string(),
861 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
863 let label_text_list
= changer
.online_media_label_texts()?
;
865 let state_path
= Path
::new(TAPE_STATUS_DIR
);
867 let mut inventory
= Inventory
::load(state_path
)?
;
869 update_changer_online_status(
876 let mut list
= Vec
::new();
878 for label_text
in label_text_list
.iter() {
879 if label_text
.starts_with("CLN") {
880 // skip cleaning unit
884 let label_text
= label_text
.to_string();
886 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
887 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
889 list
.push(LabelUuidMap { label_text, uuid: None }
);
903 schema
: DRIVE_NAME_SCHEMA
,
906 description
: "Load all tapes and try read labels (even if already inventoried)",
916 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
921 /// Note: Only useful for drives with associated changer device.
923 /// This method queries the changer to get a list of media labels. It
924 /// then loads any unknown media into the drive, reads the label, and
925 /// store the result to the media database.
927 /// Note: This updates the media online status.
928 pub fn update_inventory(
930 read_all_labels
: Option
<bool
>,
931 rpcenv
: &mut dyn RpcEnvironment
,
932 ) -> Result
<Value
, Error
> {
933 let upid_str
= run_drive_worker(
938 move |worker
, config
| {
939 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
941 let label_text_list
= changer
.online_media_label_texts()?
;
942 if label_text_list
.is_empty() {
943 worker
.log("changer device does not list any media labels".to_string());
946 let state_path
= Path
::new(TAPE_STATUS_DIR
);
948 let mut inventory
= Inventory
::load(state_path
)?
;
950 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
952 for label_text
in label_text_list
.iter() {
953 if label_text
.starts_with("CLN") {
954 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
958 let label_text
= label_text
.to_string();
960 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
961 worker
.log(format
!("media '{}' already inventoried", label_text
));
965 if let Err(err
) = changer
.load_media(&label_text
) {
966 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
970 let mut drive
= open_drive(&config
, &drive
)?
;
971 match drive
.read_label() {
973 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
976 worker
.log(format
!("media '{}' is empty", label_text
));
978 Ok((Some(media_id
), _key_config
)) => {
979 if label_text
!= media_id
.label
.label_text
{
980 worker
.warn(format
!("label text mismatch ({} != {})", label_text
, media_id
.label
.label_text
));
983 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
985 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
986 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
987 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
988 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
989 inventory
.store(media_id
, false)?
;
991 let _lock
= lock_unassigned_media_pool(state_path
)?
;
992 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
993 inventory
.store(media_id
, false)?
;
997 changer
.unload_media(None
)?
;
1011 schema
: DRIVE_NAME_SCHEMA
,
1014 schema
: MEDIA_POOL_NAME_SCHEMA
,
1020 schema
: UPID_SCHEMA
,
1023 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
1026 /// Label media with barcodes from changer device
1027 pub fn barcode_label_media(
1029 pool
: Option
<String
>,
1030 rpcenv
: &mut dyn RpcEnvironment
,
1031 ) -> Result
<Value
, Error
> {
1032 if let Some(ref pool
) = pool
{
1033 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
1035 if pool_config
.sections
.get(pool
).is_none() {
1036 bail
!("no such pool ('{}')", pool
);
1040 let upid_str
= run_drive_worker(
1043 "barcode-label-media",
1044 Some(drive
.clone()),
1045 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1051 fn barcode_label_media_worker(
1052 worker
: Arc
<WorkerTask
>,
1054 drive_config
: &SectionConfigData
,
1055 pool
: Option
<String
>,
1056 ) -> Result
<(), Error
> {
1057 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1059 let mut label_text_list
= changer
.online_media_label_texts()?
;
1061 // make sure we label them in the right order
1062 label_text_list
.sort();
1064 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1066 let mut inventory
= Inventory
::load(state_path
)?
;
1068 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
1070 if label_text_list
.is_empty() {
1071 bail
!("changer device does not list any media labels");
1074 for label_text
in label_text_list
{
1075 if label_text
.starts_with("CLN") { continue; }
1077 inventory
.reload()?
;
1078 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1079 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
1083 worker
.log(format
!("checking/loading media '{}'", label_text
));
1085 if let Err(err
) = changer
.load_media(&label_text
) {
1086 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
1090 let mut drive
= open_drive(drive_config
, &drive
)?
;
1093 match drive
.read_next_file() {
1094 Ok(Some(_file
)) => {
1095 worker
.log(format
!("media '{}' is not empty (erase first)", label_text
));
1098 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
1100 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
1101 /* assume tape is empty */
1103 worker
.warn(format
!("media '{}' read error (maybe not empty - erase first)", label_text
));
1109 let ctime
= proxmox
::tools
::time
::epoch_i64();
1110 let label
= MediaLabel
{
1111 label_text
: label_text
.to_string(),
1112 uuid
: Uuid
::generate(),
1116 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1126 schema
: DRIVE_NAME_SCHEMA
,
1131 description
: "A List of medium auxiliary memory attributes.",
1138 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1141 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1142 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1143 run_drive_blocking_task(
1145 "reading cartridge memory".to_string(),
1147 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1148 let mut handle
= drive_config
.open()?
;
1150 handle
.cartridge_memory()
1160 schema
: DRIVE_NAME_SCHEMA
,
1165 type: Lp17VolumeStatistics
,
1168 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1171 /// Read Volume Statistics (SCSI log page 17h)
1172 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1173 run_drive_blocking_task(
1175 "reading volume statistics".to_string(),
1177 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1178 let mut handle
= drive_config
.open()?
;
1180 handle
.volume_statistics()
1190 schema
: DRIVE_NAME_SCHEMA
,
1195 type: LinuxDriveAndMediaStatus
,
1198 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1201 /// Get drive/media status
1202 pub async
fn status(drive
: String
) -> Result
<LinuxDriveAndMediaStatus
, Error
> {
1203 run_drive_blocking_task(
1205 "reading drive status".to_string(),
1207 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1209 // Note: use open_linux_tape_device, because this also works if no medium loaded
1210 let file
= open_linux_tape_device(&drive_config
.path
)?
;
1212 let mut handle
= LinuxTapeHandle
::new(file
);
1214 handle
.get_drive_and_media_status()
1224 schema
: DRIVE_NAME_SCHEMA
,
1227 description
: "Force overriding existing index.",
1232 description
: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
1237 description
: "Verbose mode - log all found chunks.",
1244 schema
: UPID_SCHEMA
,
1247 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1250 /// Scan media and record content
1251 pub fn catalog_media(
1253 force
: Option
<bool
>,
1255 verbose
: Option
<bool
>,
1256 rpcenv
: &mut dyn RpcEnvironment
,
1257 ) -> Result
<Value
, Error
> {
1258 let verbose
= verbose
.unwrap_or(false);
1259 let force
= force
.unwrap_or(false);
1260 let scan
= scan
.unwrap_or(false);
1262 let upid_str
= run_drive_worker(
1266 Some(drive
.clone()),
1267 move |worker
, config
| {
1268 let mut drive
= open_drive(&config
, &drive
)?
;
1272 let media_id
= match drive
.read_label()?
{
1273 (Some(media_id
), key_config
) => {
1275 "found media label: {}",
1276 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1278 if key_config
.is_some() {
1280 "encryption key config: {}",
1281 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1286 (None
, _
) => bail
!("media is empty (no media label found)"),
1289 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1291 let mut inventory
= Inventory
::new(status_path
);
1293 let (_media_set_lock
, media_set_uuid
) = match media_id
.media_set_label
{
1295 worker
.log("media is empty");
1296 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1297 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1298 inventory
.store(media_id
.clone(), false)?
;
1302 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1303 worker
.log("media is empty");
1304 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1305 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1306 inventory
.store(media_id
.clone(), false)?
;
1309 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1310 .map(|fp
| (fp
, set
.uuid
.clone()));
1312 drive
.set_encryption(encrypt_fingerprint
)?
;
1314 let _pool_lock
= lock_media_pool(status_path
, &set
.pool
)?
;
1315 let media_set_lock
= lock_media_set(status_path
, &set
.uuid
, None
)?
;
1317 MediaCatalog
::destroy_unrelated_catalog(status_path
, &media_id
)?
;
1319 inventory
.store(media_id
.clone(), false)?
;
1321 (media_set_lock
, &set
.uuid
)
1325 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1326 bail
!("media catalog exists (please use --force to overwrite)");
1330 let media_set
= inventory
.compute_media_set_members(media_set_uuid
)?
;
1332 if fast_catalog_restore(&worker
, &mut drive
, &media_set
, &media_id
.label
.uuid
)?
{
1336 task_log
!(worker
, "no catalog found");
1339 task_log
!(worker
, "scanning entire media to reconstruct catalog");
1342 drive
.read_label()?
; // skip over labels - we already read them above
1344 restore_media(&worker
, &mut drive
, &media_id
, None
, verbose
)?
;
1357 schema
: CHANGER_NAME_SCHEMA
,
1363 description
: "The list of configured drives with model information.",
1366 type: DriveListEntry
,
1370 description
: "List configured tape drives filtered by Tape.Audit privileges",
1371 permission
: &Permission
::Anybody
,
1376 changer
: Option
<String
>,
1378 rpcenv
: &mut dyn RpcEnvironment
,
1379 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1380 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1381 let user_info
= CachedUserInfo
::new()?
;
1383 let (config
, _
) = config
::drive
::config()?
;
1385 let linux_drives
= linux_tape_device_list();
1387 let drive_list
: Vec
<LinuxTapeDrive
> = config
.convert_to_typed_array("linux")?
;
1389 let mut list
= Vec
::new();
1391 for drive
in drive_list
{
1392 if changer
.is_some() && drive
.changer
!= changer
{
1396 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1397 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1401 let info
= lookup_device_identification(&linux_drives
, &drive
.path
);
1402 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1403 let entry
= DriveListEntry { config: drive, info, state }
;
1411 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1413 "barcode-label-media",
1415 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1420 .post(&API_METHOD_CATALOG_MEDIA
)
1425 .put(&API_METHOD_CLEAN_DRIVE
)
1430 .post(&API_METHOD_EJECT_MEDIA
)
1435 .post(&API_METHOD_ERASE_MEDIA
)
1440 .put(&API_METHOD_EXPORT_MEDIA
)
1445 .get(&API_METHOD_INVENTORY
)
1446 .put(&API_METHOD_UPDATE_INVENTORY
)
1451 .post(&API_METHOD_LABEL_MEDIA
)
1456 .post(&API_METHOD_LOAD_MEDIA
)
1461 .put(&API_METHOD_LOAD_SLOT
)
1466 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1469 "volume-statistics",
1471 .get(&API_METHOD_VOLUME_STATISTICS
)
1476 .get(&API_METHOD_READ_LABEL
)
1481 .post(&API_METHOD_RESTORE_KEY
)
1486 .post(&API_METHOD_REWIND
)
1491 .get(&API_METHOD_STATUS
)
1496 .post(&API_METHOD_UNLOAD
)
1500 const ITEM_ROUTER
: Router
= Router
::new()
1501 .get(&list_subdirs_api_method
!(SUBDIRS
))
1504 pub const ROUTER
: Router
= Router
::new()
1505 .get(&API_METHOD_LIST_DRIVES
)
1506 .match_all("drive", &ITEM_ROUTER
);