1 use anyhow
::{format_err, Error}
;
2 use serde_json
::{json, Value}
;
10 section_config
::SectionConfigData
,
22 render_bytes_human_readable
,
26 worker_is_active_local
,
34 MEDIA_POOL_NAME_SCHEMA
,
39 datastore
::complete_datastore_name
,
40 drive
::complete_drive_name
,
41 media_pool
::complete_pool_name
,
45 complete_media_changer_id
,
46 complete_media_set_uuid
,
48 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
49 PROXMOX_BACKUP_CONTENT_NAME
,
58 // Note: local workers should print logs to stdout, so there is no need
59 // to fetch/display logs. We just wait for the worker to finish.
60 pub async
fn wait_for_local_worker(upid_str
: &str) -> Result
<(), Error
> {
62 let upid
: UPID
= upid_str
.parse()?
;
64 let sleep_duration
= core
::time
::Duration
::new(0, 100_000_000);
67 if worker_is_active_local(&upid
) {
68 tokio
::time
::delay_for(sleep_duration
).await
;
78 config
: &SectionConfigData
,
79 ) -> Result
<String
, Error
> {
81 let drive
= param
["drive"]
84 .or_else(|| std
::env
::var("PROXMOX_TAPE_DRIVE").ok())
87 let mut drive_names
= Vec
::new();
89 for (name
, (section_type
, _
)) in config
.sections
.iter() {
91 if !(section_type
== "linux" || section_type
== "virtual") { continue; }
92 drive_names
.push(name
);
95 if drive_names
.len() == 1 {
96 Some(drive_names
[0].to_owned())
101 .ok_or_else(|| format_err
!("unable to get (default) drive name"))?
;
110 schema
: DRIVE_NAME_SCHEMA
,
114 description
: "Use fast erase.",
123 async
fn erase_media(
125 rpcenv
: &mut dyn RpcEnvironment
,
126 ) -> Result
<(), Error
> {
128 let (config
, _digest
) = config
::drive
::config()?
;
130 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
132 let info
= &api2
::tape
::drive
::API_METHOD_ERASE_MEDIA
;
134 let result
= match info
.handler
{
135 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
139 wait_for_local_worker(result
.as_str().unwrap()).await?
;
148 schema
: DRIVE_NAME_SCHEMA
,
157 rpcenv
: &mut dyn RpcEnvironment
,
158 ) -> Result
<(), Error
> {
160 let (config
, _digest
) = config
::drive
::config()?
;
162 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
164 let info
= &api2
::tape
::drive
::API_METHOD_REWIND
;
166 let result
= match info
.handler
{
167 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
171 wait_for_local_worker(result
.as_str().unwrap()).await?
;
180 schema
: DRIVE_NAME_SCHEMA
,
186 /// Eject/Unload drive media
187 async
fn eject_media(
189 rpcenv
: &mut dyn RpcEnvironment
,
190 ) -> Result
<(), Error
> {
192 let (config
, _digest
) = config
::drive
::config()?
;
194 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
196 let info
= &api2
::tape
::drive
::API_METHOD_EJECT_MEDIA
;
199 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
210 schema
: DRIVE_NAME_SCHEMA
,
214 schema
: MEDIA_LABEL_SCHEMA
,
222 rpcenv
: &mut dyn RpcEnvironment
,
223 ) -> Result
<(), Error
> {
225 let (config
, _digest
) = config
::drive
::config()?
;
227 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
229 let info
= &api2
::tape
::drive
::API_METHOD_LOAD_MEDIA
;
232 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
243 schema
: MEDIA_POOL_NAME_SCHEMA
,
247 schema
: DRIVE_NAME_SCHEMA
,
251 schema
: MEDIA_LABEL_SCHEMA
,
257 async
fn label_media(
259 rpcenv
: &mut dyn RpcEnvironment
,
260 ) -> Result
<(), Error
> {
262 let (config
, _digest
) = config
::drive
::config()?
;
264 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
266 let info
= &api2
::tape
::drive
::API_METHOD_LABEL_MEDIA
;
268 let result
= match info
.handler
{
269 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
273 wait_for_local_worker(result
.as_str().unwrap()).await?
;
282 schema
: DRIVE_NAME_SCHEMA
,
286 schema
: OUTPUT_FORMAT
,
295 rpcenv
: &mut dyn RpcEnvironment
,
296 ) -> Result
<(), Error
> {
298 let (config
, _digest
) = config
::drive
::config()?
;
300 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
302 let output_format
= get_output_format(¶m
);
303 let info
= &api2
::tape
::drive
::API_METHOD_READ_LABEL
;
304 let mut data
= match info
.handler
{
305 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
309 let options
= default_table_format_options()
310 .column(ColumnConfig
::new("changer-id"))
311 .column(ColumnConfig
::new("uuid"))
312 .column(ColumnConfig
::new("ctime").renderer(render_epoch
))
313 .column(ColumnConfig
::new("pool"))
314 .column(ColumnConfig
::new("media-set-uuid"))
315 .column(ColumnConfig
::new("media-set-ctime").renderer(render_epoch
))
318 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
327 schema
: OUTPUT_FORMAT
,
331 schema
: DRIVE_NAME_SCHEMA
,
335 description
: "Load unknown tapes and try read labels",
340 description
: "Load all tapes and try read labels (even if already inventoried)",
347 /// List (and update) media labels (Changer Inventory)
349 read_labels
: Option
<bool
>,
350 read_all_labels
: Option
<bool
>,
352 rpcenv
: &mut dyn RpcEnvironment
,
353 ) -> Result
<(), Error
> {
355 let output_format
= get_output_format(¶m
);
357 let (config
, _digest
) = config
::drive
::config()?
;
358 let drive
= lookup_drive_name(¶m
, &config
)?
;
360 let do_read
= read_labels
.unwrap_or(false) || read_all_labels
.unwrap_or(false);
363 let mut param
= json
!({
366 if let Some(true) = read_all_labels
{
367 param
["read-all-labels"] = true.into();
369 let info
= &api2
::tape
::drive
::API_METHOD_UPDATE_INVENTORY
;
370 let result
= match info
.handler
{
371 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
374 wait_for_local_worker(result
.as_str().unwrap()).await?
;
377 let info
= &api2
::tape
::drive
::API_METHOD_INVENTORY
;
379 let param
= json
!({ "drive": &drive }
);
380 let mut data
= match info
.handler
{
381 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
385 let options
= default_table_format_options()
386 .column(ColumnConfig
::new("changer-id"))
387 .column(ColumnConfig
::new("uuid"))
390 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
399 schema
: MEDIA_POOL_NAME_SCHEMA
,
403 schema
: DRIVE_NAME_SCHEMA
,
409 /// Label media with barcodes from changer device
410 async
fn barcode_label_media(
412 rpcenv
: &mut dyn RpcEnvironment
,
413 ) -> Result
<(), Error
> {
415 let (config
, _digest
) = config
::drive
::config()?
;
417 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
419 let info
= &api2
::tape
::drive
::API_METHOD_BARCODE_LABEL_MEDIA
;
421 let result
= match info
.handler
{
422 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
426 wait_for_local_worker(result
.as_str().unwrap()).await?
;
435 schema
: DRIVE_NAME_SCHEMA
,
441 /// Move to end of media (MTEOM, used to debug)
442 fn move_to_eom(param
: Value
) -> Result
<(), Error
> {
444 let (config
, _digest
) = config
::drive
::config()?
;
446 let drive
= lookup_drive_name(¶m
, &config
)?
;
447 let mut drive
= open_drive(&config
, &drive
)?
;
449 drive
.move_to_eom()?
;
458 schema
: DRIVE_NAME_SCHEMA
,
464 /// Rewind, then read media contents and print debug info
466 /// Note: This reads unless the driver returns an IO Error, so this
467 /// method is expected to fails when we reach EOT.
468 fn debug_scan(param
: Value
) -> Result
<(), Error
> {
470 let (config
, _digest
) = config
::drive
::config()?
;
472 let drive
= lookup_drive_name(¶m
, &config
)?
;
473 let mut drive
= open_drive(&config
, &drive
)?
;
475 println
!("rewinding tape");
479 let file_number
= drive
.current_file_number()?
;
481 match drive
.read_next_file()?
{
486 Some(mut reader
) => {
487 println
!("got file number {}", file_number
);
489 let header
: Result
<MediaContentHeader
, _
> = unsafe { reader.read_le_value() }
;
492 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
493 println
!("got MediaContentHeader with wrong magic: {:?}", header
.magic
);
495 if let Some(name
) = PROXMOX_BACKUP_CONTENT_NAME
.get(&header
.content_magic
) {
496 println
!("got content header: {}", name
);
497 println
!(" uuid: {}", header
.content_uuid());
498 println
!(" ctime: {}", strftime_local("%c", header
.ctime
)?
);
499 println
!(" hsize: {}", HumanByte
::from(header
.size
as usize));
500 println
!(" part: {}", header
.part_number
);
502 println
!("got unknown content header: {:?}", header
.content_magic
);
507 println
!("unable to read content header - {}", err
);
510 let bytes
= reader
.skip_to_end()?
;
511 println
!("skipped {}", HumanByte
::from(bytes
));
521 schema
: DRIVE_NAME_SCHEMA
,
525 schema
: OUTPUT_FORMAT
,
531 /// Read Cartridge Memory (Medium auxiliary memory attributes)
534 rpcenv
: &mut dyn RpcEnvironment
,
535 ) -> Result
<(), Error
> {
537 let (config
, _digest
) = config
::drive
::config()?
;
539 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
541 let output_format
= get_output_format(¶m
);
542 let info
= &api2
::tape
::drive
::API_METHOD_CARTRIDGE_MEMORY
;
544 let mut data
= match info
.handler
{
545 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
549 let options
= default_table_format_options()
550 .column(ColumnConfig
::new("id"))
551 .column(ColumnConfig
::new("name"))
552 .column(ColumnConfig
::new("value"))
555 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
563 schema
: DRIVE_NAME_SCHEMA
,
567 schema
: OUTPUT_FORMAT
,
573 /// Get drive/media status
576 rpcenv
: &mut dyn RpcEnvironment
,
577 ) -> Result
<(), Error
> {
579 let (config
, _digest
) = config
::drive
::config()?
;
581 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
583 let output_format
= get_output_format(¶m
);
584 let info
= &api2
::tape
::drive
::API_METHOD_STATUS
;
586 let mut data
= match info
.handler
{
587 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
591 let options
= default_table_format_options()
592 .column(ColumnConfig
::new("blocksize"))
593 .column(ColumnConfig
::new("density"))
594 .column(ColumnConfig
::new("status"))
595 .column(ColumnConfig
::new("alert-flags"))
596 .column(ColumnConfig
::new("file-number"))
597 .column(ColumnConfig
::new("block-number"))
598 .column(ColumnConfig
::new("manufactured").renderer(render_epoch
))
599 .column(ColumnConfig
::new("bytes-written").renderer(render_bytes_human_readable
))
600 .column(ColumnConfig
::new("bytes-read").renderer(render_bytes_human_readable
))
601 .column(ColumnConfig
::new("medium-passes"))
602 .column(ColumnConfig
::new("volume-mounts"))
605 format_and_print_result_full(&mut data
, &info
.returns
, &output_format
, &options
);
613 schema
: DATASTORE_SCHEMA
,
616 schema
: MEDIA_POOL_NAME_SCHEMA
,
621 /// Backup datastore to tape media pool
624 rpcenv
: &mut dyn RpcEnvironment
,
625 ) -> Result
<(), Error
> {
627 let info
= &api2
::tape
::backup
::API_METHOD_BACKUP
;
629 let result
= match info
.handler
{
630 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
634 wait_for_local_worker(result
.as_str().unwrap()).await?
;
642 schema
: DATASTORE_SCHEMA
,
645 description
: "Media set UUID.",
651 /// Restore data from media-set
654 rpcenv
: &mut dyn RpcEnvironment
,
655 ) -> Result
<(), Error
> {
657 let info
= &api2
::tape
::restore
::API_METHOD_RESTORE
;
659 let result
= match info
.handler
{
660 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
664 wait_for_local_worker(result
.as_str().unwrap()).await?
;
673 schema
: DRIVE_NAME_SCHEMA
,
677 description
: "Force overriding existing index.",
682 description
: "Verbose mode - log all found chunks.",
687 schema
: OUTPUT_FORMAT
,
693 /// Scan media and record content
694 async
fn catalog_media(
696 rpcenv
: &mut dyn RpcEnvironment
,
697 ) -> Result
<(), Error
> {
699 let (config
, _digest
) = config
::drive
::config()?
;
701 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
703 let info
= &api2
::tape
::drive
::API_METHOD_CATALOG_MEDIA
;
705 let result
= match info
.handler
{
706 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
710 wait_for_local_worker(result
.as_str().unwrap()).await?
;
717 let cmd_def
= CliCommandMap
::new()
720 CliCommand
::new(&API_METHOD_BACKUP
)
721 .arg_param(&["store", "pool"])
722 .completion_cb("store", complete_datastore_name
)
723 .completion_cb("pool", complete_pool_name
)
727 CliCommand
::new(&API_METHOD_RESTORE
)
728 .arg_param(&["media-set", "store"])
729 .completion_cb("store", complete_datastore_name
)
730 .completion_cb("media-set", complete_media_set_uuid
)
734 CliCommand
::new(&API_METHOD_BARCODE_LABEL_MEDIA
)
735 .completion_cb("drive", complete_drive_name
)
736 .completion_cb("pool", complete_pool_name
)
740 CliCommand
::new(&API_METHOD_REWIND
)
741 .completion_cb("drive", complete_drive_name
)
745 CliCommand
::new(&API_METHOD_DEBUG_SCAN
)
746 .completion_cb("drive", complete_drive_name
)
750 CliCommand
::new(&API_METHOD_STATUS
)
751 .completion_cb("drive", complete_drive_name
)
755 CliCommand
::new(&API_METHOD_MOVE_TO_EOM
)
756 .completion_cb("drive", complete_drive_name
)
760 CliCommand
::new(&API_METHOD_ERASE_MEDIA
)
761 .completion_cb("drive", complete_drive_name
)
765 CliCommand
::new(&API_METHOD_EJECT_MEDIA
)
766 .completion_cb("drive", complete_drive_name
)
770 CliCommand
::new(&API_METHOD_INVENTORY
)
771 .completion_cb("drive", complete_drive_name
)
775 CliCommand
::new(&API_METHOD_READ_LABEL
)
776 .completion_cb("drive", complete_drive_name
)
780 CliCommand
::new(&API_METHOD_CATALOG_MEDIA
)
781 .completion_cb("drive", complete_drive_name
)
785 CliCommand
::new(&API_METHOD_CARTRIDGE_MEMORY
)
786 .completion_cb("drive", complete_drive_name
)
790 CliCommand
::new(&API_METHOD_LABEL_MEDIA
)
791 .completion_cb("drive", complete_drive_name
)
792 .completion_cb("pool", complete_pool_name
)
795 .insert("changer", changer_commands())
796 .insert("drive", drive_commands())
797 .insert("pool", pool_commands())
798 .insert("media", media_commands())
801 CliCommand
::new(&API_METHOD_LOAD_MEDIA
)
802 .arg_param(&["changer-id"])
803 .completion_cb("drive", complete_drive_name
)
804 .completion_cb("changer-id", complete_media_changer_id
)
808 let mut rpcenv
= CliEnvironment
::new();
809 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
811 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));