1 use anyhow
::{format_err, Error}
;
2 use serde_json
::{json, Value}
;
10 section_config
::SectionConfigData
,
25 worker_is_active_local
,
33 MEDIA_POOL_NAME_SCHEMA
,
38 datastore
::complete_datastore_name
,
39 drive
::complete_drive_name
,
40 media_pool
::complete_pool_name
,
44 complete_media_changer_id
,
46 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
47 PROXMOX_BACKUP_CONTENT_NAME
,
56 // Note: local workers should print logs to stdout, so there is no need
57 // to fetch/display logs. We just wait for the worker to finish.
58 pub async
fn wait_for_local_worker(upid_str
: &str) -> Result
<(), Error
> {
60 let upid
: UPID
= upid_str
.parse()?
;
62 let sleep_duration
= core
::time
::Duration
::new(0, 100_000_000);
65 if worker_is_active_local(&upid
) {
66 tokio
::time
::delay_for(sleep_duration
).await
;
76 config
: &SectionConfigData
,
77 ) -> Result
<String
, Error
> {
79 let drive
= param
["drive"]
82 .or_else(|| std
::env
::var("PROXMOX_TAPE_DRIVE").ok())
85 let mut drive_names
= Vec
::new();
87 for (name
, (section_type
, _
)) in config
.sections
.iter() {
89 if !(section_type
== "linux" || section_type
== "virtual") { continue; }
90 drive_names
.push(name
);
93 if drive_names
.len() == 1 {
94 Some(drive_names
[0].to_owned())
99 .ok_or_else(|| format_err
!("unable to get (default) drive name"))?
;
108 schema
: DRIVE_NAME_SCHEMA
,
112 description
: "Use fast erase.",
121 async
fn erase_media(
123 rpcenv
: &mut dyn RpcEnvironment
,
124 ) -> Result
<(), Error
> {
126 let (config
, _digest
) = config
::drive
::config()?
;
128 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
130 let info
= &api2
::tape
::drive
::API_METHOD_ERASE_MEDIA
;
132 let result
= match info
.handler
{
133 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
137 wait_for_local_worker(result
.as_str().unwrap()).await?
;
146 schema
: DRIVE_NAME_SCHEMA
,
155 rpcenv
: &mut dyn RpcEnvironment
,
156 ) -> Result
<(), Error
> {
158 let (config
, _digest
) = config
::drive
::config()?
;
160 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
162 let info
= &api2
::tape
::drive
::API_METHOD_REWIND
;
164 let result
= match info
.handler
{
165 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
169 wait_for_local_worker(result
.as_str().unwrap()).await?
;
178 schema
: DRIVE_NAME_SCHEMA
,
184 /// Eject/Unload drive media
185 async
fn eject_media(
187 rpcenv
: &mut dyn RpcEnvironment
,
188 ) -> Result
<(), Error
> {
190 let (config
, _digest
) = config
::drive
::config()?
;
192 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
194 let info
= &api2
::tape
::drive
::API_METHOD_EJECT_MEDIA
;
197 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
208 schema
: DRIVE_NAME_SCHEMA
,
212 schema
: MEDIA_LABEL_SCHEMA
,
220 rpcenv
: &mut dyn RpcEnvironment
,
221 ) -> Result
<(), Error
> {
223 let (config
, _digest
) = config
::drive
::config()?
;
225 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
227 let info
= &api2
::tape
::drive
::API_METHOD_LOAD_MEDIA
;
230 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
241 schema
: MEDIA_POOL_NAME_SCHEMA
,
245 schema
: DRIVE_NAME_SCHEMA
,
249 schema
: MEDIA_LABEL_SCHEMA
,
255 async
fn label_media(
257 rpcenv
: &mut dyn RpcEnvironment
,
258 ) -> Result
<(), Error
> {
260 let (config
, _digest
) = config
::drive
::config()?
;
262 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
264 let info
= &api2
::tape
::drive
::API_METHOD_LABEL_MEDIA
;
266 let result
= match info
.handler
{
267 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
271 wait_for_local_worker(result
.as_str().unwrap()).await?
;
280 schema
: DRIVE_NAME_SCHEMA
,
284 schema
: OUTPUT_FORMAT
,
293 rpcenv
: &mut dyn RpcEnvironment
,
294 ) -> Result
<(), Error
> {
296 let (config
, _digest
) = config
::drive
::config()?
;
298 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
300 let output_format
= get_output_format(¶m
);
301 let info
= &api2
::tape
::drive
::API_METHOD_READ_LABEL
;
302 let mut data
= match info
.handler
{
303 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
307 let options
= default_table_format_options()
308 .column(ColumnConfig
::new("changer-id"))
309 .column(ColumnConfig
::new("uuid"))
310 .column(ColumnConfig
::new("ctime").renderer(render_epoch
))
311 .column(ColumnConfig
::new("pool"))
312 .column(ColumnConfig
::new("media-set-uuid"))
313 .column(ColumnConfig
::new("media-set-ctime").renderer(render_epoch
))
316 format_and_print_result_full(&mut data
, info
.returns
, &output_format
, &options
);
325 schema
: OUTPUT_FORMAT
,
329 schema
: DRIVE_NAME_SCHEMA
,
333 description
: "Load unknown tapes and try read labels",
338 description
: "Load all tapes and try read labels (even if already inventoried)",
345 /// List (and update) media labels (Changer Inventory)
347 read_labels
: Option
<bool
>,
348 read_all_labels
: Option
<bool
>,
350 rpcenv
: &mut dyn RpcEnvironment
,
351 ) -> Result
<(), Error
> {
353 let output_format
= get_output_format(¶m
);
355 let (config
, _digest
) = config
::drive
::config()?
;
356 let drive
= lookup_drive_name(¶m
, &config
)?
;
358 let do_read
= read_labels
.unwrap_or(false) || read_all_labels
.unwrap_or(false);
361 let mut param
= json
!({
364 if let Some(true) = read_all_labels
{
365 param
["read-all-labels"] = true.into();
367 let info
= &api2
::tape
::drive
::API_METHOD_UPDATE_INVENTORY
;
368 let result
= match info
.handler
{
369 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
372 wait_for_local_worker(result
.as_str().unwrap()).await?
;
375 let info
= &api2
::tape
::drive
::API_METHOD_INVENTORY
;
377 let param
= json
!({ "drive": &drive }
);
378 let mut data
= match info
.handler
{
379 ApiHandler
::Async(handler
) => (handler
)(param
, info
, rpcenv
).await?
,
383 let options
= default_table_format_options()
384 .column(ColumnConfig
::new("changer-id"))
385 .column(ColumnConfig
::new("uuid"))
388 format_and_print_result_full(&mut data
, info
.returns
, &output_format
, &options
);
397 schema
: MEDIA_POOL_NAME_SCHEMA
,
401 schema
: DRIVE_NAME_SCHEMA
,
407 /// Label media with barcodes from changer device
408 async
fn barcode_label_media(
410 rpcenv
: &mut dyn RpcEnvironment
,
411 ) -> Result
<(), Error
> {
413 let (config
, _digest
) = config
::drive
::config()?
;
415 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
417 let info
= &api2
::tape
::drive
::API_METHOD_BARCODE_LABEL_MEDIA
;
419 let result
= match info
.handler
{
420 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
424 wait_for_local_worker(result
.as_str().unwrap()).await?
;
433 schema
: DRIVE_NAME_SCHEMA
,
439 /// Move to end of media (MTEOM, used to debug)
440 fn move_to_eom(param
: Value
) -> Result
<(), Error
> {
442 let (config
, _digest
) = config
::drive
::config()?
;
444 let drive
= lookup_drive_name(¶m
, &config
)?
;
445 let mut drive
= open_drive(&config
, &drive
)?
;
447 drive
.move_to_eom()?
;
456 schema
: DRIVE_NAME_SCHEMA
,
462 /// Rewind, then read media contents and print debug info
464 /// Note: This reads unless the driver returns an IO Error, so this
465 /// method is expected to fails when we reach EOT.
466 fn debug_scan(param
: Value
) -> Result
<(), Error
> {
468 let (config
, _digest
) = config
::drive
::config()?
;
470 let drive
= lookup_drive_name(¶m
, &config
)?
;
471 let mut drive
= open_drive(&config
, &drive
)?
;
473 println
!("rewinding tape");
477 let file_number
= drive
.current_file_number()?
;
479 match drive
.read_next_file()?
{
484 Some(mut reader
) => {
485 println
!("got file number {}", file_number
);
487 let header
: Result
<MediaContentHeader
, _
> = unsafe { reader.read_le_value() }
;
490 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
491 println
!("got MediaContentHeader with wrong magic: {:?}", header
.magic
);
493 if let Some(name
) = PROXMOX_BACKUP_CONTENT_NAME
.get(&header
.content_magic
) {
494 println
!("got content header: {}", name
);
495 println
!(" uuid: {}", header
.content_uuid());
496 println
!(" ctime: {}", strftime_local("%c", header
.ctime
)?
);
497 println
!(" hsize: {}", HumanByte
::from(header
.size
as usize));
498 println
!(" part: {}", header
.part_number
);
500 println
!("got unknown content header: {:?}", header
.content_magic
);
505 println
!("unable to read content header - {}", err
);
508 let bytes
= reader
.skip_to_end()?
;
509 println
!("skipped {}", HumanByte
::from(bytes
));
519 schema
: DRIVE_NAME_SCHEMA
,
523 schema
: OUTPUT_FORMAT
,
529 /// Read Medium auxiliary memory attributes (Cartridge Memory)
532 rpcenv
: &mut dyn RpcEnvironment
,
533 ) -> Result
<(), Error
> {
535 let (config
, _digest
) = config
::drive
::config()?
;
537 param
["drive"] = lookup_drive_name(¶m
, &config
)?
.into();
539 let output_format
= get_output_format(¶m
);
540 let info
= &api2
::tape
::drive
::API_METHOD_MAM_ATTRIBUTES
;
542 let mut data
= match info
.handler
{
543 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
547 let options
= default_table_format_options()
548 .column(ColumnConfig
::new("id"))
549 .column(ColumnConfig
::new("name"))
550 .column(ColumnConfig
::new("value"))
553 format_and_print_result_full(&mut data
, info
.returns
, &output_format
, &options
);
561 schema
: DATASTORE_SCHEMA
,
564 schema
: MEDIA_POOL_NAME_SCHEMA
,
569 /// Backup datastore to tape media pool
572 rpcenv
: &mut dyn RpcEnvironment
,
573 ) -> Result
<(), Error
> {
575 let info
= &api2
::tape
::backup
::API_METHOD_BACKUP
;
577 let result
= match info
.handler
{
578 ApiHandler
::Sync(handler
) => (handler
)(param
, info
, rpcenv
)?
,
582 wait_for_local_worker(result
.as_str().unwrap()).await?
;
589 let cmd_def
= CliCommandMap
::new()
592 CliCommand
::new(&API_METHOD_BACKUP
)
593 .arg_param(&["store", "pool"])
594 .completion_cb("store", complete_datastore_name
)
595 .completion_cb("pool", complete_pool_name
)
599 CliCommand
::new(&API_METHOD_BARCODE_LABEL_MEDIA
)
600 .completion_cb("drive", complete_drive_name
)
601 .completion_cb("pool", complete_pool_name
)
605 CliCommand
::new(&API_METHOD_REWIND
)
606 .completion_cb("drive", complete_drive_name
)
610 CliCommand
::new(&API_METHOD_DEBUG_SCAN
)
611 .completion_cb("drive", complete_drive_name
)
615 CliCommand
::new(&API_METHOD_MOVE_TO_EOM
)
616 .completion_cb("drive", complete_drive_name
)
620 CliCommand
::new(&API_METHOD_ERASE_MEDIA
)
621 .completion_cb("drive", complete_drive_name
)
625 CliCommand
::new(&API_METHOD_EJECT_MEDIA
)
626 .completion_cb("drive", complete_drive_name
)
630 CliCommand
::new(&API_METHOD_INVENTORY
)
631 .completion_cb("drive", complete_drive_name
)
635 CliCommand
::new(&API_METHOD_READ_LABEL
)
636 .completion_cb("drive", complete_drive_name
)
640 CliCommand
::new(&API_METHOD_MAM_ATTRIBUTES
)
641 .completion_cb("drive", complete_drive_name
)
645 CliCommand
::new(&API_METHOD_LABEL_MEDIA
)
646 .completion_cb("drive", complete_drive_name
)
647 .completion_cb("pool", complete_pool_name
)
650 .insert("changer", changer_commands())
651 .insert("drive", drive_commands())
652 .insert("pool", pool_commands())
653 .insert("media", media_commands())
656 CliCommand
::new(&API_METHOD_LOAD_MEDIA
)
657 .arg_param(&["changer-id"])
658 .completion_cb("drive", complete_drive_name
)
659 .completion_cb("changer-id", complete_media_changer_id
)
663 let mut rpcenv
= CliEnvironment
::new();
664 rpcenv
.set_auth_id(Some(String
::from("root@pam")));
666 proxmox_backup
::tools
::runtime
::main(run_async_cli_command(cmd_def
, rpcenv
));