1 use anyhow
::{format_err, Error}
;
2 use serde_json
::{json, Value}
;
9 section_config
::SectionConfigData
,
21 render_bytes_human_readable
,
33 MEDIA_POOL_NAME_SCHEMA
,
38 datastore
::complete_datastore_name
,
39 drive
::complete_drive_name
,
40 media_pool
::complete_pool_name
,
44 complete_media_label_text
,
45 complete_media_set_uuid
,
47 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
48 PROXMOX_BACKUP_CONTENT_NAME
,
57 pub fn lookup_drive_name(
59 config
: &SectionConfigData
,
60 ) -> Result
<String
, Error
> {
62 let drive
= param
["drive"]
65 .or_else(|| std
::env
::var("PROXMOX_TAPE_DRIVE").ok())
68 let mut drive_names
= Vec
::new();
70 for (name
, (section_type
, _
)) in config
.sections
.iter() {
72 if !(section_type
== "linux" || section_type
== "virtual") { continue; }
73 drive_names
.push(name
);
76 if drive_names
.len() == 1 {
77 Some(drive_names
[0].to_owned())
82 .ok_or_else(|| format_err
!("unable to get (default) drive name"))?
;
91 schema
: DRIVE_NAME_SCHEMA
,
95 description
: "Use fast erase.",
101 schema
: OUTPUT_FORMAT
,
108 async
fn erase_media(param
: Value
) -> Result
<(), Error
> {
110 let output_format
= get_output_format(¶m
);
112 let (config
, _digest
) = config
::drive
::config()?
;
114 let drive
= lookup_drive_name(¶m
, &config
)?
;
116 let mut client
= connect_to_localhost()?
;
118 let path
= format
!("api2/json/tape/drive/{}/erase-media", drive
);
119 let result
= client
.post(&path
, Some(param
)).await?
;
121 view_task_result(&mut client
, result
, &output_format
).await?
;
130 schema
: DRIVE_NAME_SCHEMA
,
134 schema
: OUTPUT_FORMAT
,
141 async
fn rewind(param
: Value
) -> Result
<(), Error
> {
143 let output_format
= get_output_format(¶m
);
145 let (config
, _digest
) = config
::drive
::config()?
;
147 let drive
= lookup_drive_name(¶m
, &config
)?
;
149 let mut client
= connect_to_localhost()?
;
151 let path
= format
!("api2/json/tape/drive/{}/rewind", drive
);
152 let result
= client
.post(&path
, Some(param
)).await?
;
154 view_task_result(&mut client
, result
, &output_format
).await?
;
163 schema
: DRIVE_NAME_SCHEMA
,
167 schema
: OUTPUT_FORMAT
,
173 /// Eject/Unload drive media
174 async
fn eject_media(param
: Value
) -> Result
<(), Error
> {
176 let output_format
= get_output_format(¶m
);
178 let (config
, _digest
) = config
::drive
::config()?
;
180 let drive
= lookup_drive_name(¶m
, &config
)?
;
182 let mut client
= connect_to_localhost()?
;
184 let path
= format
!("api2/json/tape/drive/{}/eject-media", drive
);
185 let result
= client
.post(&path
, Some(param
)).await?
;
187 view_task_result(&mut client
, result
, &output_format
).await?
;
196 schema
: DRIVE_NAME_SCHEMA
,
200 schema
: MEDIA_LABEL_SCHEMA
,
205 /// Load media with specified label
206 async
fn load_media(param
: Value
) -> Result
<(), Error
> {
208 let (config
, _digest
) = config
::drive
::config()?
;
210 let drive
= lookup_drive_name(¶m
, &config
)?
;
212 let mut client
= connect_to_localhost()?
;
214 let path
= format
!("api2/json/tape/drive/{}/load-media", drive
);
215 client
.put(&path
, Some(param
)).await?
;
224 schema
: DRIVE_NAME_SCHEMA
,
228 schema
: MEDIA_LABEL_SCHEMA
,
233 /// Export media with specified label
234 async
fn export_media(param
: Value
) -> Result
<(), Error
> {
236 let (config
, _digest
) = config
::drive
::config()?
;
238 let drive
= lookup_drive_name(¶m
, &config
)?
;
240 let mut client
= connect_to_localhost()?
;
242 let path
= format
!("api2/json/tape/drive/{}/export-media", drive
);
243 client
.put(&path
, Some(param
)).await?
;
252 schema
: DRIVE_NAME_SCHEMA
,
256 description
: "Source slot number.",
263 /// Load media from the specified slot
264 async
fn load_media_from_slot(param
: Value
) -> Result
<(), Error
> {
266 let (config
, _digest
) = config
::drive
::config()?
;
268 let drive
= lookup_drive_name(¶m
, &config
)?
;
270 let mut client
= connect_to_localhost()?
;
272 let path
= format
!("api2/json/tape/drive/{}/load-slot", drive
);
273 client
.put(&path
, Some(param
)).await?
;
282 schema
: DRIVE_NAME_SCHEMA
,
286 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
294 /// Unload media via changer
295 async
fn unload_media(param
: Value
) -> Result
<(), Error
> {
297 let (config
, _digest
) = config
::drive
::config()?
;
299 let drive
= lookup_drive_name(¶m
, &config
)?
;
301 let mut client
= connect_to_localhost()?
;
303 let path
= format
!("api2/json/tape/drive/{}/unload", drive
);
304 client
.put(&path
, Some(param
)).await?
;
313 schema
: MEDIA_POOL_NAME_SCHEMA
,
317 schema
: DRIVE_NAME_SCHEMA
,
321 schema
: MEDIA_LABEL_SCHEMA
,
324 schema
: OUTPUT_FORMAT
,
331 async
fn label_media(param
: Value
) -> Result
<(), Error
> {
333 let output_format
= get_output_format(¶m
);
335 let (config
, _digest
) = config
::drive
::config()?
;
337 let drive
= lookup_drive_name(¶m
, &config
)?
;
339 let mut client
= connect_to_localhost()?
;
341 let path
= format
!("api2/json/tape/drive/{}/label-media", drive
);
342 let result
= client
.post(&path
, Some(param
)).await?
;
344 view_task_result(&mut client
, result
, &output_format
).await?
;
353 schema
: DRIVE_NAME_SCHEMA
,
357 description
: "Inventorize media",
362 schema
: OUTPUT_FORMAT
,
369 async
fn read_label(param
: Value
) -> Result
<(), Error
> {
371 let output_format
= get_output_format(¶m
);
373 let (config
, _digest
) = config
::drive
::config()?
;
375 let drive
= lookup_drive_name(¶m
, &config
)?
;
377 let client
= connect_to_localhost()?
;
379 let path
= format
!("api2/json/tape/drive/{}/read-label", drive
);
380 let mut result
= client
.get(&path
, Some(param
)).await?
;
381 let mut data
= result
["data"].take();
383 let info
= &api2
::tape
::drive
::API_METHOD_READ_LABEL
;
385 let options
= default_table_format_options()
386 .column(ColumnConfig
::new("label-text"))
387 .column(ColumnConfig
::new("uuid"))
388 .column(ColumnConfig
::new("ctime").renderer(render_epoch
))
389 .column(ColumnConfig
::new("pool"))
390 .column(ColumnConfig
::new("media-set-uuid"))
391 .column(ColumnConfig
::new("media-set-ctime").renderer(render_epoch
))
392 .column(ColumnConfig
::new("encryption-key-fingerprint"))
395 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
404 schema
: OUTPUT_FORMAT
,
408 schema
: DRIVE_NAME_SCHEMA
,
412 description
: "Load unknown tapes and try read labels",
417 description
: "Load all tapes and try read labels (even if already inventoried)",
424 /// List (and update) media labels (Changer Inventory)
426 read_labels
: Option
<bool
>,
427 read_all_labels
: Option
<bool
>,
429 ) -> Result
<(), Error
> {
431 let output_format
= get_output_format(¶m
);
433 let (config
, _digest
) = config
::drive
::config()?
;
434 let drive
= lookup_drive_name(¶m
, &config
)?
;
436 let do_read
= read_labels
.unwrap_or(false) || read_all_labels
.unwrap_or(false);
438 let mut client
= connect_to_localhost()?
;
440 let path
= format
!("api2/json/tape/drive/{}/inventory", drive
);
444 let mut param
= json
!({}
);
445 if let Some(true) = read_all_labels
{
446 param
["read-all-labels"] = true.into();
449 let result
= client
.put(&path
, Some(param
)).await?
; // update inventory
450 view_task_result(&mut client
, result
, &output_format
).await?
;
453 let mut result
= client
.get(&path
, None
).await?
;
454 let mut data
= result
["data"].take();
456 let info
= &api2
::tape
::drive
::API_METHOD_INVENTORY
;
458 let options
= default_table_format_options()
459 .column(ColumnConfig
::new("label-text"))
460 .column(ColumnConfig
::new("uuid"))
463 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
472 schema
: MEDIA_POOL_NAME_SCHEMA
,
476 schema
: DRIVE_NAME_SCHEMA
,
480 schema
: OUTPUT_FORMAT
,
486 /// Label media with barcodes from changer device
487 async
fn barcode_label_media(param
: Value
) -> Result
<(), Error
> {
489 let output_format
= get_output_format(¶m
);
491 let (config
, _digest
) = config
::drive
::config()?
;
493 let drive
= lookup_drive_name(¶m
, &config
)?
;
495 let mut client
= connect_to_localhost()?
;
497 let path
= format
!("api2/json/tape/drive/{}/barcode-label-media", drive
);
498 let result
= client
.post(&path
, Some(param
)).await?
;
500 view_task_result(&mut client
, result
, &output_format
).await?
;
509 schema
: DRIVE_NAME_SCHEMA
,
515 /// Move to end of media (MTEOM, used to debug)
516 fn move_to_eom(param
: Value
) -> Result
<(), Error
> {
518 let (config
, _digest
) = config
::drive
::config()?
;
520 let drive
= lookup_drive_name(¶m
, &config
)?
;
521 let mut drive
= open_drive(&config
, &drive
)?
;
523 drive
.move_to_eom()?
;
532 schema
: DRIVE_NAME_SCHEMA
,
538 /// Rewind, then read media contents and print debug info
540 /// Note: This reads unless the driver returns an IO Error, so this
541 /// method is expected to fails when we reach EOT.
542 fn debug_scan(param
: Value
) -> Result
<(), Error
> {
544 let (config
, _digest
) = config
::drive
::config()?
;
546 let drive
= lookup_drive_name(¶m
, &config
)?
;
547 let mut drive
= open_drive(&config
, &drive
)?
;
549 println
!("rewinding tape");
553 let file_number
= drive
.current_file_number()?
;
555 match drive
.read_next_file()?
{
560 Some(mut reader
) => {
561 println
!("got file number {}", file_number
);
563 let header
: Result
<MediaContentHeader
, _
> = unsafe { reader.read_le_value() }
;
566 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
567 println
!("got MediaContentHeader with wrong magic: {:?}", header
.magic
);
568 } else if let Some(name
) = PROXMOX_BACKUP_CONTENT_NAME
.get(&header
.content_magic
) {
569 println
!("got content header: {}", name
);
570 println
!(" uuid: {}", header
.content_uuid());
571 println
!(" ctime: {}", strftime_local("%c", header
.ctime
)?
);
572 println
!(" hsize: {}", HumanByte
::from(header
.size
as usize));
573 println
!(" part: {}", header
.part_number
);
575 println
!("got unknown content header: {:?}", header
.content_magic
);
579 println
!("unable to read content header - {}", err
);
582 let bytes
= reader
.skip_to_end()?
;
583 println
!("skipped {}", HumanByte
::from(bytes
));
593 schema
: DRIVE_NAME_SCHEMA
,
597 schema
: OUTPUT_FORMAT
,
603 /// Read Cartridge Memory (Medium auxiliary memory attributes)
604 async
fn cartridge_memory(param
: Value
) -> Result
<(), Error
> {
606 let output_format
= get_output_format(¶m
);
608 let (config
, _digest
) = config
::drive
::config()?
;
610 let drive
= lookup_drive_name(¶m
, &config
)?
;
612 let client
= connect_to_localhost()?
;
614 let path
= format
!("api2/json/tape/drive/{}/cartridge-memory", drive
);
615 let mut result
= client
.get(&path
, Some(param
)).await?
;
616 let mut data
= result
["data"].take();
618 let info
= &api2
::tape
::drive
::API_METHOD_CARTRIDGE_MEMORY
;
620 let options
= default_table_format_options()
621 .column(ColumnConfig
::new("id"))
622 .column(ColumnConfig
::new("name"))
623 .column(ColumnConfig
::new("value"))
626 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
634 schema
: DRIVE_NAME_SCHEMA
,
638 schema
: OUTPUT_FORMAT
,
644 /// Read Volume Statistics (SCSI log page 17h)
645 async
fn volume_statistics(param
: Value
) -> Result
<(), Error
> {
647 let output_format
= get_output_format(¶m
);
649 let (config
, _digest
) = config
::drive
::config()?
;
651 let drive
= lookup_drive_name(¶m
, &config
)?
;
653 let client
= connect_to_localhost()?
;
655 let path
= format
!("api2/json/tape/drive/{}/volume-statistics", drive
);
656 let mut result
= client
.get(&path
, Some(param
)).await?
;
657 let mut data
= result
["data"].take();
659 let info
= &api2
::tape
::drive
::API_METHOD_VOLUME_STATISTICS
;
661 let options
= default_table_format_options();
663 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
672 schema
: DRIVE_NAME_SCHEMA
,
676 schema
: OUTPUT_FORMAT
,
682 /// Get drive/media status
683 async
fn status(param
: Value
) -> Result
<(), Error
> {
685 let output_format
= get_output_format(¶m
);
687 let (config
, _digest
) = config
::drive
::config()?
;
689 let drive
= lookup_drive_name(¶m
, &config
)?
;
691 let client
= connect_to_localhost()?
;
693 let path
= format
!("api2/json/tape/drive/{}/status", drive
);
694 let mut result
= client
.get(&path
, Some(param
)).await?
;
695 let mut data
= result
["data"].take();
697 let info
= &api2
::tape
::drive
::API_METHOD_STATUS
;
699 let render_percentage
= |value
: &Value
, _record
: &Value
| {
700 match value
.as_f64() {
701 Some(wearout
) => Ok(format
!("{:.2}%", wearout
*100.0)),
702 None
=> Ok(String
::from("ERROR")), // should never happen
706 let options
= default_table_format_options()
707 .column(ColumnConfig
::new("blocksize"))
708 .column(ColumnConfig
::new("density"))
709 .column(ColumnConfig
::new("status"))
710 .column(ColumnConfig
::new("alert-flags"))
711 .column(ColumnConfig
::new("file-number"))
712 .column(ColumnConfig
::new("block-number"))
713 .column(ColumnConfig
::new("manufactured").renderer(render_epoch
))
714 .column(ColumnConfig
::new("bytes-written").renderer(render_bytes_human_readable
))
715 .column(ColumnConfig
::new("bytes-read").renderer(render_bytes_human_readable
))
716 .column(ColumnConfig
::new("medium-passes"))
717 .column(ColumnConfig
::new("medium-wearout").renderer(render_percentage
))
718 .column(ColumnConfig
::new("volume-mounts"))
721 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
730 schema
: DRIVE_NAME_SCHEMA
,
734 schema
: OUTPUT_FORMAT
,
741 async
fn clean_drive(param
: Value
) -> Result
<(), Error
> {
743 let output_format
= get_output_format(¶m
);
745 let (config
, _digest
) = config
::drive
::config()?
;
747 let drive
= lookup_drive_name(¶m
, &config
)?
;
749 let mut client
= connect_to_localhost()?
;
751 let path
= format
!("api2/json/tape/drive/{}/clean-drive", drive
);
752 let result
= client
.post(&path
, Some(param
)).await?
;
754 view_task_result(&mut client
, result
, &output_format
).await?
;
763 schema
: DATASTORE_SCHEMA
,
766 schema
: MEDIA_POOL_NAME_SCHEMA
,
769 schema
: DRIVE_NAME_SCHEMA
,
773 description
: "Eject media upon job completion.",
777 "export-media-set": {
778 description
: "Export media set upon job completion.",
783 schema
: OUTPUT_FORMAT
,
789 /// Backup datastore to tape media pool
790 async
fn backup(mut param
: Value
) -> Result
<(), Error
> {
792 let output_format
= get_output_format(¶m
);
794 let (config
, _digest
) = config
::drive
::config()?
;
796 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
798 let mut client
= connect_to_localhost()?
;
800 let result
= client
.post("api2/json/tape/backup", Some(param
)).await?
;
802 view_task_result(&mut client
, result
, &output_format
).await?
;
811 schema
: DATASTORE_SCHEMA
,
814 schema
: DRIVE_NAME_SCHEMA
,
818 description
: "Media set UUID.",
822 schema
: OUTPUT_FORMAT
,
828 /// Restore data from media-set
829 async
fn restore(mut param
: Value
) -> Result
<(), Error
> {
831 let output_format
= get_output_format(¶m
);
833 let (config
, _digest
) = config
::drive
::config()?
;
835 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
837 let mut client
= connect_to_localhost()?
;
839 let result
= client
.post("api2/json/tape/restore", Some(param
)).await?
;
841 view_task_result(&mut client
, result
, &output_format
).await?
;
850 schema
: DRIVE_NAME_SCHEMA
,
854 description
: "Force overriding existing index.",
859 description
: "Verbose mode - log all found chunks.",
864 schema
: OUTPUT_FORMAT
,
870 /// Scan media and record content
871 async
fn catalog_media(param
: Value
) -> Result
<(), Error
> {
873 let output_format
= get_output_format(¶m
);
875 let (config
, _digest
) = config
::drive
::config()?
;
877 let drive
= lookup_drive_name(¶m
, &config
)?
;
879 let mut client
= connect_to_localhost()?
;
881 let path
= format
!("api2/json/tape/drive/{}/catalog", drive
);
882 let result
= client
.post(&path
, Some(param
)).await?
;
884 view_task_result(&mut client
, result
, &output_format
).await?
;
891 let cmd_def
= CliCommandMap
::new()
894 CliCommand
::new(&API_METHOD_BACKUP
)
895 .arg_param(&["store", "pool"])
896 .completion_cb("store", complete_datastore_name
)
897 .completion_cb("pool", complete_pool_name
)
901 CliCommand
::new(&API_METHOD_RESTORE
)
902 .arg_param(&["media-set", "store"])
903 .completion_cb("store", complete_datastore_name
)
904 .completion_cb("media-set", complete_media_set_uuid
)
908 CliCommand
::new(&API_METHOD_BARCODE_LABEL_MEDIA
)
909 .completion_cb("drive", complete_drive_name
)
910 .completion_cb("pool", complete_pool_name
)
914 CliCommand
::new(&API_METHOD_REWIND
)
915 .completion_cb("drive", complete_drive_name
)
919 CliCommand
::new(&API_METHOD_DEBUG_SCAN
)
920 .completion_cb("drive", complete_drive_name
)
924 CliCommand
::new(&API_METHOD_STATUS
)
925 .completion_cb("drive", complete_drive_name
)
929 CliCommand
::new(&API_METHOD_MOVE_TO_EOM
)
930 .completion_cb("drive", complete_drive_name
)
934 CliCommand
::new(&API_METHOD_ERASE_MEDIA
)
935 .completion_cb("drive", complete_drive_name
)
939 CliCommand
::new(&API_METHOD_EJECT_MEDIA
)
940 .completion_cb("drive", complete_drive_name
)
944 CliCommand
::new(&API_METHOD_INVENTORY
)
945 .completion_cb("drive", complete_drive_name
)
949 CliCommand
::new(&API_METHOD_READ_LABEL
)
950 .completion_cb("drive", complete_drive_name
)
954 CliCommand
::new(&API_METHOD_CATALOG_MEDIA
)
955 .completion_cb("drive", complete_drive_name
)
959 CliCommand
::new(&API_METHOD_CARTRIDGE_MEMORY
)
960 .completion_cb("drive", complete_drive_name
)
964 CliCommand
::new(&API_METHOD_VOLUME_STATISTICS
)
965 .completion_cb("drive", complete_drive_name
)
969 CliCommand
::new(&API_METHOD_CLEAN_DRIVE
)
970 .completion_cb("drive", complete_drive_name
)
974 CliCommand
::new(&API_METHOD_LABEL_MEDIA
)
975 .completion_cb("drive", complete_drive_name
)
976 .completion_cb("pool", complete_pool_name
)
979 .insert("changer", changer_commands())
980 .insert("drive", drive_commands())
981 .insert("pool", pool_commands())
982 .insert("media", media_commands())
983 .insert("key", encryption_key_commands())
986 CliCommand
::new(&API_METHOD_LOAD_MEDIA
)
987 .arg_param(&["label-text"])
988 .completion_cb("drive", complete_drive_name
)
989 .completion_cb("label-text", complete_media_label_text
)
992 "load-media-from-slot",
993 CliCommand
::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT
)
994 .arg_param(&["source-slot"])
995 .completion_cb("drive", complete_drive_name
)
999 CliCommand
::new(&API_METHOD_UNLOAD_MEDIA
)
1000 .completion_cb("drive", complete_drive_name
)
1004 CliCommand
::new(&API_METHOD_EXPORT_MEDIA
)
1005 .arg_param(&["label-text"])
1006 .completion_cb("drive", complete_drive_name
)
1007 .completion_cb("label-text", complete_media_label_text
)
1011 let mut rpcenv
= CliEnvironment
::new();
1012 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
1014 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));