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
,
60 linux_tape_device_list
,
61 lookup_device_identification
,
70 open_linux_tape_device
,
72 required_media_changer
,
75 set_tape_device_state
,
76 get_tape_device_state
,
77 tape_alert_flags_critical
,
79 changer
::update_changer_online_status
,
83 fn run_drive_worker
<F
>(
84 rpcenv
: &dyn RpcEnvironment
,
87 job_id
: Option
<String
>,
89 ) -> Result
<String
, Error
>
94 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
96 // early check/lock before starting worker
97 let (config
, _digest
) = config
::drive
::config()?
;
98 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
100 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
101 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
103 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
104 let _lock_guard
= lock_guard
;
105 set_tape_device_state(&drive
, &worker
.upid().to_string())
106 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
108 let result
= f(worker
, config
);
109 set_tape_device_state(&drive
, "")
110 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
115 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
117 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
120 // early check/lock before starting worker
121 let (config
, _digest
) = config
::drive
::config()?
;
122 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
123 tokio
::task
::spawn_blocking(move || {
124 let _lock_guard
= lock_guard
;
125 set_tape_device_state(&drive
, &state
)
126 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
127 let result
= f(config
);
128 set_tape_device_state(&drive
, "")
129 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
139 schema
: DRIVE_NAME_SCHEMA
,
142 schema
: MEDIA_LABEL_SCHEMA
,
150 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
153 /// Load media with specified label
155 /// Issue a media load request to the associated changer device.
159 rpcenv
: &mut dyn RpcEnvironment
,
160 ) -> Result
<Value
, Error
> {
161 let job_id
= format
!("{}:{}", drive
, label_text
);
163 let upid_str
= run_drive_worker(
168 move |worker
, config
| {
169 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
170 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
171 changer
.load_media(&label_text
)?
;
183 schema
: DRIVE_NAME_SCHEMA
,
186 description
: "Source slot number.",
192 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
195 /// Load media from the specified slot
197 /// Issue a media load request to the associated changer device.
198 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
199 run_drive_blocking_task(
201 format
!("load from slot {}", source_slot
),
203 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
204 changer
.load_media_from_slot(source_slot
)?
;
215 schema
: DRIVE_NAME_SCHEMA
,
218 schema
: MEDIA_LABEL_SCHEMA
,
223 description
: "The import-export slot number the media was transfered to.",
228 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
231 /// Export media with specified label
232 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
233 run_drive_blocking_task(
235 format
!("export media {}", label_text
),
237 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
238 match changer
.export_media(&label_text
)?
{
239 Some(slot
) => Ok(slot
),
241 "media '{}' is not online (via changer '{}')",
255 schema
: DRIVE_NAME_SCHEMA
,
258 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
268 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
271 /// Unload media via changer
274 target_slot
: Option
<u64>,
275 rpcenv
: &mut dyn RpcEnvironment
,
276 ) -> Result
<Value
, Error
> {
277 let upid_str
= run_drive_worker(
282 move |worker
, config
| {
283 task_log
!(worker
, "unloading media from drive '{}'", drive
);
285 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
286 changer
.unload_media(target_slot
)?
;
298 schema
: DRIVE_NAME_SCHEMA
,
301 description
: "Use fast erase.",
307 schema
: MEDIA_LABEL_SCHEMA
,
316 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
319 /// Erase media. Check for label-text if given (cancels if wrong media).
323 label_text
: Option
<String
>,
324 rpcenv
: &mut dyn RpcEnvironment
,
325 ) -> Result
<Value
, Error
> {
326 let upid_str
= run_drive_worker(
331 move |worker
, config
| {
332 if let Some(ref label
) = label_text
{
333 task_log
!(worker
, "try to load media '{}'", label
);
334 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
335 changer
.load_media(label
)?
;
339 let mut handle
= open_drive(&config
, &drive
)?
;
341 match handle
.read_label() {
343 if let Some(label
) = label_text
{
344 bail
!("expected label '{}', found unrelated data", label
);
346 /* assume drive contains no or unrelated data */
347 task_log
!(worker
, "unable to read media label: {}", err
);
348 task_log
!(worker
, "erase anyways");
349 handle
.erase_media(fast
.unwrap_or(true))?
;
352 if let Some(label
) = label_text
{
353 bail
!("expected label '{}', found empty tape", label
);
355 task_log
!(worker
, "found empty media - erase anyways");
356 handle
.erase_media(fast
.unwrap_or(true))?
;
358 Ok((Some(media_id
), _key_config
)) => {
359 if let Some(label_text
) = label_text
{
360 if media_id
.label
.label_text
!= label_text
{
362 "expected label '{}', found '{}', aborting",
364 media_id
.label
.label_text
371 "found media '{}' with uuid '{}'",
372 media_id
.label
.label_text
, media_id
.label
.uuid
,
375 let status_path
= Path
::new(TAPE_STATUS_DIR
);
376 let mut inventory
= Inventory
::load(status_path
)?
;
378 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
379 inventory
.remove_media(&media_id
.label
.uuid
)?
;
380 handle
.erase_media(fast
.unwrap_or(true))?
;
395 schema
: DRIVE_NAME_SCHEMA
,
403 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
409 rpcenv
: &mut dyn RpcEnvironment
,
410 ) -> Result
<Value
, Error
> {
411 let upid_str
= run_drive_worker(
416 move |_worker
, config
| {
417 let mut drive
= open_drive(&config
, &drive
)?
;
430 schema
: DRIVE_NAME_SCHEMA
,
438 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
441 /// Eject/Unload drive media
444 rpcenv
: &mut dyn RpcEnvironment
,
445 ) -> Result
<Value
, Error
> {
446 let upid_str
= run_drive_worker(
451 move |_worker
, config
| {
452 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
453 changer
.unload_media(None
)?
;
455 let mut drive
= open_drive(&config
, &drive
)?
;
456 drive
.eject_media()?
;
469 schema
: DRIVE_NAME_SCHEMA
,
472 schema
: MEDIA_LABEL_SCHEMA
,
475 schema
: MEDIA_POOL_NAME_SCHEMA
,
484 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
489 /// Write a new media label to the media in 'drive'. The media is
490 /// assigned to the specified 'pool', or else to the free media pool.
492 /// Note: The media need to be empty (you may want to erase it first).
495 pool
: Option
<String
>,
497 rpcenv
: &mut dyn RpcEnvironment
,
498 ) -> Result
<Value
, Error
> {
499 if let Some(ref pool
) = pool
{
500 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
502 if pool_config
.sections
.get(pool
).is_none() {
503 bail
!("no such pool ('{}')", pool
);
506 let upid_str
= run_drive_worker(
511 move |worker
, config
| {
512 let mut drive
= open_drive(&config
, &drive
)?
;
516 match drive
.read_next_file() {
517 Ok(Some(_file
)) => bail
!("media is not empty (erase first)"),
518 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
520 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
521 /* assume tape is empty */
523 bail
!("media read error - {}", err
);
528 let ctime
= proxmox
::tools
::time
::epoch_i64();
529 let label
= MediaLabel
{
530 label_text
: label_text
.to_string(),
531 uuid
: Uuid
::generate(),
535 write_media_label(worker
, &mut drive
, label
, pool
)
542 fn write_media_label(
543 worker
: Arc
<WorkerTask
>,
544 drive
: &mut Box
<dyn TapeDriver
>,
546 pool
: Option
<String
>,
547 ) -> Result
<(), Error
> {
549 drive
.label_tape(&label
)?
;
551 let mut media_set_label
= None
;
553 if let Some(ref pool
) = pool
{
554 // assign media to pool by writing special media set label
555 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
556 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
558 drive
.write_media_set_label(&set
, None
)?
;
559 media_set_label
= Some(set
);
561 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
564 let media_id
= MediaId { label, media_set_label }
;
566 let status_path
= Path
::new(TAPE_STATUS_DIR
);
568 // Create the media catalog
569 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
571 let mut inventory
= Inventory
::load(status_path
)?
;
572 inventory
.store(media_id
.clone(), false)?
;
576 match drive
.read_label() {
577 Ok((Some(info
), _
)) => {
578 if info
.label
.uuid
!= media_id
.label
.uuid
{
579 bail
!("verify label failed - got wrong label uuid");
581 if let Some(ref pool
) = pool
{
582 match info
.media_set_label
{
584 if set
.uuid
!= [0u8; 16].into() {
585 bail
!("verify media set label failed - got wrong set uuid");
587 if &set
.pool
!= pool
{
588 bail
!("verify media set label failed - got wrong pool");
592 bail
!("verify media set label failed (missing set label)");
597 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
598 Err(err
) => bail
!("verify label failed - {}", err
),
611 schema
: DRIVE_NAME_SCHEMA
,
614 description
: "Encryption key password.",
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
> {
627 run_drive_blocking_task(
629 "restore key".to_string(),
631 let mut drive
= open_drive(&config
, &drive
)?
;
633 let (_media_id
, key_config
) = drive
.read_label()?
;
635 if let Some(key_config
) = key_config
{
636 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
637 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
638 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
640 bail
!("media does not contain any encryption key configuration");
653 schema
: DRIVE_NAME_SCHEMA
,
656 description
: "Inventorize media",
665 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
668 /// Read media label (optionally inventorize media)
669 pub async
fn read_label(
671 inventorize
: Option
<bool
>,
672 ) -> Result
<MediaIdFlat
, Error
> {
673 run_drive_blocking_task(
675 "reading label".to_string(),
677 let mut drive
= open_drive(&config
, &drive
)?
;
679 let (media_id
, _key_config
) = drive
.read_label()?
;
681 let media_id
= match media_id
{
683 let mut flat
= MediaIdFlat
{
684 uuid
: media_id
.label
.uuid
.clone(),
685 label_text
: media_id
.label
.label_text
.clone(),
686 ctime
: media_id
.label
.ctime
,
687 media_set_ctime
: None
,
688 media_set_uuid
: None
,
689 encryption_key_fingerprint
: None
,
693 if let Some(ref set
) = media_id
.media_set_label
{
694 flat
.pool
= Some(set
.pool
.clone());
695 flat
.seq_nr
= Some(set
.seq_nr
);
696 flat
.media_set_uuid
= Some(set
.uuid
.clone());
697 flat
.media_set_ctime
= Some(set
.ctime
);
698 flat
.encryption_key_fingerprint
= set
699 .encryption_key_fingerprint
701 .map(|fp
| crate::tools
::format
::as_fingerprint(fp
.bytes()));
703 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
704 .map(|fp
| (fp
, set
.uuid
.clone()));
706 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
707 // try, but ignore errors. just log to stderr
708 eprintln
!("uable to load encryption key: {}", err
);
712 if let Some(true) = inventorize
{
713 let state_path
= Path
::new(TAPE_STATUS_DIR
);
714 let mut inventory
= Inventory
::load(state_path
)?
;
715 inventory
.store(media_id
, false)?
;
721 bail
!("Media is empty (no label).");
735 schema
: DRIVE_NAME_SCHEMA
,
743 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
749 rpcenv
: &mut dyn RpcEnvironment
,
750 ) -> Result
<Value
, Error
> {
751 let upid_str
= run_drive_worker(
756 move |worker
, config
| {
757 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
759 worker
.log("Starting drive clean");
761 changer
.clean_drive()?
;
763 if let Ok(drive_config
) = config
.lookup
::<LinuxTapeDrive
>("linux", &drive
) {
764 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
765 let mut handle
= LinuxTapeHandle
::new(open_linux_tape_device(&drive_config
.path
)?
);
767 // test for critical tape alert flags
768 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
769 if !alert_flags
.is_empty() {
770 worker
.log(format
!("TapeAlertFlags: {:?}", alert_flags
));
771 if tape_alert_flags_critical(alert_flags
) {
772 bail
!("found critical tape alert flags: {:?}", alert_flags
);
777 // test wearout (max. 50 mounts)
778 if let Ok(volume_stats
) = handle
.volume_statistics() {
779 worker
.log(format
!("Volume mounts: {}", volume_stats
.volume_mounts
));
780 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
781 worker
.log(format
!("Cleaning tape wearout: {}%", wearout
));
785 worker
.log("Drive cleaned sucessfully");
798 schema
: DRIVE_NAME_SCHEMA
,
803 description
: "The list of media labels with associated media Uuid (if any).",
810 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
813 /// List known media labels (Changer Inventory)
815 /// Note: Only useful for drives with associated changer device.
817 /// This method queries the changer to get a list of media labels.
819 /// Note: This updates the media online status.
820 pub async
fn inventory(
822 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
823 run_drive_blocking_task(
825 "inventorize".to_string(),
827 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
829 let label_text_list
= changer
.online_media_label_texts()?
;
831 let state_path
= Path
::new(TAPE_STATUS_DIR
);
833 let mut inventory
= Inventory
::load(state_path
)?
;
835 update_changer_online_status(
842 let mut list
= Vec
::new();
844 for label_text
in label_text_list
.iter() {
845 if label_text
.starts_with("CLN") {
846 // skip cleaning unit
850 let label_text
= label_text
.to_string();
852 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
853 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
855 list
.push(LabelUuidMap { label_text, uuid: None }
);
869 schema
: DRIVE_NAME_SCHEMA
,
872 description
: "Load all tapes and try read labels (even if already inventoried)",
882 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
887 /// Note: Only useful for drives with associated changer device.
889 /// This method queries the changer to get a list of media labels. It
890 /// then loads any unknown media into the drive, reads the label, and
891 /// store the result to the media database.
893 /// Note: This updates the media online status.
894 pub fn update_inventory(
896 read_all_labels
: Option
<bool
>,
897 rpcenv
: &mut dyn RpcEnvironment
,
898 ) -> Result
<Value
, Error
> {
899 let upid_str
= run_drive_worker(
904 move |worker
, config
| {
905 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
907 let label_text_list
= changer
.online_media_label_texts()?
;
908 if label_text_list
.is_empty() {
909 worker
.log("changer device does not list any media labels".to_string());
912 let state_path
= Path
::new(TAPE_STATUS_DIR
);
914 let mut inventory
= Inventory
::load(state_path
)?
;
916 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
918 for label_text
in label_text_list
.iter() {
919 if label_text
.starts_with("CLN") {
920 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
924 let label_text
= label_text
.to_string();
926 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
927 worker
.log(format
!("media '{}' already inventoried", label_text
));
931 if let Err(err
) = changer
.load_media(&label_text
) {
932 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
936 let mut drive
= open_drive(&config
, &drive
)?
;
937 match drive
.read_label() {
939 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
942 worker
.log(format
!("media '{}' is empty", label_text
));
944 Ok((Some(media_id
), _key_config
)) => {
945 if label_text
!= media_id
.label
.label_text
{
946 worker
.warn(format
!("label text missmatch ({} != {})", label_text
, media_id
.label
.label_text
));
949 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
950 inventory
.store(media_id
, false)?
;
953 changer
.unload_media(None
)?
;
967 schema
: DRIVE_NAME_SCHEMA
,
970 schema
: MEDIA_POOL_NAME_SCHEMA
,
979 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
982 /// Label media with barcodes from changer device
983 pub fn barcode_label_media(
985 pool
: Option
<String
>,
986 rpcenv
: &mut dyn RpcEnvironment
,
987 ) -> Result
<Value
, Error
> {
988 if let Some(ref pool
) = pool
{
989 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
991 if pool_config
.sections
.get(pool
).is_none() {
992 bail
!("no such pool ('{}')", pool
);
996 let upid_str
= run_drive_worker(
999 "barcode-label-media",
1000 Some(drive
.clone()),
1001 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1007 fn barcode_label_media_worker(
1008 worker
: Arc
<WorkerTask
>,
1010 drive_config
: &SectionConfigData
,
1011 pool
: Option
<String
>,
1012 ) -> Result
<(), Error
> {
1013 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1015 let mut label_text_list
= changer
.online_media_label_texts()?
;
1017 // make sure we label them in the right order
1018 label_text_list
.sort();
1020 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1022 let mut inventory
= Inventory
::load(state_path
)?
;
1024 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
1026 if label_text_list
.is_empty() {
1027 bail
!("changer device does not list any media labels");
1030 for label_text
in label_text_list
{
1031 if label_text
.starts_with("CLN") { continue; }
1033 inventory
.reload()?
;
1034 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1035 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
1039 worker
.log(format
!("checking/loading media '{}'", label_text
));
1041 if let Err(err
) = changer
.load_media(&label_text
) {
1042 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
1046 let mut drive
= open_drive(drive_config
, &drive
)?
;
1049 match drive
.read_next_file() {
1050 Ok(Some(_file
)) => {
1051 worker
.log(format
!("media '{}' is not empty (erase first)", label_text
));
1054 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
1056 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
1057 /* assume tape is empty */
1059 worker
.warn(format
!("media '{}' read error (maybe not empty - erase first)", label_text
));
1065 let ctime
= proxmox
::tools
::time
::epoch_i64();
1066 let label
= MediaLabel
{
1067 label_text
: label_text
.to_string(),
1068 uuid
: Uuid
::generate(),
1072 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1082 schema
: DRIVE_NAME_SCHEMA
,
1087 description
: "A List of medium auxiliary memory attributes.",
1094 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1097 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1098 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1099 run_drive_blocking_task(
1101 "reading cartridge memory".to_string(),
1103 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1104 let mut handle
= drive_config
.open()?
;
1106 handle
.cartridge_memory()
1116 schema
: DRIVE_NAME_SCHEMA
,
1121 type: Lp17VolumeStatistics
,
1124 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1127 /// Read Volume Statistics (SCSI log page 17h)
1128 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1129 run_drive_blocking_task(
1131 "reading volume statistics".to_string(),
1133 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1134 let mut handle
= drive_config
.open()?
;
1136 handle
.volume_statistics()
1146 schema
: DRIVE_NAME_SCHEMA
,
1151 type: LinuxDriveAndMediaStatus
,
1154 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1157 /// Get drive/media status
1158 pub async
fn status(drive
: String
) -> Result
<LinuxDriveAndMediaStatus
, Error
> {
1159 run_drive_blocking_task(
1161 "reading drive status".to_string(),
1163 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1165 // Note: use open_linux_tape_device, because this also works if no medium loaded
1166 let file
= open_linux_tape_device(&drive_config
.path
)?
;
1168 let mut handle
= LinuxTapeHandle
::new(file
);
1170 handle
.get_drive_and_media_status()
1180 schema
: DRIVE_NAME_SCHEMA
,
1183 description
: "Force overriding existing index.",
1188 description
: "Verbose mode - log all found chunks.",
1195 schema
: UPID_SCHEMA
,
1198 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1201 /// Scan media and record content
1202 pub fn catalog_media(
1204 force
: Option
<bool
>,
1205 verbose
: Option
<bool
>,
1206 rpcenv
: &mut dyn RpcEnvironment
,
1207 ) -> Result
<Value
, Error
> {
1208 let verbose
= verbose
.unwrap_or(false);
1209 let force
= force
.unwrap_or(false);
1211 let upid_str
= run_drive_worker(
1215 Some(drive
.clone()),
1216 move |worker
, config
| {
1217 let mut drive
= open_drive(&config
, &drive
)?
;
1221 let media_id
= match drive
.read_label()?
{
1222 (Some(media_id
), key_config
) => {
1224 "found media label: {}",
1225 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1227 if key_config
.is_some() {
1229 "encryption key config: {}",
1230 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1235 (None
, _
) => bail
!("media is empty (no media label found)"),
1238 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1240 let mut inventory
= Inventory
::load(status_path
)?
;
1241 inventory
.store(media_id
.clone(), false)?
;
1243 let pool
= match media_id
.media_set_label
{
1245 worker
.log("media is empty");
1246 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1250 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1251 worker
.log("media is empty");
1252 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1255 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1256 .map(|fp
| (fp
, set
.uuid
.clone()));
1258 drive
.set_encryption(encrypt_fingerprint
)?
;
1264 let _lock
= MediaPool
::lock(status_path
, &pool
)?
;
1266 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1267 bail
!("media catalog exists (please use --force to overwrite)");
1270 restore_media(&worker
, &mut drive
, &media_id
, None
, verbose
)?
;
1283 schema
: CHANGER_NAME_SCHEMA
,
1289 description
: "The list of configured drives with model information.",
1292 type: DriveListEntry
,
1296 description
: "List configured tape drives filtered by Tape.Audit privileges",
1297 permission
: &Permission
::Anybody
,
1302 changer
: Option
<String
>,
1304 rpcenv
: &mut dyn RpcEnvironment
,
1305 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1306 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1307 let user_info
= CachedUserInfo
::new()?
;
1309 let (config
, _
) = config
::drive
::config()?
;
1311 let linux_drives
= linux_tape_device_list();
1313 let drive_list
: Vec
<LinuxTapeDrive
> = config
.convert_to_typed_array("linux")?
;
1315 let mut list
= Vec
::new();
1317 for drive
in drive_list
{
1318 if changer
.is_some() && drive
.changer
!= changer
{
1322 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1323 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1327 let info
= lookup_device_identification(&linux_drives
, &drive
.path
);
1328 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1329 let entry
= DriveListEntry { config: drive, info, state }
;
1337 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1339 "barcode-label-media",
1341 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1346 .post(&API_METHOD_CATALOG_MEDIA
)
1351 .put(&API_METHOD_CLEAN_DRIVE
)
1356 .post(&API_METHOD_EJECT_MEDIA
)
1361 .post(&API_METHOD_ERASE_MEDIA
)
1366 .put(&API_METHOD_EXPORT_MEDIA
)
1371 .get(&API_METHOD_INVENTORY
)
1372 .put(&API_METHOD_UPDATE_INVENTORY
)
1377 .post(&API_METHOD_LABEL_MEDIA
)
1382 .post(&API_METHOD_LOAD_MEDIA
)
1387 .put(&API_METHOD_LOAD_SLOT
)
1392 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1395 "volume-statistics",
1397 .get(&API_METHOD_VOLUME_STATISTICS
)
1402 .get(&API_METHOD_READ_LABEL
)
1407 .post(&API_METHOD_RESTORE_KEY
)
1412 .post(&API_METHOD_REWIND
)
1417 .get(&API_METHOD_STATUS
)
1422 .post(&API_METHOD_UNLOAD
)
1426 const ITEM_ROUTER
: Router
= Router
::new()
1427 .get(&list_subdirs_api_method
!(SUBDIRS
))
1430 pub const ROUTER
: Router
= Router
::new()
1431 .get(&API_METHOD_LIST_DRIVES
)
1432 .match_all("drive", &ITEM_ROUTER
);