1 use std
::panic
::UnwindSafe
;
5 use anyhow
::{bail, format_err, Error}
;
11 list_subdirs_api_method
,
16 section_config
::SectionConfigData
,
33 MEDIA_POOL_NAME_SCHEMA
,
40 LinuxDriveAndMediaStatus
,
42 tape
::restore
::restore_media
,
51 linux_tape_device_list
,
52 lookup_device_identification
,
61 open_linux_tape_device
,
63 required_media_changer
,
66 set_tape_device_state
,
68 changer
::update_changer_online_status
,
72 fn run_drive_worker
<F
>(
73 rpcenv
: &dyn RpcEnvironment
,
76 job_id
: Option
<String
>,
78 ) -> Result
<String
, Error
>
83 + FnOnce(Arc
<WorkerTask
>, SectionConfigData
) -> Result
<(), Error
>,
85 // early check/lock before starting worker
86 let (config
, _digest
) = config
::drive
::config()?
;
87 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
89 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
90 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
92 WorkerTask
::new_thread(worker_type
, job_id
, auth_id
, to_stdout
, move |worker
| {
93 let _lock_guard
= lock_guard
;
94 set_tape_device_state(&drive
, &worker
.upid().to_string())
95 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
97 let result
= f(worker
, config
);
98 set_tape_device_state(&drive
, "")
99 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
104 async
fn run_drive_blocking_task
<F
, R
>(drive
: String
, state
: String
, f
: F
) -> Result
<R
, Error
>
106 F
: Send
+ '
static + FnOnce(SectionConfigData
) -> Result
<R
, Error
>,
109 // early check/lock before starting worker
110 let (config
, _digest
) = config
::drive
::config()?
;
111 let lock_guard
= lock_tape_device(&config
, &drive
)?
;
112 tokio
::task
::spawn_blocking(move || {
113 let _lock_guard
= lock_guard
;
114 set_tape_device_state(&drive
, &state
)
115 .map_err(|err
| format_err
!("could not set tape device state: {}", err
))?
;
116 let result
= f(config
);
117 set_tape_device_state(&drive
, "")
118 .map_err(|err
| format_err
!("could not unset tape device state: {}", err
))?
;
128 schema
: DRIVE_NAME_SCHEMA
,
131 schema
: MEDIA_LABEL_SCHEMA
,
139 /// Load media with specified label
141 /// Issue a media load request to the associated changer device.
145 rpcenv
: &mut dyn RpcEnvironment
,
146 ) -> Result
<Value
, Error
> {
147 let job_id
= format
!("{}:{}", drive
, label_text
);
149 let upid_str
= run_drive_worker(
154 move |worker
, config
| {
155 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
156 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
157 changer
.load_media(&label_text
)?
;
169 schema
: DRIVE_NAME_SCHEMA
,
172 description
: "Source slot number.",
178 /// Load media from the specified slot
180 /// Issue a media load request to the associated changer device.
181 pub async
fn load_slot(drive
: String
, source_slot
: u64) -> Result
<(), Error
> {
182 run_drive_blocking_task(
184 format
!("load from slot {}", source_slot
),
186 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
187 changer
.load_media_from_slot(source_slot
)?
;
198 schema
: DRIVE_NAME_SCHEMA
,
201 schema
: MEDIA_LABEL_SCHEMA
,
206 description
: "The import-export slot number the media was transfered to.",
211 /// Export media with specified label
212 pub async
fn export_media(drive
: String
, label_text
: String
) -> Result
<u64, Error
> {
213 run_drive_blocking_task(
215 format
!("export media {}", label_text
),
217 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
218 match changer
.export_media(&label_text
)?
{
219 Some(slot
) => Ok(slot
),
221 "media '{}' is not online (via changer '{}')",
235 schema
: DRIVE_NAME_SCHEMA
,
238 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
248 /// Unload media via changer
251 target_slot
: Option
<u64>,
252 rpcenv
: &mut dyn RpcEnvironment
,
253 ) -> Result
<Value
, Error
> {
254 let upid_str
= run_drive_worker(
259 move |worker
, config
| {
260 task_log
!(worker
, "unloading media from drive '{}'", drive
);
262 let (mut changer
, _
) = required_media_changer(&config
, &drive
)?
;
263 changer
.unload_media(target_slot
)?
;
275 schema
: DRIVE_NAME_SCHEMA
,
278 description
: "Use fast erase.",
284 schema
: MEDIA_LABEL_SCHEMA
,
293 /// Erase media. Check for label-text if given (cancels if wrong media).
297 label_text
: Option
<String
>,
298 rpcenv
: &mut dyn RpcEnvironment
,
299 ) -> Result
<Value
, Error
> {
300 let upid_str
= run_drive_worker(
305 move |worker
, config
| {
306 if let Some(ref label
) = label_text
{
307 task_log
!(worker
, "try to load media '{}'", label
);
308 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
309 changer
.load_media(label
)?
;
313 let mut handle
= open_drive(&config
, &drive
)?
;
315 match handle
.read_label() {
317 if let Some(label
) = label_text
{
318 bail
!("expected label '{}', found unrelated data", label
);
320 /* assume drive contains no or unrelated data */
321 task_log
!(worker
, "unable to read media label: {}", err
);
322 task_log
!(worker
, "erase anyways");
323 handle
.erase_media(fast
.unwrap_or(true))?
;
326 if let Some(label
) = label_text
{
327 bail
!("expected label '{}', found empty tape", label
);
329 task_log
!(worker
, "found empty media - erase anyways");
330 handle
.erase_media(fast
.unwrap_or(true))?
;
332 Ok((Some(media_id
), _key_config
)) => {
333 if let Some(label_text
) = label_text
{
334 if media_id
.label
.label_text
!= label_text
{
336 "expected label '{}', found '{}', aborting",
338 media_id
.label
.label_text
345 "found media '{}' with uuid '{}'",
346 media_id
.label
.label_text
, media_id
.label
.uuid
,
349 let status_path
= Path
::new(TAPE_STATUS_DIR
);
350 let mut inventory
= Inventory
::load(status_path
)?
;
352 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
353 inventory
.remove_media(&media_id
.label
.uuid
)?
;
354 handle
.erase_media(fast
.unwrap_or(true))?
;
369 schema
: DRIVE_NAME_SCHEMA
,
380 rpcenv
: &mut dyn RpcEnvironment
,
381 ) -> Result
<Value
, Error
> {
382 let upid_str
= run_drive_worker(
387 move |_worker
, config
| {
388 let mut drive
= open_drive(&config
, &drive
)?
;
401 schema
: DRIVE_NAME_SCHEMA
,
409 /// Eject/Unload drive media
412 rpcenv
: &mut dyn RpcEnvironment
,
413 ) -> Result
<Value
, Error
> {
414 let upid_str
= run_drive_worker(
419 move |_worker
, config
| {
420 if let Some((mut changer
, _
)) = media_changer(&config
, &drive
)?
{
421 changer
.unload_media(None
)?
;
423 let mut drive
= open_drive(&config
, &drive
)?
;
424 drive
.eject_media()?
;
437 schema
: DRIVE_NAME_SCHEMA
,
440 schema
: MEDIA_LABEL_SCHEMA
,
443 schema
: MEDIA_POOL_NAME_SCHEMA
,
454 /// Write a new media label to the media in 'drive'. The media is
455 /// assigned to the specified 'pool', or else to the free media pool.
457 /// Note: The media need to be empty (you may want to erase it first).
460 pool
: Option
<String
>,
462 rpcenv
: &mut dyn RpcEnvironment
,
463 ) -> Result
<Value
, Error
> {
464 if let Some(ref pool
) = pool
{
465 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
467 if pool_config
.sections
.get(pool
).is_none() {
468 bail
!("no such pool ('{}')", pool
);
471 let upid_str
= run_drive_worker(
476 move |worker
, config
| {
477 let mut drive
= open_drive(&config
, &drive
)?
;
481 match drive
.read_next_file() {
482 Ok(Some(_file
)) => bail
!("media is not empty (erase first)"),
483 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
485 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
486 /* assume tape is empty */
488 bail
!("media read error - {}", err
);
493 let ctime
= proxmox
::tools
::time
::epoch_i64();
494 let label
= MediaLabel
{
495 label_text
: label_text
.to_string(),
496 uuid
: Uuid
::generate(),
500 write_media_label(worker
, &mut drive
, label
, pool
)
507 fn write_media_label(
508 worker
: Arc
<WorkerTask
>,
509 drive
: &mut Box
<dyn TapeDriver
>,
511 pool
: Option
<String
>,
512 ) -> Result
<(), Error
> {
514 drive
.label_tape(&label
)?
;
516 let mut media_set_label
= None
;
518 if let Some(ref pool
) = pool
{
519 // assign media to pool by writing special media set label
520 worker
.log(format
!("Label media '{}' for pool '{}'", label
.label_text
, pool
));
521 let set
= MediaSetLabel
::with_data(&pool
, [0u8; 16].into(), 0, label
.ctime
, None
);
523 drive
.write_media_set_label(&set
, None
)?
;
524 media_set_label
= Some(set
);
526 worker
.log(format
!("Label media '{}' (no pool assignment)", label
.label_text
));
529 let media_id
= MediaId { label, media_set_label }
;
531 let status_path
= Path
::new(TAPE_STATUS_DIR
);
533 // Create the media catalog
534 MediaCatalog
::overwrite(status_path
, &media_id
, false)?
;
536 let mut inventory
= Inventory
::load(status_path
)?
;
537 inventory
.store(media_id
.clone(), false)?
;
541 match drive
.read_label() {
542 Ok((Some(info
), _
)) => {
543 if info
.label
.uuid
!= media_id
.label
.uuid
{
544 bail
!("verify label failed - got wrong label uuid");
546 if let Some(ref pool
) = pool
{
547 match info
.media_set_label
{
549 if set
.uuid
!= [0u8; 16].into() {
550 bail
!("verify media set label failed - got wrong set uuid");
552 if &set
.pool
!= pool
{
553 bail
!("verify media set label failed - got wrong pool");
557 bail
!("verify media set label failed (missing set label)");
562 Ok((None
, _
)) => bail
!("verify label failed (got empty media)"),
563 Err(err
) => bail
!("verify label failed - {}", err
),
576 schema
: DRIVE_NAME_SCHEMA
,
579 description
: "Encryption key password.",
584 /// Try to restore a tape encryption key
585 pub async
fn restore_key(
588 ) -> Result
<(), Error
> {
589 run_drive_blocking_task(
591 "restore key".to_string(),
593 let mut drive
= open_drive(&config
, &drive
)?
;
595 let (_media_id
, key_config
) = drive
.read_label()?
;
597 if let Some(key_config
) = key_config
{
598 let password_fn
= || { Ok(password.as_bytes().to_vec()) }
;
599 let (key
, ..) = key_config
.decrypt(&password_fn
)?
;
600 config
::tape_encryption_keys
::insert_key(key
, key_config
, true)?
;
602 bail
!("media does not contain any encryption key configuration");
615 schema
: DRIVE_NAME_SCHEMA
,
618 description
: "Inventorize media",
627 /// Read media label (optionally inventorize media)
628 pub async
fn read_label(
630 inventorize
: Option
<bool
>,
631 ) -> Result
<MediaIdFlat
, Error
> {
632 run_drive_blocking_task(
634 "reading label".to_string(),
636 let mut drive
= open_drive(&config
, &drive
)?
;
638 let (media_id
, _key_config
) = drive
.read_label()?
;
640 let media_id
= match media_id
{
642 let mut flat
= MediaIdFlat
{
643 uuid
: media_id
.label
.uuid
.clone(),
644 label_text
: media_id
.label
.label_text
.clone(),
645 ctime
: media_id
.label
.ctime
,
646 media_set_ctime
: None
,
647 media_set_uuid
: None
,
648 encryption_key_fingerprint
: None
,
652 if let Some(ref set
) = media_id
.media_set_label
{
653 flat
.pool
= Some(set
.pool
.clone());
654 flat
.seq_nr
= Some(set
.seq_nr
);
655 flat
.media_set_uuid
= Some(set
.uuid
.clone());
656 flat
.media_set_ctime
= Some(set
.ctime
);
657 flat
.encryption_key_fingerprint
= set
658 .encryption_key_fingerprint
660 .map(|fp
| crate::tools
::format
::as_fingerprint(fp
.bytes()));
662 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
663 .map(|fp
| (fp
, set
.uuid
.clone()));
665 if let Err(err
) = drive
.set_encryption(encrypt_fingerprint
) {
666 // try, but ignore errors. just log to stderr
667 eprintln
!("uable to load encryption key: {}", err
);
671 if let Some(true) = inventorize
{
672 let state_path
= Path
::new(TAPE_STATUS_DIR
);
673 let mut inventory
= Inventory
::load(state_path
)?
;
674 inventory
.store(media_id
, false)?
;
680 bail
!("Media is empty (no label).");
694 schema
: DRIVE_NAME_SCHEMA
,
705 rpcenv
: &mut dyn RpcEnvironment
,
706 ) -> Result
<Value
, Error
> {
707 let upid_str
= run_drive_worker(
712 move |worker
, config
| {
713 let (mut changer
, _changer_name
) = required_media_changer(&config
, &drive
)?
;
715 worker
.log("Starting drive clean");
717 changer
.clean_drive()?
;
719 worker
.log("Drive cleaned sucessfully");
732 schema
: DRIVE_NAME_SCHEMA
,
737 description
: "The list of media labels with associated media Uuid (if any).",
744 /// List known media labels (Changer Inventory)
746 /// Note: Only useful for drives with associated changer device.
748 /// This method queries the changer to get a list of media labels.
750 /// Note: This updates the media online status.
751 pub async
fn inventory(
753 ) -> Result
<Vec
<LabelUuidMap
>, Error
> {
754 run_drive_blocking_task(
756 "inventorize".to_string(),
758 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
760 let label_text_list
= changer
.online_media_label_texts()?
;
762 let state_path
= Path
::new(TAPE_STATUS_DIR
);
764 let mut inventory
= Inventory
::load(state_path
)?
;
766 update_changer_online_status(
773 let mut list
= Vec
::new();
775 for label_text
in label_text_list
.iter() {
776 if label_text
.starts_with("CLN") {
777 // skip cleaning unit
781 let label_text
= label_text
.to_string();
783 if let Some(media_id
) = inventory
.find_media_by_label_text(&label_text
) {
784 list
.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) }
);
786 list
.push(LabelUuidMap { label_text, uuid: None }
);
800 schema
: DRIVE_NAME_SCHEMA
,
803 description
: "Load all tapes and try read labels (even if already inventoried)",
815 /// Note: Only useful for drives with associated changer device.
817 /// This method queries the changer to get a list of media labels. It
818 /// then loads any unknown media into the drive, reads the label, and
819 /// store the result to the media database.
821 /// Note: This updates the media online status.
822 pub fn update_inventory(
824 read_all_labels
: Option
<bool
>,
825 rpcenv
: &mut dyn RpcEnvironment
,
826 ) -> Result
<Value
, Error
> {
827 let upid_str
= run_drive_worker(
832 move |worker
, config
| {
833 let (mut changer
, changer_name
) = required_media_changer(&config
, &drive
)?
;
835 let label_text_list
= changer
.online_media_label_texts()?
;
836 if label_text_list
.is_empty() {
837 worker
.log("changer device does not list any media labels".to_string());
840 let state_path
= Path
::new(TAPE_STATUS_DIR
);
842 let mut inventory
= Inventory
::load(state_path
)?
;
844 update_changer_online_status(&config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
846 for label_text
in label_text_list
.iter() {
847 if label_text
.starts_with("CLN") {
848 worker
.log(format
!("skip cleaning unit '{}'", label_text
));
852 let label_text
= label_text
.to_string();
854 if !read_all_labels
.unwrap_or(false) && inventory
.find_media_by_label_text(&label_text
).is_some() {
855 worker
.log(format
!("media '{}' already inventoried", label_text
));
859 if let Err(err
) = changer
.load_media(&label_text
) {
860 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
864 let mut drive
= open_drive(&config
, &drive
)?
;
865 match drive
.read_label() {
867 worker
.warn(format
!("unable to read label form media '{}' - {}", label_text
, err
));
870 worker
.log(format
!("media '{}' is empty", label_text
));
872 Ok((Some(media_id
), _key_config
)) => {
873 if label_text
!= media_id
.label
.label_text
{
874 worker
.warn(format
!("label text missmatch ({} != {})", label_text
, media_id
.label
.label_text
));
877 worker
.log(format
!("inventorize media '{}' with uuid '{}'", label_text
, media_id
.label
.uuid
));
878 inventory
.store(media_id
, false)?
;
881 changer
.unload_media(None
)?
;
895 schema
: DRIVE_NAME_SCHEMA
,
898 schema
: MEDIA_POOL_NAME_SCHEMA
,
907 /// Label media with barcodes from changer device
908 pub fn barcode_label_media(
910 pool
: Option
<String
>,
911 rpcenv
: &mut dyn RpcEnvironment
,
912 ) -> Result
<Value
, Error
> {
913 if let Some(ref pool
) = pool
{
914 let (pool_config
, _digest
) = config
::media_pool
::config()?
;
916 if pool_config
.sections
.get(pool
).is_none() {
917 bail
!("no such pool ('{}')", pool
);
921 let upid_str
= run_drive_worker(
924 "barcode-label-media",
926 move |worker
, config
| barcode_label_media_worker(worker
, drive
, &config
, pool
),
932 fn barcode_label_media_worker(
933 worker
: Arc
<WorkerTask
>,
935 drive_config
: &SectionConfigData
,
936 pool
: Option
<String
>,
937 ) -> Result
<(), Error
> {
938 let (mut changer
, changer_name
) = required_media_changer(drive_config
, &drive
)?
;
940 let label_text_list
= changer
.online_media_label_texts()?
;
942 let state_path
= Path
::new(TAPE_STATUS_DIR
);
944 let mut inventory
= Inventory
::load(state_path
)?
;
946 update_changer_online_status(drive_config
, &mut inventory
, &changer_name
, &label_text_list
)?
;
948 if label_text_list
.is_empty() {
949 bail
!("changer device does not list any media labels");
952 for label_text
in label_text_list
{
953 if label_text
.starts_with("CLN") { continue; }
956 if inventory
.find_media_by_label_text(&label_text
).is_some() {
957 worker
.log(format
!("media '{}' already inventoried (already labeled)", label_text
));
961 worker
.log(format
!("checking/loading media '{}'", label_text
));
963 if let Err(err
) = changer
.load_media(&label_text
) {
964 worker
.warn(format
!("unable to load media '{}' - {}", label_text
, err
));
968 let mut drive
= open_drive(drive_config
, &drive
)?
;
971 match drive
.read_next_file() {
973 worker
.log(format
!("media '{}' is not empty (erase first)", label_text
));
976 Ok(None
) => { /* EOF mark at BOT, assume tape is empty */ }
,
978 if err
.is_errno(nix
::errno
::Errno
::ENOSPC
) || err
.is_errno(nix
::errno
::Errno
::EIO
) {
979 /* assume tape is empty */
981 worker
.warn(format
!("media '{}' read error (maybe not empty - erase first)", label_text
));
987 let ctime
= proxmox
::tools
::time
::epoch_i64();
988 let label
= MediaLabel
{
989 label_text
: label_text
.to_string(),
990 uuid
: Uuid
::generate(),
994 write_media_label(worker
.clone(), &mut drive
, label
, pool
.clone())?
1004 schema
: DRIVE_NAME_SCHEMA
,
1009 description
: "A List of medium auxiliary memory attributes.",
1016 /// Read Cartridge Memory (Medium auxiliary memory attributes)
1017 pub async
fn cartridge_memory(drive
: String
) -> Result
<Vec
<MamAttribute
>, Error
> {
1018 run_drive_blocking_task(
1020 "reading cartridge memory".to_string(),
1022 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1023 let mut handle
= drive_config
.open()?
;
1025 handle
.cartridge_memory()
1035 schema
: DRIVE_NAME_SCHEMA
,
1040 type: Lp17VolumeStatistics
,
1043 /// Read Volume Statistics (SCSI log page 17h)
1044 pub async
fn volume_statistics(drive
: String
) -> Result
<Lp17VolumeStatistics
, Error
> {
1045 run_drive_blocking_task(
1047 "reading volume statistics".to_string(),
1049 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1050 let mut handle
= drive_config
.open()?
;
1052 handle
.volume_statistics()
1062 schema
: DRIVE_NAME_SCHEMA
,
1067 type: LinuxDriveAndMediaStatus
,
1070 /// Get drive/media status
1071 pub async
fn status(drive
: String
) -> Result
<LinuxDriveAndMediaStatus
, Error
> {
1072 run_drive_blocking_task(
1074 "reading drive status".to_string(),
1076 let drive_config
: LinuxTapeDrive
= config
.lookup("linux", &drive
)?
;
1078 // Note: use open_linux_tape_device, because this also works if no medium loaded
1079 let file
= open_linux_tape_device(&drive_config
.path
)?
;
1081 let mut handle
= LinuxTapeHandle
::new(file
);
1083 handle
.get_drive_and_media_status()
1093 schema
: DRIVE_NAME_SCHEMA
,
1096 description
: "Force overriding existing index.",
1101 description
: "Verbose mode - log all found chunks.",
1108 schema
: UPID_SCHEMA
,
1111 /// Scan media and record content
1112 pub fn catalog_media(
1114 force
: Option
<bool
>,
1115 verbose
: Option
<bool
>,
1116 rpcenv
: &mut dyn RpcEnvironment
,
1117 ) -> Result
<Value
, Error
> {
1118 let verbose
= verbose
.unwrap_or(false);
1119 let force
= force
.unwrap_or(false);
1121 let upid_str
= run_drive_worker(
1125 Some(drive
.clone()),
1126 move |worker
, config
| {
1127 let mut drive
= open_drive(&config
, &drive
)?
;
1131 let media_id
= match drive
.read_label()?
{
1132 (Some(media_id
), key_config
) => {
1134 "found media label: {}",
1135 serde_json
::to_string_pretty(&serde_json
::to_value(&media_id
)?
)?
1137 if key_config
.is_some() {
1139 "encryption key config: {}",
1140 serde_json
::to_string_pretty(&serde_json
::to_value(&key_config
)?
)?
1145 (None
, _
) => bail
!("media is empty (no media label found)"),
1148 let status_path
= Path
::new(TAPE_STATUS_DIR
);
1150 let mut inventory
= Inventory
::load(status_path
)?
;
1151 inventory
.store(media_id
.clone(), false)?
;
1153 let pool
= match media_id
.media_set_label
{
1155 worker
.log("media is empty");
1156 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1160 if set
.uuid
.as_ref() == [0u8;16] { // media is empty
1161 worker
.log("media is empty");
1162 MediaCatalog
::destroy(status_path
, &media_id
.label
.uuid
)?
;
1165 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
1166 .map(|fp
| (fp
, set
.uuid
.clone()));
1168 drive
.set_encryption(encrypt_fingerprint
)?
;
1174 let _lock
= MediaPool
::lock(status_path
, &pool
)?
;
1176 if MediaCatalog
::exists(status_path
, &media_id
.label
.uuid
) && !force
{
1177 bail
!("media catalog exists (please use --force to overwrite)");
1180 restore_media(&worker
, &mut drive
, &media_id
, None
, verbose
)?
;
1193 schema
: CHANGER_NAME_SCHEMA
,
1199 description
: "The list of configured drives with model information.",
1202 type: DriveListEntry
,
1208 changer
: Option
<String
>,
1210 ) -> Result
<Vec
<DriveListEntry
>, Error
> {
1212 let (config
, _
) = config
::drive
::config()?
;
1214 let linux_drives
= linux_tape_device_list();
1216 let drive_list
: Vec
<LinuxTapeDrive
> = config
.convert_to_typed_array("linux")?
;
1218 let mut list
= Vec
::new();
1220 for drive
in drive_list
{
1221 if changer
.is_some() && drive
.changer
!= changer
{
1225 let info
= lookup_device_identification(&linux_drives
, &drive
.path
);
1226 let entry
= DriveListEntry { config: drive, info }
;
1234 pub const SUBDIRS
: SubdirMap
= &sorted
!([
1236 "barcode-label-media",
1238 .post(&API_METHOD_BARCODE_LABEL_MEDIA
)
1243 .post(&API_METHOD_CATALOG_MEDIA
)
1248 .put(&API_METHOD_CLEAN_DRIVE
)
1253 .post(&API_METHOD_EJECT_MEDIA
)
1258 .post(&API_METHOD_ERASE_MEDIA
)
1263 .put(&API_METHOD_EXPORT_MEDIA
)
1268 .get(&API_METHOD_INVENTORY
)
1269 .put(&API_METHOD_UPDATE_INVENTORY
)
1274 .post(&API_METHOD_LABEL_MEDIA
)
1279 .post(&API_METHOD_LOAD_MEDIA
)
1284 .put(&API_METHOD_LOAD_SLOT
)
1289 .get(&API_METHOD_CARTRIDGE_MEMORY
)
1292 "volume-statistics",
1294 .get(&API_METHOD_VOLUME_STATISTICS
)
1299 .get(&API_METHOD_READ_LABEL
)
1304 .post(&API_METHOD_RESTORE_KEY
)
1309 .post(&API_METHOD_REWIND
)
1314 .get(&API_METHOD_STATUS
)
1319 .post(&API_METHOD_UNLOAD
)
1323 const ITEM_ROUTER
: Router
= Router
::new()
1324 .get(&list_subdirs_api_method
!(SUBDIRS
))
1327 pub const ROUTER
: Router
= Router
::new()
1328 .get(&API_METHOD_LIST_DRIVES
)
1329 .match_all("drive", &ITEM_ROUTER
);