]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/proxmox-tape.rs
drop pbs_tools::auth
[proxmox-backup.git] / src / bin / proxmox-tape.rs
index 016267b332dd9070ce21b40bec105355869844a9..98d28c9569357d45c35cc7403e984952687b55d1 100644 (file)
@@ -5,7 +5,6 @@ use proxmox::{
     api::{
         api,
         cli::*,
-        ApiHandler,
         RpcEnvironment,
         section_config::SectionConfigData,
     },
@@ -15,88 +14,49 @@ use proxmox::{
     },
 };
 
+use pbs_client::view_task_result;
+use pbs_tools::format::{
+    HumanByte,
+    render_epoch,
+    render_bytes_human_readable,
+};
+
+use pbs_config::drive::complete_drive_name;
+use pbs_config::media_pool::complete_pool_name;
+use pbs_config::datastore::complete_datastore_name;
+
+use pbs_api_types::{
+    Userid, Authid, DATASTORE_SCHEMA, DATASTORE_MAP_LIST_SCHEMA,
+    DRIVE_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA,
+    TAPE_RESTORE_SNAPSHOT_SCHEMA,
+};
+use pbs_tape::{
+    PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0, BlockReadError, MediaContentHeader,
+};
+
 use proxmox_backup::{
-    tools::format::{
-        HumanByte,
-        render_epoch,
-        render_bytes_human_readable,
-    },
-    client::{
-        HttpClient,
-        display_task_log,
-        connect_to_localhost,
-    },
-    server::{
-        UPID,
-        worker_is_active_local,
-    },
-    api2::{
-        self,
-        types::{
-            DATASTORE_SCHEMA,
-            DRIVE_NAME_SCHEMA,
-            MEDIA_LABEL_SCHEMA,
-            MEDIA_POOL_NAME_SCHEMA,
-        },
-    },
-    config::{
-        self,
-        datastore::complete_datastore_name,
-        drive::complete_drive_name,
-        media_pool::complete_pool_name,
-    },
+    api2,
     tape::{
-        drive::open_drive,
+        drive::{
+            open_drive,
+            lock_tape_device,
+            set_tape_device_state,
+        },
         complete_media_label_text,
         complete_media_set_uuid,
+        complete_media_set_snapshots,
         file_formats::{
-            PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
-            PROXMOX_BACKUP_CONTENT_NAME,
-            MediaContentHeader,
+            proxmox_tape_magic_to_text,
         },
     },
+    client_helpers::connect_to_localhost,
 };
 
 mod proxmox_tape;
 use proxmox_tape::*;
 
-async fn view_task_result(
-    client: HttpClient,
-    result: Value,
-    output_format: &str,
-) -> Result<(), Error> {
-    let data = &result["data"];
-    if output_format == "text" {
-        if let Some(upid) = data.as_str() {
-            display_task_log(client, upid, true).await?;
-        }
-    } else {
-        format_and_print_result(&data, &output_format);
-    }
-
-    Ok(())
-}
-
-// Note: local workers should print logs to stdout, so there is no need
-// to fetch/display logs. We just wait for the worker to finish.
-pub async fn wait_for_local_worker(upid_str: &str) -> Result<(), Error> {
-
-    let upid: UPID = upid_str.parse()?;
-
-    let sleep_duration = core::time::Duration::new(0, 100_000_000);
-
-    loop {
-        if worker_is_active_local(&upid) {
-            tokio::time::sleep(sleep_duration).await;
-        } else {
-            break;
-        }
-    }
-    Ok(())
-}
-
-pub fn lookup_drive_name(
-    param: &Value,
+pub fn extract_drive_name(
+    param: &mut Value,
     config: &SectionConfigData,
 ) -> Result<String, Error> {
 
@@ -122,6 +82,10 @@ pub fn lookup_drive_name(
         })
         .ok_or_else(|| format_err!("unable to get (default) drive name"))?;
 
+    if let Some(map) = param.as_object_mut() {
+        map.remove("drive");
+    }
+
     Ok(drive)
 }
 
@@ -138,27 +102,28 @@ pub fn lookup_drive_name(
                 optional: true,
                 default: true,
             },
-        },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+       },
     },
 )]
-/// Erase media
-async fn erase_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+/// Format media
+async fn format_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_ERASE_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/format-media", drive);
+    let result = client.post(&path, Some(param)).await?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -170,27 +135,28 @@ async fn erase_media(
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Rewind tape
-async fn rewind(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn rewind(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_REWIND;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/rewind", drive);
+    let result = client.post(&path, Some(param)).await?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -202,25 +168,28 @@ async fn rewind(
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Eject/Unload drive media
-async fn eject_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn eject_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_EJECT_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/eject-media", drive);
+    let result = client.post(&path, Some(param)).await?;
+
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -235,25 +204,28 @@ async fn eject_media(
             "label-text": {
                 schema: MEDIA_LABEL_SCHEMA,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Load media with specified label
-async fn load_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn load_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_LOAD_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/load-media", drive);
+    let result = client.post(&path, Some(param)).await?;
+
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -272,21 +244,16 @@ async fn load_media(
     },
 )]
 /// Export media with specified label
-async fn export_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn export_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let info = &api2::tape::drive::API_METHOD_EXPORT_MEDIA;
+    let mut client = connect_to_localhost()?;
 
-    match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let path = format!("api2/json/tape/drive/{}/export-media", drive);
+    client.put(&path, Some(param)).await?;
 
     Ok(())
 }
@@ -307,21 +274,16 @@ async fn export_media(
     },
 )]
 /// Load media from the specified slot
-async fn load_media_from_slot(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn load_media_from_slot(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let info = &api2::tape::drive::API_METHOD_LOAD_SLOT;
+    let mut client = connect_to_localhost()?;
 
-    match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let path = format!("api2/json/tape/drive/{}/load-slot", drive);
+    client.put(&path, Some(param)).await?;
 
     Ok(())
 }
@@ -339,25 +301,28 @@ async fn load_media_from_slot(
                 minimum: 1,
                 optional: true,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Unload media via changer
-async fn unload_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn unload_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_UNLOAD;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/unload", drive);
+    let result = client.post(&path, Some(param)).await?;
+
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -376,27 +341,28 @@ async fn unload_media(
             "label-text": {
                 schema: MEDIA_LABEL_SCHEMA,
             },
-       },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
+        },
     },
 )]
 /// Label media
-async fn label_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn label_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_LABEL_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    let path = format!("api2/json/tape/drive/{}/label-media", drive);
+    let result = client.post(&path, Some(param)).await?;
+
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -421,21 +387,21 @@ async fn label_media(
     },
 )]
 /// Read media label
-async fn read_label(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn read_label(mut param: Value) -> Result<(), Error> {
+
+    let output_format = extract_output_format(&mut param);
+
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let (config, _digest) = config::drive::config()?;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/read-label", drive);
+    let mut result = client.get(&path, Some(param)).await?;
+    let mut data = result["data"].take();
 
-    let output_format = get_output_format(&param);
     let info = &api2::tape::drive::API_METHOD_READ_LABEL;
-    let mut data = match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
 
     let options = default_table_format_options()
         .column(ColumnConfig::new("label-text"))
@@ -480,39 +446,35 @@ async fn read_label(
 async fn inventory(
     read_labels: Option<bool>,
     read_all_labels: Option<bool>,
-    param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
+    mut param: Value,
 ) -> Result<(), Error> {
 
-    let output_format = get_output_format(&param);
+    let output_format = extract_output_format(&mut param);
 
-    let (config, _digest) = config::drive::config()?;
-    let drive = lookup_drive_name(&param, &config)?;
+    let (config, _digest) = pbs_config::drive::config()?;
+    let drive = extract_drive_name(&mut param, &config)?;
 
     let do_read = read_labels.unwrap_or(false) || read_all_labels.unwrap_or(false);
 
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/inventory", drive);
+
     if do_read {
-        let mut param = json!({
-            "drive": &drive,
-        });
+
+        let mut param = json!({});
         if let Some(true) = read_all_labels {
             param["read-all-labels"] = true.into();
         }
-        let info = &api2::tape::drive::API_METHOD_UPDATE_INVENTORY;
-        let result = match info.handler {
-            ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-            _ => unreachable!(),
-        };
-        wait_for_local_worker(result.as_str().unwrap()).await?;
+
+        let result = client.put(&path, Some(param)).await?; // update inventory
+        view_task_result(&mut client, result, &output_format).await?;
     }
 
-    let info = &api2::tape::drive::API_METHOD_INVENTORY;
+    let mut result = client.get(&path, None).await?;
+    let mut data = result["data"].take();
 
-    let param = json!({ "drive": &drive });
-    let mut data = match info.handler {
-        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
-        _ => unreachable!(),
-    };
+    let info = &api2::tape::drive::API_METHOD_INVENTORY;
 
     let options = default_table_format_options()
         .column(ColumnConfig::new("label-text"))
@@ -535,27 +497,28 @@ async fn inventory(
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Label media with barcodes from changer device
-async fn barcode_label_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn barcode_label_media(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_BARCODE_LABEL_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/barcode-label-media", drive);
+    let result = client.post(&path, Some(param)).await?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -571,14 +534,18 @@ async fn barcode_label_media(
     },
 )]
 /// Move to end of media (MTEOM, used to debug)
-fn move_to_eom(param: Value) -> Result<(), Error> {
+fn move_to_eom(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let (config, _digest) = pbs_config::drive::config()?;
+
+    let drive = extract_drive_name(&mut param, &config)?;
+
+    let _lock = lock_tape_device(&config, &drive)?;
+    set_tape_device_state(&drive, "moving to eom")?;
 
-    let drive = lookup_drive_name(&param, &config)?;
     let mut drive = open_drive(&config, &drive)?;
 
-    drive.move_to_eom()?;
+    drive.move_to_eom(false)?;
 
     Ok(())
 }
@@ -597,11 +564,15 @@ fn move_to_eom(param: Value) -> Result<(), Error> {
 ///
 /// Note: This reads unless the driver returns an IO Error, so this
 /// method is expected to fails when we reach EOT.
-fn debug_scan(param: Value) -> Result<(), Error> {
+fn debug_scan(mut param: Value) -> Result<(), Error> {
+
+    let (config, _digest) = pbs_config::drive::config()?;
+
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let (config, _digest) = config::drive::config()?;
+    let _lock = lock_tape_device(&config, &drive)?;
+    set_tape_device_state(&drive, "debug scan")?;
 
-    let drive = lookup_drive_name(&param, &config)?;
     let mut drive = open_drive(&config, &drive)?;
 
     println!("rewinding tape");
@@ -610,12 +581,19 @@ fn debug_scan(param: Value) -> Result<(), Error> {
     loop {
         let file_number = drive.current_file_number()?;
 
-        match drive.read_next_file()? {
-            None => {
-                println!("EOD");
+        match drive.read_next_file() {
+            Err(BlockReadError::EndOfFile) => {
+                println!("filemark number {}", file_number);
                 continue;
-            },
-            Some(mut reader) => {
+            }
+            Err(BlockReadError::EndOfStream) => {
+                println!("got EOT");
+                return Ok(());
+            }
+            Err(BlockReadError::Error(err)) => {
+                return Err(err.into());
+            }
+            Ok(mut reader) => {
                 println!("got file number {}", file_number);
 
                 let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
@@ -623,7 +601,7 @@ fn debug_scan(param: Value) -> Result<(), Error> {
                     Ok(header) => {
                         if header.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
                             println!("got MediaContentHeader with wrong magic: {:?}", header.magic);
-                        } else if let Some(name) = PROXMOX_BACKUP_CONTENT_NAME.get(&header.content_magic) {
+                        } else if let Some(name) = proxmox_tape_magic_to_text(&header.content_magic) {
                             println!("got content header: {}", name);
                             println!("  uuid:  {}", header.content_uuid());
                             println!("  ctime: {}", strftime_local("%c", header.ctime)?);
@@ -637,8 +615,15 @@ fn debug_scan(param: Value) -> Result<(), Error> {
                         println!("unable to read content header - {}", err);
                     }
                 }
-                let bytes = reader.skip_to_end()?;
+                let bytes = reader.skip_data()?;
                 println!("skipped {}", HumanByte::from(bytes));
+                if let Ok(true) = reader.has_end_marker() {
+                    if reader.is_incomplete()? {
+                        println!("WARNING: file is incomplete");
+                    }
+                } else {
+                    println!("WARNING: file without end marker");
+                }
             }
         }
     }
@@ -651,30 +636,29 @@ fn debug_scan(param: Value) -> Result<(), Error> {
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
-             "output-format": {
+            "output-format": {
                 schema: OUTPUT_FORMAT,
                 optional: true,
-             },
+            },
         },
     },
 )]
 /// Read Cartridge Memory (Medium auxiliary memory attributes)
-fn cartridge_memory(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn cartridge_memory(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let output_format = get_output_format(&param);
-    let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let mut data = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/cartridge-memory", drive);
+    let mut result = client.get(&path, Some(param)).await?;
+    let mut data = result["data"].take();
+
+    let info = &api2::tape::drive::API_METHOD_CARTRIDGE_MEMORY;
 
     let options = default_table_format_options()
         .column(ColumnConfig::new("id"))
@@ -693,34 +677,34 @@ fn cartridge_memory(
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
-             "output-format": {
+            "output-format": {
                 schema: OUTPUT_FORMAT,
                 optional: true,
-             },
+            },
         },
     },
 )]
 /// Read Volume Statistics (SCSI log page 17h)
-fn volume_statistics(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn volume_statistics(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let output_format = get_output_format(&param);
-    let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let mut data = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/volume-statistics", drive);
+    let mut result = client.get(&path, Some(param)).await?;
+    let mut data = result["data"].take();
+
+    let info = &api2::tape::drive::API_METHOD_VOLUME_STATISTICS;
 
     let options = default_table_format_options();
 
     format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
     Ok(())
 }
 
@@ -739,22 +723,21 @@ fn volume_statistics(
     },
 )]
 /// Get drive/media status
-fn status(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn status(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let output_format = get_output_format(&param);
-    let info = &api2::tape::drive::API_METHOD_STATUS;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let mut data = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/status", drive);
+    let mut result = client.get(&path, Some(param)).await?;
+    let mut data = result["data"].take();
+
+    let info = &api2::tape::drive::API_METHOD_STATUS;
 
     let render_percentage = |value: &Value, _record: &Value| {
         match value.as_f64() {
@@ -766,7 +749,9 @@ fn status(
     let options = default_table_format_options()
         .column(ColumnConfig::new("blocksize"))
         .column(ColumnConfig::new("density"))
-        .column(ColumnConfig::new("status"))
+        .column(ColumnConfig::new("compression"))
+        .column(ColumnConfig::new("buffer-mode"))
+        .column(ColumnConfig::new("write-protect"))
         .column(ColumnConfig::new("alert-flags"))
         .column(ColumnConfig::new("file-number"))
         .column(ColumnConfig::new("block-number"))
@@ -779,6 +764,7 @@ fn status(
         ;
 
     format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
+
     Ok(())
 }
 
@@ -789,40 +775,52 @@ fn status(
                 schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Clean drive
-async fn clean_drive(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn clean_drive(mut param: Value) -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_CLEAN_DRIVE;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/clean", drive);
+    let result = client.put(&path, Some(param)).await?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
 
 #[api(
-   input: {
+    input: {
         properties: {
+
+            // Note: We cannot use TapeBackupJobSetup, because drive needs to be optional here
+            //setup: {
+            //    type: TapeBackupJobSetup,
+            //    flatten: true,
+            //},
+
             store: {
                 schema: DATASTORE_SCHEMA,
             },
             pool: {
                 schema: MEDIA_POOL_NAME_SCHEMA,
             },
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
+            },
             "eject-media": {
                 description: "Eject media upon job completion.",
                 type: bool,
@@ -833,6 +831,17 @@ async fn clean_drive(
                 type: bool,
                 optional: true,
             },
+            "latest-only": {
+                description: "Backup latest snapshots only.",
+                type: bool,
+                optional: true,
+            },
+            "force-media-set": {
+                description: "Ignore the allocation policy and start a new media-set.",
+                optional: true,
+                type: bool,
+                default: false,
+            },
             "output-format": {
                 schema: OUTPUT_FORMAT,
                 optional: true,
@@ -841,45 +850,74 @@ async fn clean_drive(
     },
 )]
 /// Backup datastore to tape media pool
-async fn backup(param: Value) -> Result<(), Error> {
+async fn backup(mut param: Value) -> Result<(), Error> {
+
+    let output_format = extract_output_format(&mut param);
+
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let output_format = get_output_format(&param);
+    param["drive"] = extract_drive_name(&mut param, &config)?.into();
 
     let mut client = connect_to_localhost()?;
 
     let result = client.post("api2/json/tape/backup", Some(param)).await?;
 
-    view_task_result(client, result, &output_format).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
+
 #[api(
    input: {
         properties: {
             store: {
-                schema: DATASTORE_SCHEMA,
+                schema: DATASTORE_MAP_LIST_SCHEMA,
+            },
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
             },
             "media-set": {
                 description: "Media set UUID.",
                 type: String,
             },
+            "notify-user": {
+                type: Userid,
+                optional: true,
+            },
+            "snapshots": {
+                description: "List of snapshots.",
+                type: Array,
+                optional: true,
+                items: {
+                    schema: TAPE_RESTORE_SNAPSHOT_SCHEMA,
+                },
+            },
+            owner: {
+                type: Authid,
+                optional: true,
+            },
+            "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+            },
         },
     },
 )]
 /// Restore data from media-set
-async fn restore(
-    param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-) -> Result<(), Error> {
+async fn restore(mut param: Value) -> Result<(), Error> {
 
-    let info = &api2::tape::restore::API_METHOD_RESTORE;
+    let output_format = extract_output_format(&mut param);
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let (config, _digest) = pbs_config::drive::config()?;
+
+    param["drive"] = extract_drive_name(&mut param, &config)?.into();
+
+    let mut client = connect_to_localhost()?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    let result = client.post("api2/json/tape/restore", Some(param)).await?;
+
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -896,6 +934,11 @@ async fn restore(
                 type: bool,
                 optional: true,
             },
+            scan: {
+                description: "Re-read the whole tape to reconstruct the catalog instead of restoring saved versions.",
+                type: bool,
+                optional: true,
+            },
             verbose: {
                 description: "Verbose mode - log all found chunks.",
                 type: bool,
@@ -909,23 +952,20 @@ async fn restore(
     },
 )]
 /// Scan media and record content
-async fn catalog_media(
-    mut param: Value,
-    rpcenv: &mut dyn RpcEnvironment,
-)  -> Result<(), Error> {
+async fn catalog_media(mut param: Value)  -> Result<(), Error> {
 
-    let (config, _digest) = config::drive::config()?;
+    let output_format = extract_output_format(&mut param);
 
-    param["drive"] = lookup_drive_name(&param, &config)?.into();
+    let (config, _digest) = pbs_config::drive::config()?;
 
-    let info = &api2::tape::drive::API_METHOD_CATALOG_MEDIA;
+    let drive = extract_drive_name(&mut param, &config)?;
 
-    let result = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
-        _ => unreachable!(),
-    };
+    let mut client = connect_to_localhost()?;
+
+    let path = format!("api2/json/tape/drive/{}/catalog", drive);
+    let result = client.post(&path, Some(param)).await?;
 
-    wait_for_local_worker(result.as_str().unwrap()).await?;
+    view_task_result(&mut client, result, &output_format).await?;
 
     Ok(())
 }
@@ -937,15 +977,17 @@ fn main() {
             "backup",
             CliCommand::new(&API_METHOD_BACKUP)
                 .arg_param(&["store", "pool"])
+                .completion_cb("drive", complete_drive_name)
                 .completion_cb("store", complete_datastore_name)
                 .completion_cb("pool", complete_pool_name)
         )
         .insert(
             "restore",
             CliCommand::new(&API_METHOD_RESTORE)
-                .arg_param(&["media-set", "store"])
+                .arg_param(&["media-set", "store", "snapshots"])
                 .completion_cb("store", complete_datastore_name)
                 .completion_cb("media-set", complete_media_set_uuid)
+                .completion_cb("snapshots", complete_media_set_snapshots)
         )
         .insert(
             "barcode-label",
@@ -974,8 +1016,8 @@ fn main() {
                 .completion_cb("drive", complete_drive_name)
         )
         .insert(
-            "erase",
-            CliCommand::new(&API_METHOD_ERASE_MEDIA)
+            "format",
+            CliCommand::new(&API_METHOD_FORMAT_MEDIA)
                 .completion_cb("drive", complete_drive_name)
         )
         .insert(
@@ -1025,6 +1067,7 @@ fn main() {
         .insert("pool", pool_commands())
         .insert("media", media_commands())
         .insert("key", encryption_key_commands())
+        .insert("backup-job", backup_job_commands())
         .insert(
             "load-media",
             CliCommand::new(&API_METHOD_LOAD_MEDIA)
@@ -1055,5 +1098,5 @@ fn main() {
     let mut rpcenv = CliEnvironment::new();
     rpcenv.set_auth_id(Some(String::from("root@pam")));
 
-    proxmox_backup::tools::runtime::main(run_async_cli_command(cmd_def, rpcenv));
+    pbs_runtime::main(run_async_cli_command(cmd_def, rpcenv));
 }