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,
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,
+ },
},
};
input: {
properties: {
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
fast: {
input: {
properties: {
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
},
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> {
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!(),
};
input: {
properties: {
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
"changer-id": {
},
)]
/// Load media
-fn load_media(
+async fn load_media(
mut param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
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!(),
};
optional: true,
},
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
"changer-id": {
input: {
properties: {
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
"output-format": {
},
)]
/// Read media label
-fn read_label(
+async fn read_label(
mut param: Value,
rpcenv: &mut dyn RpcEnvironment,
) -> Result<(), Error> {
let output_format = get_output_format(¶m);
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!(),
};
.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(())
}
optional: true,
},
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
"read-labels": {
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!(),
};
.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(())
}
optional: true,
},
drive: {
- schema: DRIVE_ID_SCHEMA,
+ schema: DRIVE_NAME_SCHEMA,
optional: true,
},
},
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(¶m, &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(¶m, &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(¶m, &config)?.into();
+
+ let output_format = get_output_format(¶m);
+ 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)
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)
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)
.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)