]> git.proxmox.com Git - proxmox-backup.git/blobdiff - src/bin/proxmox-tape.rs
adaptions for proxmox 0.9 and proxmox-api-macro 0.3
[proxmox-backup.git] / src / bin / proxmox-tape.rs
index f5c3264efe293e744cd636622ef8dd3bec6f8654..6c138ca4ca000054f1eb860536b866a816382b8f 100644 (file)
@@ -9,10 +9,17 @@ use proxmox::{
         RpcEnvironment,
         section_config::SectionConfigData,
     },
+    tools::{
+        time::strftime_local,
+        io::ReadExt,
+    },
 };
 
 use proxmox_backup::{
-    tools::format::render_epoch,
+    tools::format::{
+        HumanByte,
+        render_epoch,
+    },
     server::{
         UPID,
         worker_is_active_local,
@@ -20,18 +27,26 @@ use proxmox_backup::{
     api2::{
         self,
         types::{
-            DRIVE_ID_SCHEMA,
+            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,
     },
     tape::{
+        open_drive,
         complete_media_changer_id,
+        file_formats::{
+            PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
+            PROXMOX_BACKUP_CONTENT_NAME,
+            MediaContentHeader,
+        },
     },
 };
 
@@ -90,7 +105,7 @@ fn lookup_drive_name(
     input: {
         properties: {
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
             fast: {
@@ -128,7 +143,7 @@ async fn erase_media(
     input: {
         properties: {
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
         },
@@ -160,14 +175,14 @@ async fn rewind(
     input: {
         properties: {
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
         },
     },
 )]
 /// Eject/Unload drive media
-fn eject_media(
+async fn eject_media(
     mut param: Value,
     rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<(), Error> {
@@ -179,7 +194,7 @@ fn eject_media(
     let info = &api2::tape::drive::API_METHOD_EJECT_MEDIA;
 
     match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
         _ => unreachable!(),
     };
 
@@ -190,7 +205,7 @@ fn eject_media(
     input: {
         properties: {
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
             "changer-id": {
@@ -200,7 +215,7 @@ fn eject_media(
     },
 )]
 /// Load media
-fn load_media(
+async fn load_media(
     mut param: Value,
     rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<(), Error> {
@@ -212,7 +227,7 @@ fn load_media(
     let info = &api2::tape::drive::API_METHOD_LOAD_MEDIA;
 
     match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
         _ => unreachable!(),
     };
 
@@ -227,7 +242,7 @@ fn load_media(
                 optional: true,
             },
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
             "changer-id": {
@@ -262,7 +277,7 @@ async fn label_media(
     input: {
         properties: {
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
              "output-format": {
@@ -273,7 +288,7 @@ async fn label_media(
     },
 )]
 /// Read media label
-fn read_label(
+async fn read_label(
     mut param: Value,
     rpcenv: &mut dyn RpcEnvironment,
 ) -> Result<(), Error> {
@@ -285,7 +300,7 @@ fn read_label(
     let output_format = get_output_format(&param);
     let info = &api2::tape::drive::API_METHOD_READ_LABEL;
     let mut data = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
         _ => unreachable!(),
     };
 
@@ -298,7 +313,7 @@ fn read_label(
         .column(ColumnConfig::new("media-set-ctime").renderer(render_epoch))
         ;
 
-    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+    format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
 
     Ok(())
 }
@@ -311,7 +326,7 @@ fn read_label(
                 optional: true,
             },
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
             "read-labels": {
@@ -361,7 +376,7 @@ async fn inventory(
 
     let param = json!({ "drive": &drive });
     let mut data = match info.handler {
-        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        ApiHandler::Async(handler) => (handler)(param, info, rpcenv).await?,
         _ => unreachable!(),
     };
 
@@ -370,7 +385,7 @@ async fn inventory(
         .column(ColumnConfig::new("uuid"))
         ;
 
-    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+    format_and_print_result_full(&mut data, &info.returns, &output_format, &options);
 
     Ok(())
 }
@@ -383,7 +398,7 @@ async fn inventory(
                 optional: true,
             },
             drive: {
-                schema: DRIVE_ID_SCHEMA,
+                schema: DRIVE_NAME_SCHEMA,
                 optional: true,
             },
         },
@@ -411,9 +426,174 @@ async fn barcode_label_media(
     Ok(())
 }
 
+#[api(
+    input: {
+        properties: {
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
+            },
+        },
+    },
+)]
+/// Move to end of media (MTEOM, used to debug)
+fn move_to_eom(param: Value) -> Result<(), Error> {
+
+    let (config, _digest) = config::drive::config()?;
+
+    let drive = lookup_drive_name(&param, &config)?;
+    let mut drive = open_drive(&config, &drive)?;
+
+    drive.move_to_eom()?;
+
+    Ok(())
+}
+
+#[api(
+    input: {
+        properties: {
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
+            },
+        },
+    },
+)]
+/// Rewind, then read media contents and print debug info
+///
+/// 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> {
+
+    let (config, _digest) = config::drive::config()?;
+
+    let drive = lookup_drive_name(&param, &config)?;
+    let mut drive = open_drive(&config, &drive)?;
+
+    println!("rewinding tape");
+    drive.rewind()?;
+
+    loop {
+        let file_number = drive.current_file_number()?;
+
+        match drive.read_next_file()? {
+            None => {
+                println!("EOD");
+                continue;
+            },
+            Some(mut reader) => {
+                println!("got file number {}", file_number);
+
+                let header: Result<MediaContentHeader, _> = unsafe { reader.read_le_value() };
+                match header {
+                    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) {
+                                println!("got content header: {}", name);
+                                println!("  uuid:  {}", header.content_uuid());
+                                println!("  ctime: {}", strftime_local("%c", header.ctime)?);
+                                println!("  hsize: {}", HumanByte::from(header.size as usize));
+                                println!("  part:  {}", header.part_number);
+                            } else {
+                                println!("got unknown content header: {:?}", header.content_magic);
+                            }
+                        }
+                    }
+                    Err(err) => {
+                        println!("unable to read content header - {}", err);
+                    }
+                }
+                let bytes = reader.skip_to_end()?;
+                println!("skipped {}", HumanByte::from(bytes));
+            }
+        }
+    }
+}
+
+#[api(
+    input: {
+        properties: {
+            drive: {
+                schema: DRIVE_NAME_SCHEMA,
+                optional: true,
+            },
+             "output-format": {
+                schema: OUTPUT_FORMAT,
+                optional: true,
+             },
+        },
+    },
+)]
+/// Read Medium auxiliary memory attributes (Cartridge Memory)
+fn mam_attributes(
+    mut param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+    let (config, _digest) = config::drive::config()?;
+
+    param["drive"] = lookup_drive_name(&param, &config)?.into();
+
+    let output_format = get_output_format(&param);
+    let info = &api2::tape::drive::API_METHOD_MAM_ATTRIBUTES;
+
+    let mut data = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
+
+    let options = default_table_format_options()
+        .column(ColumnConfig::new("id"))
+        .column(ColumnConfig::new("name"))
+        .column(ColumnConfig::new("value"))
+        ;
+
+    format_and_print_result_full(&mut data, info.returns, &output_format, &options);
+    Ok(())
+}
+
+#[api(
+   input: {
+        properties: {
+            store: {
+                schema: DATASTORE_SCHEMA,
+            },
+            pool: {
+                schema: MEDIA_POOL_NAME_SCHEMA,
+            },
+        },
+    },
+)]
+/// Backup datastore to tape media pool
+async fn backup(
+    param: Value,
+    rpcenv: &mut dyn RpcEnvironment,
+) -> Result<(), Error> {
+
+    let info = &api2::tape::backup::API_METHOD_BACKUP;
+
+    let result = match info.handler {
+        ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?,
+        _ => unreachable!(),
+    };
+
+    wait_for_local_worker(result.as_str().unwrap()).await?;
+
+    Ok(())
+}
+
 fn main() {
 
     let cmd_def = CliCommandMap::new()
+        .insert(
+            "backup",
+            CliCommand::new(&API_METHOD_BACKUP)
+                .arg_param(&["store", "pool"])
+                .completion_cb("store", complete_datastore_name)
+                .completion_cb("pool", complete_pool_name)
+        )
         .insert(
             "barcode-label",
             CliCommand::new(&API_METHOD_BARCODE_LABEL_MEDIA)
@@ -425,6 +605,16 @@ fn main() {
             CliCommand::new(&API_METHOD_REWIND)
                 .completion_cb("drive", complete_drive_name)
         )
+        .insert(
+            "scan",
+            CliCommand::new(&API_METHOD_DEBUG_SCAN)
+                .completion_cb("drive", complete_drive_name)
+        )
+        .insert(
+            "eod",
+            CliCommand::new(&API_METHOD_MOVE_TO_EOM)
+                .completion_cb("drive", complete_drive_name)
+        )
         .insert(
             "erase",
             CliCommand::new(&API_METHOD_ERASE_MEDIA)
@@ -445,6 +635,11 @@ fn main() {
             CliCommand::new(&API_METHOD_READ_LABEL)
                 .completion_cb("drive", complete_drive_name)
         )
+        .insert(
+            "mam",
+            CliCommand::new(&API_METHOD_MAM_ATTRIBUTES)
+                .completion_cb("drive", complete_drive_name)
+        )
         .insert(
             "label",
             CliCommand::new(&API_METHOD_LABEL_MEDIA)
@@ -455,6 +650,7 @@ fn main() {
         .insert("changer", changer_commands())
         .insert("drive", drive_commands())
         .insert("pool", pool_commands())
+        .insert("media", media_commands())
         .insert(
             "load-media",
             CliCommand::new(&API_METHOD_LOAD_MEDIA)