1 use std
::collections
::HashMap
;
3 use anyhow
::{bail, format_err, Error}
;
4 use serde_json
::{json, Value}
;
6 use proxmox_io
::ReadExt
;
7 use proxmox_router
::RpcEnvironment
;
8 use proxmox_router
::cli
::*;
9 use proxmox_schema
::api
;
10 use proxmox_section_config
::SectionConfigData
;
11 use proxmox_time
::strftime_local
;
13 use pbs_client
::view_task_result
;
14 use pbs_tools
::format
::{
17 render_bytes_human_readable
,
20 use pbs_config
::drive
::complete_drive_name
;
21 use pbs_config
::media_pool
::complete_pool_name
;
22 use pbs_config
::datastore
::complete_datastore_name
;
25 Userid
, Authid
, DATASTORE_SCHEMA
, DATASTORE_MAP_LIST_SCHEMA
,
26 DRIVE_NAME_SCHEMA
, MEDIA_LABEL_SCHEMA
, MEDIA_POOL_NAME_SCHEMA
,
27 TAPE_RESTORE_SNAPSHOT_SCHEMA
, GROUP_FILTER_LIST_SCHEMA
, GroupListItem
,
30 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
, BlockReadError
, MediaContentHeader
,
39 set_tape_device_state
,
41 complete_media_label_text
,
42 complete_media_set_uuid
,
43 complete_media_set_snapshots
,
45 proxmox_tape_magic_to_text
,
48 client_helpers
::connect_to_localhost
,
54 async
fn get_backup_groups(store
: &str) -> Result
<Vec
<GroupListItem
>, Error
> {
55 let client
= connect_to_localhost()?
;
57 .get(&format
!("api2/json/admin/datastore/{}/groups", store
), None
)
60 match api_res
.get("data") {
61 Some(data
) => Ok(serde_json
::from_value
::<Vec
<GroupListItem
>>(data
.to_owned())?
),
62 None
=> bail
!("could not get group list"),
66 // shell completion helper
67 pub fn complete_datastore_group_filter(_arg
: &str, param
: &HashMap
<String
, String
>) -> Vec
<String
> {
69 let mut list
= Vec
::new();
71 list
.push("regex:".to_string());
72 list
.push("type:ct".to_string());
73 list
.push("type:host".to_string());
74 list
.push("type:vm".to_string());
76 if let Some(store
) = param
.get("store") {
77 let groups
= pbs_runtime
::block_on(async { get_backup_groups(store).await }
);
78 if let Ok(groups
) = groups
{
79 list
.extend(groups
.iter().map(|group
| format
!("group:{}/{}", group
.backup_type
, group
.backup_id
)));
86 pub fn extract_drive_name(
88 config
: &SectionConfigData
,
89 ) -> Result
<String
, Error
> {
91 let drive
= param
["drive"]
94 .or_else(|| std
::env
::var("PROXMOX_TAPE_DRIVE").ok())
97 let mut drive_names
= Vec
::new();
99 for (name
, (section_type
, _
)) in config
.sections
.iter() {
101 if !(section_type
== "linux" || section_type
== "virtual") { continue; }
102 drive_names
.push(name
);
105 if drive_names
.len() == 1 {
106 Some(drive_names
[0].to_owned())
111 .ok_or_else(|| format_err
!("unable to get (default) drive name"))?
;
113 if let Some(map
) = param
.as_object_mut() {
124 schema
: DRIVE_NAME_SCHEMA
,
128 description
: "Use fast erase.",
134 schema
: OUTPUT_FORMAT
,
141 async
fn format_media(mut param
: Value
) -> Result
<(), Error
> {
143 let output_format
= extract_output_format(&mut param
);
145 let (config
, _digest
) = pbs_config
::drive
::config()?
;
147 let drive
= extract_drive_name(&mut param
, &config
)?
;
149 let mut client
= connect_to_localhost()?
;
151 let path
= format
!("api2/json/tape/drive/{}/format-media", 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
,
174 async
fn rewind(mut param
: Value
) -> Result
<(), Error
> {
176 let output_format
= extract_output_format(&mut param
);
178 let (config
, _digest
) = pbs_config
::drive
::config()?
;
180 let drive
= extract_drive_name(&mut param
, &config
)?
;
182 let mut client
= connect_to_localhost()?
;
184 let path
= format
!("api2/json/tape/drive/{}/rewind", 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
: OUTPUT_FORMAT
,
206 /// Eject/Unload drive media
207 async
fn eject_media(mut param
: Value
) -> Result
<(), Error
> {
209 let output_format
= extract_output_format(&mut param
);
211 let (config
, _digest
) = pbs_config
::drive
::config()?
;
213 let drive
= extract_drive_name(&mut param
, &config
)?
;
215 let mut client
= connect_to_localhost()?
;
217 let path
= format
!("api2/json/tape/drive/{}/eject-media", drive
);
218 let result
= client
.post(&path
, Some(param
)).await?
;
220 view_task_result(&mut client
, result
, &output_format
).await?
;
229 schema
: DRIVE_NAME_SCHEMA
,
233 schema
: MEDIA_LABEL_SCHEMA
,
236 schema
: OUTPUT_FORMAT
,
242 /// Load media with specified label
243 async
fn load_media(mut param
: Value
) -> Result
<(), Error
> {
245 let output_format
= extract_output_format(&mut param
);
247 let (config
, _digest
) = pbs_config
::drive
::config()?
;
249 let drive
= extract_drive_name(&mut param
, &config
)?
;
251 let mut client
= connect_to_localhost()?
;
253 let path
= format
!("api2/json/tape/drive/{}/load-media", drive
);
254 let result
= client
.post(&path
, Some(param
)).await?
;
256 view_task_result(&mut client
, result
, &output_format
).await?
;
265 schema
: DRIVE_NAME_SCHEMA
,
269 schema
: MEDIA_LABEL_SCHEMA
,
274 /// Export media with specified label
275 async
fn export_media(mut param
: Value
) -> Result
<(), Error
> {
277 let (config
, _digest
) = pbs_config
::drive
::config()?
;
279 let drive
= extract_drive_name(&mut param
, &config
)?
;
281 let mut client
= connect_to_localhost()?
;
283 let path
= format
!("api2/json/tape/drive/{}/export-media", drive
);
284 client
.put(&path
, Some(param
)).await?
;
293 schema
: DRIVE_NAME_SCHEMA
,
297 description
: "Source slot number.",
304 /// Load media from the specified slot
305 async
fn load_media_from_slot(mut param
: Value
) -> Result
<(), Error
> {
307 let (config
, _digest
) = pbs_config
::drive
::config()?
;
309 let drive
= extract_drive_name(&mut param
, &config
)?
;
311 let mut client
= connect_to_localhost()?
;
313 let path
= format
!("api2/json/tape/drive/{}/load-slot", drive
);
314 client
.put(&path
, Some(param
)).await?
;
323 schema
: DRIVE_NAME_SCHEMA
,
327 description
: "Target slot number. If omitted, defaults to the slot that the drive was loaded from.",
333 schema
: OUTPUT_FORMAT
,
339 /// Unload media via changer
340 async
fn unload_media(mut param
: Value
) -> Result
<(), Error
> {
342 let output_format
= extract_output_format(&mut param
);
344 let (config
, _digest
) = pbs_config
::drive
::config()?
;
346 let drive
= extract_drive_name(&mut param
, &config
)?
;
348 let mut client
= connect_to_localhost()?
;
350 let path
= format
!("api2/json/tape/drive/{}/unload", drive
);
351 let result
= client
.post(&path
, Some(param
)).await?
;
353 view_task_result(&mut client
, result
, &output_format
).await?
;
362 schema
: MEDIA_POOL_NAME_SCHEMA
,
366 schema
: DRIVE_NAME_SCHEMA
,
370 schema
: MEDIA_LABEL_SCHEMA
,
373 schema
: OUTPUT_FORMAT
,
380 async
fn label_media(mut param
: Value
) -> Result
<(), Error
> {
382 let output_format
= extract_output_format(&mut param
);
384 let (config
, _digest
) = pbs_config
::drive
::config()?
;
386 let drive
= extract_drive_name(&mut param
, &config
)?
;
388 let mut client
= connect_to_localhost()?
;
390 let path
= format
!("api2/json/tape/drive/{}/label-media", drive
);
391 let result
= client
.post(&path
, Some(param
)).await?
;
393 view_task_result(&mut client
, result
, &output_format
).await?
;
402 schema
: DRIVE_NAME_SCHEMA
,
406 description
: "Inventorize media",
411 schema
: OUTPUT_FORMAT
,
418 async
fn read_label(mut param
: Value
) -> Result
<(), Error
> {
420 let output_format
= extract_output_format(&mut param
);
422 let (config
, _digest
) = pbs_config
::drive
::config()?
;
424 let drive
= extract_drive_name(&mut param
, &config
)?
;
426 let client
= connect_to_localhost()?
;
428 let path
= format
!("api2/json/tape/drive/{}/read-label", drive
);
429 let mut result
= client
.get(&path
, Some(param
)).await?
;
430 let mut data
= result
["data"].take();
432 let info
= &api2
::tape
::drive
::API_METHOD_READ_LABEL
;
434 let options
= default_table_format_options()
435 .column(ColumnConfig
::new("label-text"))
436 .column(ColumnConfig
::new("uuid"))
437 .column(ColumnConfig
::new("ctime").renderer(render_epoch
))
438 .column(ColumnConfig
::new("pool"))
439 .column(ColumnConfig
::new("media-set-uuid"))
440 .column(ColumnConfig
::new("media-set-ctime").renderer(render_epoch
))
441 .column(ColumnConfig
::new("encryption-key-fingerprint"))
444 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
453 schema
: OUTPUT_FORMAT
,
457 schema
: DRIVE_NAME_SCHEMA
,
461 description
: "Load unknown tapes and try read labels",
466 description
: "Load all tapes and try read labels (even if already inventoried)",
473 /// List (and update) media labels (Changer Inventory)
475 read_labels
: Option
<bool
>,
476 read_all_labels
: Option
<bool
>,
478 ) -> Result
<(), Error
> {
480 let output_format
= extract_output_format(&mut param
);
482 let (config
, _digest
) = pbs_config
::drive
::config()?
;
483 let drive
= extract_drive_name(&mut param
, &config
)?
;
485 let do_read
= read_labels
.unwrap_or(false) || read_all_labels
.unwrap_or(false);
487 let mut client
= connect_to_localhost()?
;
489 let path
= format
!("api2/json/tape/drive/{}/inventory", drive
);
493 let mut param
= json
!({}
);
494 if let Some(true) = read_all_labels
{
495 param
["read-all-labels"] = true.into();
498 let result
= client
.put(&path
, Some(param
)).await?
; // update inventory
499 view_task_result(&mut client
, result
, &output_format
).await?
;
502 let mut result
= client
.get(&path
, None
).await?
;
503 let mut data
= result
["data"].take();
505 let info
= &api2
::tape
::drive
::API_METHOD_INVENTORY
;
507 let options
= default_table_format_options()
508 .column(ColumnConfig
::new("label-text"))
509 .column(ColumnConfig
::new("uuid"))
512 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
521 schema
: MEDIA_POOL_NAME_SCHEMA
,
525 schema
: DRIVE_NAME_SCHEMA
,
529 schema
: OUTPUT_FORMAT
,
535 /// Label media with barcodes from changer device
536 async
fn barcode_label_media(mut param
: Value
) -> Result
<(), Error
> {
538 let output_format
= extract_output_format(&mut param
);
540 let (config
, _digest
) = pbs_config
::drive
::config()?
;
542 let drive
= extract_drive_name(&mut param
, &config
)?
;
544 let mut client
= connect_to_localhost()?
;
546 let path
= format
!("api2/json/tape/drive/{}/barcode-label-media", drive
);
547 let result
= client
.post(&path
, Some(param
)).await?
;
549 view_task_result(&mut client
, result
, &output_format
).await?
;
558 schema
: DRIVE_NAME_SCHEMA
,
564 /// Move to end of media (MTEOM, used to debug)
565 fn move_to_eom(mut param
: Value
) -> Result
<(), Error
> {
567 let (config
, _digest
) = pbs_config
::drive
::config()?
;
569 let drive
= extract_drive_name(&mut param
, &config
)?
;
571 let _lock
= lock_tape_device(&config
, &drive
)?
;
572 set_tape_device_state(&drive
, "moving to eom")?
;
574 let mut drive
= open_drive(&config
, &drive
)?
;
576 drive
.move_to_eom(false)?
;
585 schema
: DRIVE_NAME_SCHEMA
,
591 /// Rewind, then read media contents and print debug info
593 /// Note: This reads unless the driver returns an IO Error, so this
594 /// method is expected to fails when we reach EOT.
595 fn debug_scan(mut param
: Value
) -> Result
<(), Error
> {
597 let (config
, _digest
) = pbs_config
::drive
::config()?
;
599 let drive
= extract_drive_name(&mut param
, &config
)?
;
601 let _lock
= lock_tape_device(&config
, &drive
)?
;
602 set_tape_device_state(&drive
, "debug scan")?
;
604 let mut drive
= open_drive(&config
, &drive
)?
;
606 println
!("rewinding tape");
610 let file_number
= drive
.current_file_number()?
;
612 match drive
.read_next_file() {
613 Err(BlockReadError
::EndOfFile
) => {
614 println
!("filemark number {}", file_number
);
617 Err(BlockReadError
::EndOfStream
) => {
621 Err(BlockReadError
::Error(err
)) => {
622 return Err(err
.into());
625 println
!("got file number {}", file_number
);
627 let header
: Result
<MediaContentHeader
, _
> = unsafe { reader.read_le_value() }
;
630 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
631 println
!("got MediaContentHeader with wrong magic: {:?}", header
.magic
);
632 } else if let Some(name
) = proxmox_tape_magic_to_text(&header
.content_magic
) {
633 println
!("got content header: {}", name
);
634 println
!(" uuid: {}", header
.content_uuid());
635 println
!(" ctime: {}", strftime_local("%c", header
.ctime
)?
);
636 println
!(" hsize: {}", HumanByte
::from(header
.size
as usize));
637 println
!(" part: {}", header
.part_number
);
639 println
!("got unknown content header: {:?}", header
.content_magic
);
643 println
!("unable to read content header - {}", err
);
646 let bytes
= reader
.skip_data()?
;
647 println
!("skipped {}", HumanByte
::from(bytes
));
648 if let Ok(true) = reader
.has_end_marker() {
649 if reader
.is_incomplete()?
{
650 println
!("WARNING: file is incomplete");
653 println
!("WARNING: file without end marker");
664 schema
: DRIVE_NAME_SCHEMA
,
668 schema
: OUTPUT_FORMAT
,
674 /// Read Cartridge Memory (Medium auxiliary memory attributes)
675 async
fn cartridge_memory(mut param
: Value
) -> Result
<(), Error
> {
677 let output_format
= extract_output_format(&mut param
);
679 let (config
, _digest
) = pbs_config
::drive
::config()?
;
681 let drive
= extract_drive_name(&mut param
, &config
)?
;
683 let client
= connect_to_localhost()?
;
685 let path
= format
!("api2/json/tape/drive/{}/cartridge-memory", drive
);
686 let mut result
= client
.get(&path
, Some(param
)).await?
;
687 let mut data
= result
["data"].take();
689 let info
= &api2
::tape
::drive
::API_METHOD_CARTRIDGE_MEMORY
;
691 let options
= default_table_format_options()
692 .column(ColumnConfig
::new("id"))
693 .column(ColumnConfig
::new("name"))
694 .column(ColumnConfig
::new("value"))
697 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
705 schema
: DRIVE_NAME_SCHEMA
,
709 schema
: OUTPUT_FORMAT
,
715 /// Read Volume Statistics (SCSI log page 17h)
716 async
fn volume_statistics(mut param
: Value
) -> Result
<(), Error
> {
718 let output_format
= extract_output_format(&mut param
);
720 let (config
, _digest
) = pbs_config
::drive
::config()?
;
722 let drive
= extract_drive_name(&mut param
, &config
)?
;
724 let client
= connect_to_localhost()?
;
726 let path
= format
!("api2/json/tape/drive/{}/volume-statistics", drive
);
727 let mut result
= client
.get(&path
, Some(param
)).await?
;
728 let mut data
= result
["data"].take();
730 let info
= &api2
::tape
::drive
::API_METHOD_VOLUME_STATISTICS
;
732 let options
= default_table_format_options();
734 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
743 schema
: DRIVE_NAME_SCHEMA
,
747 schema
: OUTPUT_FORMAT
,
753 /// Get drive/media status
754 async
fn status(mut param
: Value
) -> Result
<(), Error
> {
756 let output_format
= extract_output_format(&mut param
);
758 let (config
, _digest
) = pbs_config
::drive
::config()?
;
760 let drive
= extract_drive_name(&mut param
, &config
)?
;
762 let client
= connect_to_localhost()?
;
764 let path
= format
!("api2/json/tape/drive/{}/status", drive
);
765 let mut result
= client
.get(&path
, Some(param
)).await?
;
766 let mut data
= result
["data"].take();
768 let info
= &api2
::tape
::drive
::API_METHOD_STATUS
;
770 let render_percentage
= |value
: &Value
, _record
: &Value
| {
771 match value
.as_f64() {
772 Some(wearout
) => Ok(format
!("{:.2}%", wearout
*100.0)),
773 None
=> Ok(String
::from("ERROR")), // should never happen
777 let options
= default_table_format_options()
778 .column(ColumnConfig
::new("blocksize"))
779 .column(ColumnConfig
::new("density"))
780 .column(ColumnConfig
::new("compression"))
781 .column(ColumnConfig
::new("buffer-mode"))
782 .column(ColumnConfig
::new("write-protect"))
783 .column(ColumnConfig
::new("alert-flags"))
784 .column(ColumnConfig
::new("file-number"))
785 .column(ColumnConfig
::new("block-number"))
786 .column(ColumnConfig
::new("manufactured").renderer(render_epoch
))
787 .column(ColumnConfig
::new("bytes-written").renderer(render_bytes_human_readable
))
788 .column(ColumnConfig
::new("bytes-read").renderer(render_bytes_human_readable
))
789 .column(ColumnConfig
::new("medium-passes"))
790 .column(ColumnConfig
::new("medium-wearout").renderer(render_percentage
))
791 .column(ColumnConfig
::new("volume-mounts"))
794 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
803 schema
: DRIVE_NAME_SCHEMA
,
807 schema
: OUTPUT_FORMAT
,
814 async
fn clean_drive(mut param
: Value
) -> Result
<(), Error
> {
816 let output_format
= extract_output_format(&mut param
);
818 let (config
, _digest
) = pbs_config
::drive
::config()?
;
820 let drive
= extract_drive_name(&mut param
, &config
)?
;
822 let mut client
= connect_to_localhost()?
;
824 let path
= format
!("api2/json/tape/drive/{}/clean", drive
);
825 let result
= client
.put(&path
, Some(param
)).await?
;
827 view_task_result(&mut client
, result
, &output_format
).await?
;
836 // Note: We cannot use TapeBackupJobSetup, because drive needs to be optional here
838 // type: TapeBackupJobSetup,
843 schema
: DATASTORE_SCHEMA
,
846 schema
: MEDIA_POOL_NAME_SCHEMA
,
849 schema
: DRIVE_NAME_SCHEMA
,
853 description
: "Eject media upon job completion.",
857 "export-media-set": {
858 description
: "Export media set upon job completion.",
863 description
: "Backup latest snapshots only.",
872 schema
: GROUP_FILTER_LIST_SCHEMA
,
876 description
: "Ignore the allocation policy and start a new media-set.",
882 schema
: OUTPUT_FORMAT
,
888 /// Backup datastore to tape media pool
889 async
fn backup(mut param
: Value
) -> Result
<(), Error
> {
891 let output_format
= extract_output_format(&mut param
);
893 let (config
, _digest
) = pbs_config
::drive
::config()?
;
895 param
["drive"] = extract_drive_name(&mut param
, &config
)?
.into();
897 let mut client
= connect_to_localhost()?
;
899 let result
= client
.post("api2/json/tape/backup", Some(param
)).await?
;
901 view_task_result(&mut client
, result
, &output_format
).await?
;
910 schema
: DATASTORE_MAP_LIST_SCHEMA
,
913 schema
: DRIVE_NAME_SCHEMA
,
917 description
: "Media set UUID.",
925 description
: "List of snapshots.",
929 schema
: TAPE_RESTORE_SNAPSHOT_SCHEMA
,
937 schema
: OUTPUT_FORMAT
,
943 /// Restore data from media-set
944 async
fn restore(mut param
: Value
) -> Result
<(), Error
> {
946 let output_format
= extract_output_format(&mut param
);
948 let (config
, _digest
) = pbs_config
::drive
::config()?
;
950 param
["drive"] = extract_drive_name(&mut param
, &config
)?
.into();
952 let mut client
= connect_to_localhost()?
;
954 let result
= client
.post("api2/json/tape/restore", Some(param
)).await?
;
956 view_task_result(&mut client
, result
, &output_format
).await?
;
965 schema
: DRIVE_NAME_SCHEMA
,
969 description
: "Force overriding existing index.",
974 description
: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
979 description
: "Verbose mode - log all found chunks.",
984 schema
: OUTPUT_FORMAT
,
990 /// Scan media and record content
991 async
fn catalog_media(mut param
: Value
) -> Result
<(), Error
> {
993 let output_format
= extract_output_format(&mut param
);
995 let (config
, _digest
) = pbs_config
::drive
::config()?
;
997 let drive
= extract_drive_name(&mut param
, &config
)?
;
999 let mut client
= connect_to_localhost()?
;
1001 let path
= format
!("api2/json/tape/drive/{}/catalog", drive
);
1002 let result
= client
.post(&path
, Some(param
)).await?
;
1004 view_task_result(&mut client
, result
, &output_format
).await?
;
1011 let cmd_def
= CliCommandMap
::new()
1014 CliCommand
::new(&API_METHOD_BACKUP
)
1015 .arg_param(&["store", "pool"])
1016 .completion_cb("drive", complete_drive_name
)
1017 .completion_cb("store", complete_datastore_name
)
1018 .completion_cb("pool", complete_pool_name
)
1019 .completion_cb("groups", complete_datastore_group_filter
)
1023 CliCommand
::new(&API_METHOD_RESTORE
)
1024 .arg_param(&["media-set", "store", "snapshots"])
1025 .completion_cb("store", complete_datastore_name
)
1026 .completion_cb("media-set", complete_media_set_uuid
)
1027 .completion_cb("snapshots", complete_media_set_snapshots
)
1031 CliCommand
::new(&API_METHOD_BARCODE_LABEL_MEDIA
)
1032 .completion_cb("drive", complete_drive_name
)
1033 .completion_cb("pool", complete_pool_name
)
1037 CliCommand
::new(&API_METHOD_REWIND
)
1038 .completion_cb("drive", complete_drive_name
)
1042 CliCommand
::new(&API_METHOD_DEBUG_SCAN
)
1043 .completion_cb("drive", complete_drive_name
)
1047 CliCommand
::new(&API_METHOD_STATUS
)
1048 .completion_cb("drive", complete_drive_name
)
1052 CliCommand
::new(&API_METHOD_MOVE_TO_EOM
)
1053 .completion_cb("drive", complete_drive_name
)
1057 CliCommand
::new(&API_METHOD_FORMAT_MEDIA
)
1058 .completion_cb("drive", complete_drive_name
)
1062 CliCommand
::new(&API_METHOD_EJECT_MEDIA
)
1063 .completion_cb("drive", complete_drive_name
)
1067 CliCommand
::new(&API_METHOD_INVENTORY
)
1068 .completion_cb("drive", complete_drive_name
)
1072 CliCommand
::new(&API_METHOD_READ_LABEL
)
1073 .completion_cb("drive", complete_drive_name
)
1077 CliCommand
::new(&API_METHOD_CATALOG_MEDIA
)
1078 .completion_cb("drive", complete_drive_name
)
1082 CliCommand
::new(&API_METHOD_CARTRIDGE_MEMORY
)
1083 .completion_cb("drive", complete_drive_name
)
1086 "volume-statistics",
1087 CliCommand
::new(&API_METHOD_VOLUME_STATISTICS
)
1088 .completion_cb("drive", complete_drive_name
)
1092 CliCommand
::new(&API_METHOD_CLEAN_DRIVE
)
1093 .completion_cb("drive", complete_drive_name
)
1097 CliCommand
::new(&API_METHOD_LABEL_MEDIA
)
1098 .completion_cb("drive", complete_drive_name
)
1099 .completion_cb("pool", complete_pool_name
)
1102 .insert("changer", changer_commands())
1103 .insert("drive", drive_commands())
1104 .insert("pool", pool_commands())
1105 .insert("media", media_commands())
1106 .insert("key", encryption_key_commands())
1107 .insert("backup-job", backup_job_commands())
1110 CliCommand
::new(&API_METHOD_LOAD_MEDIA
)
1111 .arg_param(&["label-text"])
1112 .completion_cb("drive", complete_drive_name
)
1113 .completion_cb("label-text", complete_media_label_text
)
1116 "load-media-from-slot",
1117 CliCommand
::new(&API_METHOD_LOAD_MEDIA_FROM_SLOT
)
1118 .arg_param(&["source-slot"])
1119 .completion_cb("drive", complete_drive_name
)
1123 CliCommand
::new(&API_METHOD_UNLOAD_MEDIA
)
1124 .completion_cb("drive", complete_drive_name
)
1128 CliCommand
::new(&API_METHOD_EXPORT_MEDIA
)
1129 .arg_param(&["label-text"])
1130 .completion_cb("drive", complete_drive_name
)
1131 .completion_cb("label-text", complete_media_label_text
)
1135 let mut rpcenv
= CliEnvironment
::new();
1136 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
1138 pbs_runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));