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
,
25 use pbs_datastore
::task_log
;
30 cached_user_info
::CachedUserInfo
,
43 MEDIA_POOL_NAME_SCHEMA
,
50 LtoDriveAndMediaStatus
,
67 lock_unassigned_media_pool
,
69 lookup_device_identification
,
80 required_media_changer
,
83 set_tape_device_state
,
84 get_tape_device_state
,
85 tape_alert_flags_critical
,
87 changer
::update_changer_online_status
,
91 fn run_drive_worker
<F
>(
92 rpcenv
: &dyn RpcEnvironment
,
95 job_id
: Option
<String
>,
97 ) -> Result
<String
, Error
>
102 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
104 // early check/lock before starting worker
105 let (config
, _digest
) = pbs_config
::drive
::config()?
;
106 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
108 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
109 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
111 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
112 let _lock_guard
= lock_guard
;
113 set_tape_device_state(&drive
, &worker
.upid().to_string())
114 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
116 let result
= f(worker
, config
);
117 set_tape_device_state(&drive
, "")
118 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
123 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
125 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
128 // early check/lock before starting worker
129 let (config
, _digest
) = pbs_config
::drive
::config()?
;
130 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
131 tokio
::task
::spawn_blocking(move || {
132 let _lock_guard
= lock_guard
;
133 set_tape_device_state(&drive
, &state
)
134 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
135 let result
= f(config
);
136 set_tape_device_state(&drive
, "")
137 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
147 schema
: DRIVE_NAME_SCHEMA
,
150 schema
: MEDIA_LABEL_SCHEMA
,
158 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
161 /// Load media with specified label
163 /// Issue a media load request to the associated changer device.
167 rpcenv
: &mut dyn RpcEnvironment
,
168 ) -> Result
<Value
, Error
> {
169 let job_id
= format
!("{}:{}", drive
, label_text
);
171 let upid_str
= run_drive_worker(
176 move |worker
, config
| {
177 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
178 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
179 changer
.load_media(&label_text
)?
;
191 schema
: DRIVE_NAME_SCHEMA
,
194 description
: "Source slot number.",
200 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
203 /// Load media from the specified slot
205 /// Issue a media load request to the associated changer device.
206 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
207 run_drive_blocking_task(
209 format
!("load from slot {}", source_slot
),
211 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
212 changer
.load_media_from_slot(source_slot
)?
;
223 schema
: DRIVE_NAME_SCHEMA
,
226 schema
: MEDIA_LABEL_SCHEMA
,
231 description
: "The import-export slot number the media was transferred to.",
236 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
239 /// Export media with specified label
240 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
241 run_drive_blocking_task(
243 format
!("export media {}", label_text
),
245 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
246 match changer
.export_media(&label_text
)?
{
247 Some(slot
) => Ok(slot
),
249 "media '{}' is not online (via changer '{}')",
263 schema
: DRIVE_NAME_SCHEMA
,
266 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
276 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
279 /// Unload media via changer
282 target_slot
: Option
<u64>,
283 rpcenv
: &mut dyn RpcEnvironment
,
284 ) -> Result
<Value
, Error
> {
285 let upid_str
= run_drive_worker(
290 move |worker
, config
| {
291 task_log
!(worker
, "unloading media from drive '{}'", drive
);
293 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
294 changer
.unload_media(target_slot
)?
;
306 schema
: DRIVE_NAME_SCHEMA
,
309 description
: "Use fast erase.",
315 schema
: MEDIA_LABEL_SCHEMA
,
324 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
327 /// Format media. Check for label-text if given (cancels if wrong media).
331 label_text
: Option
<String
>,
332 rpcenv
: &mut dyn RpcEnvironment
,
333 ) -> Result
<Value
, Error
> {
334 let upid_str
= run_drive_worker(
339 move |worker
, config
| {
340 if let Some(ref label
) = label_text
{
341 task_log
!(worker
, "try to load media '{}'", label
);
342 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
343 changer
.load_media(label
)?
;
347 let mut handle
= open_drive(&config
, &drive
)?
;
349 match handle
.read_label() {
351 if let Some(label
) = label_text
{
352 bail
!("expected label '{}', found unrelated data", label
);
354 /* assume drive contains no or unrelated data */
355 task_log
!(worker
, "unable to read media label: {}", err
);
356 task_log
!(worker
, "format anyways");
357 handle
.format_media(fast
.unwrap_or(true))?
;
360 if let Some(label
) = label_text
{
361 bail
!("expected label '{}', found empty tape", label
);
363 task_log
!(worker
, "found empty media - format anyways");
364 handle
.format_media(fast
.unwrap_or(true))?
;
366 Ok((Some(media_id
), _key_config
)) => {
367 if let Some(label_text
) = label_text
{
368 if media_id
.label
.label_text
!= label_text
{
370 "expected label '{}', found '{}', aborting",
372 media_id
.label
.label_text
379 "found media '{}' with uuid '{}'",
380 media_id
.label
.label_text
, media_id
.label
.uuid
,
383 let status_path
= Path
::new(TAPE_STATUS_DIR
);
384 let mut inventory
= Inventory
::new(status_path
);
386 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
387 let _pool_lock
= lock_media_pool(status_path
, pool
)?
;
388 let _media_set_lock
= lock_media_set(status_path
, uuid
, None
)?
;
389 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
390 inventory
.remove_media(&media_id
.label
.uuid
)?
;
392 let _lock
= lock_unassigned_media_pool(status_path
)?
;
393 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
394 inventory
.remove_media(&media_id
.label
.uuid
)?
;
397 handle
.format_media(fast
.unwrap_or(true))?
;
412 schema
: DRIVE_NAME_SCHEMA
,
420 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
426 rpcenv
: &mut dyn RpcEnvironment
,
427 ) -> Result
<Value
, Error
> {
428 let upid_str
= run_drive_worker(
433 move |_worker
, config
| {
434 let mut drive
= open_drive(&config
, &drive
)?
;
447 schema
: DRIVE_NAME_SCHEMA
,
455 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
458 /// Eject/Unload drive media
461 rpcenv
: &mut dyn RpcEnvironment
,
462 ) -> Result
<Value
, Error
> {
463 let upid_str
= run_drive_worker(
468 move |_worker
, config
| {
469 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
470 changer
.unload_media(None
)?
;
472 let mut drive
= open_drive(&config
, &drive
)?
;
473 drive
.eject_media()?
;
486 schema
: DRIVE_NAME_SCHEMA
,
489 schema
: MEDIA_LABEL_SCHEMA
,
492 schema
: MEDIA_POOL_NAME_SCHEMA
,
501 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
506 /// Write a new media label to the media in 'drive'. The media is
507 /// assigned to the specified 'pool', or else to the free media pool.
509 /// Note: The media need to be empty (you may want to format it first).
512 pool
: Option
<String
>,
514 rpcenv
: &mut dyn RpcEnvironment
,
515 ) -> Result
<Value
, Error
> {
516 if let Some(ref pool
) = pool
{
517 let (pool_config
, _digest
) = pbs_config
::media_pool
::config()?
;
519 if pool_config
.sections
.get(pool
).is_none() {
520 bail
!("no such pool ('{}')", pool
);
523 let upid_str
= run_drive_worker(
528 move |worker
, config
| {
529 let mut drive
= open_drive(&config
, &drive
)?
;
533 match drive
.read_next_file() {
534 Ok(_reader
) => bail
!("media is not empty (format it first)"),
535 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
,
536 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
,
538 bail
!("media read error - {}", err
);
542 let ctime
= proxmox
::tools
::time
::epoch_i64();
543 let label
= MediaLabel
{
544 label_text
: label_text
.to_string(),
545 uuid
: Uuid
::generate(),
549 write_media_label(worker
, &mut drive
, label
, pool
)
556 fn write_media_label(
557 worker
: Arc
<WorkerTask
>,
558 drive
: &mut Box
<dyn TapeDriver
>,
560 pool
: Option
<String
>,
561 ) -> Result
<(), Error
> {
563 drive
.label_tape(&label
)?
;
565 let status_path
= Path
::new(TAPE_STATUS_DIR
);
567 let media_id
= if let Some(ref pool
) = pool
{
568 // assign media to pool by writing special media set label
569 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
570 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
572 drive
.write_media_set_label(&set
, None
)?
;
574 let media_id
= MediaId { label, media_set_label: Some(set) }
;
576 // Create the media catalog
577 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
579 let mut inventory
= Inventory
::new(status_path
);
580 inventory
.store(media_id
.clone(), false)?
;
584 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
586 let media_id
= MediaId { label, media_set_label: None }
;
588 // Create the media catalog
589 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
591 let mut inventory
= Inventory
::new(status_path
);
592 inventory
.store(media_id
.clone(), false)?
;
599 match drive
.read_label() {
600 Ok((Some(info
), _
)) => {
601 if info
.label
.uuid
!= media_id
.label
.uuid
{
602 bail
!("verify label failed - got wrong label uuid");
604 if let Some(ref pool
) = pool
{
605 match info
.media_set_label
{
607 if set
.uuid
!= [0u8; 16].into() {
608 bail
!("verify media set label failed - got wrong set uuid");
610 if &set
.pool
!= pool
{
611 bail
!("verify media set label failed - got wrong pool");
615 bail
!("verify media set label failed (missing set label)");
620 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
621 Err(err
) => bail
!("verify label failed - {}", err
),
634 schema
: DRIVE_NAME_SCHEMA
,
637 description
: "Encryption key password.",
642 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
645 /// Try to restore a tape encryption key
646 pub async
fn restore_key(
649 ) -> Result
<(), Error
> {
650 run_drive_blocking_task(
652 "restore key".to_string(),
654 let mut drive
= open_drive(&config
, &drive
)?
;
656 let (_media_id
, key_config
) = drive
.read_label()?
;
658 if let Some(key_config
) = key_config
{
659 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
660 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
661 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
663 bail
!("media does not contain any encryption key configuration");
676 schema
: DRIVE_NAME_SCHEMA
,
679 description
: "Inventorize media",
688 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
691 /// Read media label (optionally inventorize media)
692 pub async
fn read_label(
694 inventorize
: Option
<bool
>,
695 ) -> Result
<MediaIdFlat
, Error
> {
696 run_drive_blocking_task(
698 "reading label".to_string(),
700 let mut drive
= open_drive(&config
, &drive
)?
;
702 let (media_id
, _key_config
) = drive
.read_label()?
;
704 let media_id
= match media_id
{
706 let mut flat
= MediaIdFlat
{
707 uuid
: media_id
.label
.uuid
.clone(),
708 label_text
: media_id
.label
.label_text
.clone(),
709 ctime
: media_id
.label
.ctime
,
710 media_set_ctime
: None
,
711 media_set_uuid
: None
,
712 encryption_key_fingerprint
: None
,
716 if let Some(ref set
) = media_id
.media_set_label
{
717 flat
.pool
= Some(set
.pool
.clone());
718 flat
.seq_nr
= Some(set
.seq_nr
);
719 flat
.media_set_uuid
= Some(set
.uuid
.clone());
720 flat
.media_set_ctime
= Some(set
.ctime
);
721 flat
.encryption_key_fingerprint
= set
722 .encryption_key_fingerprint
724 .map(|fp
| pbs_tools
::format
::as_fingerprint(fp
.bytes()));
726 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
727 .map(|fp
| (fp
, set
.uuid
.clone()));
729 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
730 // try, but ignore errors. just log to stderr
731 eprintln
!("unable to load encryption key: {}", err
);
735 if let Some(true) = inventorize
{
736 let state_path
= Path
::new(TAPE_STATUS_DIR
);
737 let mut inventory
= Inventory
::new(state_path
);
739 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
740 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
741 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
742 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
743 inventory
.store(media_id
, false)?
;
745 let _lock
= lock_unassigned_media_pool(state_path
)?
;
746 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
747 inventory
.store(media_id
, false)?
;
754 bail
!("Media is empty (no label).");
768 schema
: DRIVE_NAME_SCHEMA
,
776 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
782 rpcenv
: &mut dyn RpcEnvironment
,
783 ) -> Result
<Value
, Error
> {
784 let upid_str
= run_drive_worker(
789 move |worker
, config
| {
790 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
792 worker
.log("Starting drive clean");
794 changer
.clean_drive()?
;
796 if let Ok(drive_config
) = config
.lookup
::<LtoTapeDrive
>("lto", &drive
) {
797 // Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
798 let mut handle
= LtoTapeHandle
::new(open_lto_tape_device(&drive_config
.path
)?
)?
;
800 // test for critical tape alert flags
801 if let Ok(alert_flags
) = handle
.tape_alert_flags() {
802 if !alert_flags
.is_empty() {
803 worker
.log(format
!("TapeAlertFlags: {:?}", alert_flags
));
804 if tape_alert_flags_critical(alert_flags
) {
805 bail
!("found critical tape alert flags: {:?}", alert_flags
);
810 // test wearout (max. 50 mounts)
811 if let Ok(volume_stats
) = handle
.volume_statistics() {
812 worker
.log(format
!("Volume mounts: {}", volume_stats
.volume_mounts
));
813 let wearout
= volume_stats
.volume_mounts
* 2; // (*100.0/50.0);
814 worker
.log(format
!("Cleaning tape wearout: {}%", wearout
));
818 worker
.log("Drive cleaned successfully");
831 schema
: DRIVE_NAME_SCHEMA
,
836 description
: "The list of media labels with associated media Uuid (if any).",
843 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
846 /// List known media labels (Changer Inventory)
848 /// Note: Only useful for drives with associated changer device.
850 /// This method queries the changer to get a list of media labels.
852 /// Note: This updates the media online status.
853 pub async
fn inventory(
855 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
856 run_drive_blocking_task(
858 "inventorize".to_string(),
860 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
862 let label_text_list
= changer
.online_media_label_texts()?
;
864 let state_path
= Path
::new(TAPE_STATUS_DIR
);
866 let mut inventory
= Inventory
::load(state_path
)?
;
868 update_changer_online_status(
875 let mut list
= Vec
::new();
877 for label_text
in label_text_list
.iter() {
878 if label_text
.starts_with("CLN") {
879 // skip cleaning unit
883 let label_text
= label_text
.to_string();
885 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
886 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
888 list
.push(LabelUuidMap { label_text, uuid: None }
);
902 schema
: DRIVE_NAME_SCHEMA
,
905 description
: "Load all tapes and try read labels (even if already inventoried)",
915 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
920 /// Note: Only useful for drives with associated changer device.
922 /// This method queries the changer to get a list of media labels. It
923 /// then loads any unknown media into the drive, reads the label, and
924 /// store the result to the media database.
926 /// Note: This updates the media online status.
927 pub fn update_inventory(
929 read_all_labels
: Option
<bool
>,
930 rpcenv
: &mut dyn RpcEnvironment
,
931 ) -> Result
<Value
, Error
> {
932 let upid_str
= run_drive_worker(
937 move |worker
, config
| {
938 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
940 let label_text_list
= changer
.online_media_label_texts()?
;
941 if label_text_list
.is_empty() {
942 worker
.log("changer device does not list any media labels".to_string());
945 let state_path
= Path
::new(TAPE_STATUS_DIR
);
947 let mut inventory
= Inventory
::load(state_path
)?
;
949 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
951 for label_text
in label_text_list
.iter() {
952 if label_text
.starts_with("CLN") {
953 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
957 let label_text
= label_text
.to_string();
959 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
960 worker
.log(format
!("media '{}' already inventoried", label_text
));
964 if let Err(err
) = changer
.load_media(&label_text
) {
965 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
969 let mut drive
= open_drive(&config
, &drive
)?
;
970 match drive
.read_label() {
972 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
975 worker
.log(format
!("media '{}' is empty", label_text
));
977 Ok((Some(media_id
), _key_config
)) => {
978 if label_text
!= media_id
.label
.label_text
{
979 worker
.warn(format
!("label text mismatch ({} != {})", label_text
, media_id
.label
.label_text
));
982 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
984 if let Some(MediaSetLabel { ref pool, ref uuid, ..}
) = media_id
.media_set_label
{
985 let _pool_lock
= lock_media_pool(state_path
, pool
)?
;
986 let _lock
= lock_media_set(state_path
, uuid
, None
)?
;
987 MediaCatalog
::destroy_unrelated_catalog(state_path
, &media_id
)?
;
988 inventory
.store(media_id
, false)?
;
990 let _lock
= lock_unassigned_media_pool(state_path
)?
;
991 MediaCatalog
::destroy(state_path
, &media_id
.label
.uuid
)?
;
992 inventory
.store(media_id
, false)?
;
996 changer
.unload_media(None
)?
;
1010 schema
: DRIVE_NAME_SCHEMA
,
1013 schema
: MEDIA_POOL_NAME_SCHEMA
,
1019 schema
: UPID_SCHEMA
,
1022 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_WRITE
, false),
1025 /// Label media with barcodes from changer device
1026 pub fn barcode_label_media(
1028 pool
: Option
<String
>,
1029 rpcenv
: &mut dyn RpcEnvironment
,
1030 ) -> Result
<Value
, Error
> {
1031 if let Some(ref pool
) = pool
{
1032 let (pool_config
, _digest
) = pbs_config
::media_pool
::config()?
;
1034 if pool_config
.sections
.get(pool
).is_none() {
1035 bail
!("no such pool ('{}')", pool
);
1039 let upid_str
= run_drive_worker(
1042 "barcode-label-media",
1043 Some(drive
.clone()),
1044 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
1050 fn barcode_label_media_worker(
1051 worker
: Arc
<WorkerTask
>,
1053 drive_config
: &SectionConfigData
,
1054 pool
: Option
<String
>,
1055 ) -> Result
<(), Error
> {
1056 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
1058 let mut label_text_list
= changer
.online_media_label_texts()?
;
1060 // make sure we label them in the right order
1061 label_text_list
.sort();
1063 let state_path
= Path
::new(TAPE_STATUS_DIR
);
1065 let mut inventory
= Inventory
::load(state_path
)?
;
1067 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
1069 if label_text_list
.is_empty() {
1070 bail
!("changer device does not list any media labels");
1073 for label_text
in label_text_list
{
1074 if label_text
.starts_with("CLN") { continue; }
1076 inventory
.reload()?
;
1077 if inventory
.find_media_by_label_text(&label_text
).is_some() {
1078 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
1082 worker
.log(format
!("checking/loading media '{}'", label_text
));
1084 if let Err(err
) = changer
.load_media(&label_text
) {
1085 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
1089 let mut drive
= open_drive(drive_config
, &drive
)?
;
1092 match drive
.read_next_file() {
1094 worker
.log(format
!("media '{}' is not empty (format it first)", label_text
));
1097 Err(BlockReadError
::EndOfFile
) => { /* EOF mark at BOT, assume tape is empty */ }
,
1098 Err(BlockReadError
::EndOfStream
) => { /* tape is empty */ }
,
1100 worker
.warn(format
!("media '{}' read error (maybe not empty - format it 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
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1144 let mut handle
= open_lto_tape_drive(&drive_config
)?
;
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
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1174 let mut handle
= open_lto_tape_drive(&drive_config
)?
;
1176 handle
.volume_statistics()
1186 schema
: DRIVE_NAME_SCHEMA
,
1191 type: LtoDriveAndMediaStatus
,
1194 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_AUDIT
, false),
1197 /// Get drive/media status
1198 pub async
fn status(drive
: String
) -> Result
<LtoDriveAndMediaStatus
, Error
> {
1199 run_drive_blocking_task(
1201 "reading drive status".to_string(),
1203 let drive_config
: LtoTapeDrive
= config
.lookup("lto", &drive
)?
;
1205 // Note: use open_lto_tape_device, because this also works if no medium loaded
1206 let file
= open_lto_tape_device(&drive_config
.path
)?
;
1208 let mut handle
= LtoTapeHandle
::new(file
)?
;
1210 handle
.get_drive_and_media_status()
1220 schema
: DRIVE_NAME_SCHEMA
,
1223 description
: "Force overriding existing index.",
1228 description
: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
1233 description
: "Verbose mode - log all found chunks.",
1240 schema
: UPID_SCHEMA
,
1243 permission
: &Permission
::Privilege(&["tape", "device", "{drive}"], PRIV_TAPE_READ
, false),
1246 /// Scan media and record content
1247 pub fn catalog_media(
1249 force
: Option
<bool
>,
1251 verbose
: Option
<bool
>,
1252 rpcenv
: &mut dyn RpcEnvironment
,
1253 ) -> Result
<Value
, Error
> {
1254 let verbose
= verbose
.unwrap_or(false);
1255 let force
= force
.unwrap_or(false);
1256 let scan
= scan
.unwrap_or(false);
1258 let upid_str
= run_drive_worker(
1262 Some(drive
.clone()),
1263 move |worker
, config
| {
1264 let mut drive
= open_drive(&config
, &drive
)?
;
1268 let media_id
= match drive
.read_label()?
{
1269 (Some(media_id
), key_config
) => {
1271 "found media label: {}",
1272 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1274 if key_config
.is_some() {
1276 "encryption key config: {}",
1277 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1282 (None
, _
) => bail
!("media is empty (no media label found)"),
1285 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1287 let mut inventory
= Inventory
::new(status_path
);
1289 let (_media_set_lock
, media_set_uuid
) = match media_id
.media_set_label
{
1291 worker
.log("media is empty");
1292 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1293 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1294 inventory
.store(media_id
.clone(), false)?
;
1298 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1299 worker
.log("media is empty");
1300 let _lock
= lock_unassigned_media_pool(status_path
)?
;
1301 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1302 inventory
.store(media_id
.clone(), false)?
;
1305 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1306 .map(|fp
| (fp
, set
.uuid
.clone()));
1308 drive
.set_encryption(encrypt_fingerprint
)?
;
1310 let _pool_lock
= lock_media_pool(status_path
, &set
.pool
)?
;
1311 let media_set_lock
= lock_media_set(status_path
, &set
.uuid
, None
)?
;
1313 MediaCatalog
::destroy_unrelated_catalog(status_path
, &media_id
)?
;
1315 inventory
.store(media_id
.clone(), false)?
;
1317 (media_set_lock
, &set
.uuid
)
1321 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1322 bail
!("media catalog exists (please use --force to overwrite)");
1326 let media_set
= inventory
.compute_media_set_members(media_set_uuid
)?
;
1328 if fast_catalog_restore(&worker
, &mut drive
, &media_set
, &media_id
.label
.uuid
)?
{
1332 task_log
!(worker
, "no catalog found");
1335 task_log
!(worker
, "scanning entire media to reconstruct catalog");
1338 drive
.read_label()?
; // skip over labels - we already read them above
1340 let mut checked_chunks
= HashMap
::new();
1341 restore_media(worker
, &mut drive
, &media_id
, None
, &mut checked_chunks
, verbose
)?
;
1354 schema
: CHANGER_NAME_SCHEMA
,
1360 description
: "The list of configured drives with model information.",
1363 type: DriveListEntry
,
1367 description
: "List configured tape drives filtered by Tape.Audit privileges",
1368 permission
: &Permission
::Anybody
,
1373 changer
: Option
<String
>,
1375 rpcenv
: &mut dyn RpcEnvironment
,
1376 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1377 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
1378 let user_info
= CachedUserInfo
::new()?
;
1380 let (config
, _
) = pbs_config
::drive
::config()?
;
1382 let lto_drives
= lto_tape_device_list();
1384 let drive_list
: Vec
<LtoTapeDrive
> = config
.convert_to_typed_array("lto")?
;
1386 let mut list
= Vec
::new();
1388 for drive
in drive_list
{
1389 if changer
.is_some() && drive
.changer
!= changer
{
1393 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
.name
]);
1394 if (privs
& PRIV_TAPE_AUDIT
) == 0 {
1398 let info
= lookup_device_identification(<o_drives
, &drive
.path
);
1399 let state
= get_tape_device_state(&config
, &drive
.name
)?
;
1400 let entry
= DriveListEntry { config: drive, info, state }
;
1408 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1410 "barcode-label-media",
1412 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1417 .post(&API_METHOD_CATALOG_MEDIA
)
1422 .put(&API_METHOD_CLEAN_DRIVE
)
1427 .post(&API_METHOD_EJECT_MEDIA
)
1432 .post(&API_METHOD_FORMAT_MEDIA
)
1437 .put(&API_METHOD_EXPORT_MEDIA
)
1442 .get(&API_METHOD_INVENTORY
)
1443 .put(&API_METHOD_UPDATE_INVENTORY
)
1448 .post(&API_METHOD_LABEL_MEDIA
)
1453 .post(&API_METHOD_LOAD_MEDIA
)
1458 .post(&API_METHOD_LOAD_SLOT
)
1463 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1466 "volume-statistics",
1468 .get(&API_METHOD_VOLUME_STATISTICS
)
1473 .get(&API_METHOD_READ_LABEL
)
1478 .post(&API_METHOD_RESTORE_KEY
)
1483 .post(&API_METHOD_REWIND
)
1488 .get(&API_METHOD_STATUS
)
1493 .post(&API_METHOD_UNLOAD
)
1497 const ITEM_ROUTER
: Router
= Router
::new()
1498 .get(&list_subdirs_api_method
!(SUBDIRS
))
1501 pub const ROUTER
: Router
= Router
::new()
1502 .get(&API_METHOD_LIST_DRIVES
)
1503 .match_all("drive", &ITEM_ROUTER
);