]>
git.proxmox.com Git - proxmox-backup.git/blob - src/bin/pmtx.rs
1 /// SCSI changer command implemented using scsi-generic raw commands
3 /// This is a Rust implementation, meant to replace the 'mtx' command
12 /// - list serial number for attached drives, so that it is possible
13 /// to associate drive numbers with drives.
17 use anyhow
::{bail, Error}
;
18 use serde_json
::Value
;
28 use pbs_config
::drive
::complete_changer_name
;
35 SCSI_CHANGER_PATH_SCHEMA
,
41 linux_tape_changer_list
,
42 complete_changer_path
,
50 fn get_changer_handle(param
: &Value
) -> Result
<File
, Error
> {
52 if let Some(name
) = param
["changer"].as_str() {
53 let (config
, _digest
) = pbs_config
::drive
::config()?
;
54 let changer_config
: ScsiTapeChanger
= config
.lookup("changer", &name
)?
;
55 eprintln
!("using device {}", changer_config
.path
);
56 return sg_pt_changer
::open(&changer_config
.path
);
59 if let Some(device
) = param
["device"].as_str() {
60 eprintln
!("using device {}", device
);
61 return sg_pt_changer
::open(device
);
64 if let Ok(name
) = std
::env
::var("PROXMOX_TAPE_DRIVE") {
65 let (config
, _digest
) = pbs_config
::drive
::config()?
;
66 let drive
: LtoTapeDrive
= config
.lookup("lto", &name
)?
;
67 if let Some(changer
) = drive
.changer
{
68 let changer_config
: ScsiTapeChanger
= config
.lookup("changer", &changer
)?
;
69 eprintln
!("using device {}", changer_config
.path
);
70 return sg_pt_changer
::open(&changer_config
.path
);
74 if let Ok(device
) = std
::env
::var("CHANGER") {
75 eprintln
!("using device {}", device
);
76 return sg_pt_changer
::open(device
);
79 bail
!("no changer device specified");
86 schema
: CHANGER_NAME_SCHEMA
,
90 schema
: SCSI_CHANGER_PATH_SCHEMA
,
94 schema
: OUTPUT_FORMAT
,
103 ) -> Result
<(), Error
> {
105 let output_format
= get_output_format(¶m
);
107 let result
: Result
<_
, Error
> = proxmox
::try_block
!({
108 let mut file
= get_changer_handle(¶m
)?
;
109 let info
= scsi_inquiry(&mut file
)?
;
113 if output_format
== "json-pretty" {
114 let result
= result
.map_err(|err
: Error
| err
.to_string());
115 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
119 if output_format
== "json" {
120 let result
= result
.map_err(|err
: Error
| err
.to_string());
121 println
!("{}", serde_json
::to_string(&result
)?
);
125 if output_format
!= "text" {
126 bail
!("unknown output format '{}'", output_format
);
131 println
!("Type: {} ({})", info
.peripheral_type_text
, info
.peripheral_type
);
132 println
!("Vendor: {}", info
.vendor
);
133 println
!("Product: {}", info
.product
);
134 println
!("Revision: {}", info
.revision
);
143 schema
: CHANGER_NAME_SCHEMA
,
147 schema
: SCSI_CHANGER_PATH_SCHEMA
,
156 ) -> Result
<(), Error
> {
158 let mut file
= get_changer_handle(¶m
)?
;
159 sg_pt_changer
::initialize_element_status(&mut file
)?
;
168 schema
: CHANGER_NAME_SCHEMA
,
172 schema
: SCSI_CHANGER_PATH_SCHEMA
,
176 description
: "Storage slot number (source).",
180 description
: "Target drive number (defaults to Drive 0)",
191 drivenum
: Option
<u64>,
192 ) -> Result
<(), Error
> {
194 let mut file
= get_changer_handle(¶m
)?
;
196 let drivenum
= drivenum
.unwrap_or(0);
198 sg_pt_changer
::load_slot(&mut file
, slot
, drivenum
)?
;
207 schema
: CHANGER_NAME_SCHEMA
,
211 schema
: SCSI_CHANGER_PATH_SCHEMA
,
215 description
: "Storage slot number (target). If omitted, defaults to the slot that the drive was loaded from.",
220 description
: "Target drive number (defaults to Drive 0)",
231 drivenum
: Option
<u64>,
232 ) -> Result
<(), Error
> {
234 let mut file
= get_changer_handle(¶m
)?
;
236 let drivenum
= drivenum
.unwrap_or(0);
238 if let Some(to_slot
) = slot
{
239 sg_pt_changer
::unload(&mut file
, to_slot
, drivenum
)?
;
243 let status
= sg_pt_changer
::read_element_status(&mut file
)?
;
245 if let Some(info
) = status
.drives
.get(drivenum
as usize) {
246 if let ElementStatus
::Empty
= info
.status
{
247 bail
!("Drive {} is empty.", drivenum
);
249 if let Some(to_slot
) = info
.loaded_slot
{
250 // check if original slot is empty/usable
251 if let Some(slot_info
) = status
.slots
.get(to_slot
as usize - 1) {
252 if let ElementStatus
::Empty
= slot_info
.status
{
253 sg_pt_changer
::unload(&mut file
, to_slot
, drivenum
)?
;
259 if let Some(to_slot
) = status
.find_free_slot(false) {
260 sg_pt_changer
::unload(&mut file
, to_slot
, drivenum
)?
;
263 bail
!("Drive '{}' unload failure - no free slot", drivenum
);
266 bail
!("Drive {} does not exist.", drivenum
);
274 schema
: CHANGER_NAME_SCHEMA
,
278 schema
: SCSI_CHANGER_PATH_SCHEMA
,
282 schema
: OUTPUT_FORMAT
,
291 ) -> Result
<(), Error
> {
293 let output_format
= get_output_format(¶m
);
295 let result
: Result
<_
, Error
> = proxmox
::try_block
!({
296 let mut file
= get_changer_handle(¶m
)?
;
297 let status
= sg_pt_changer
::read_element_status(&mut file
)?
;
301 if output_format
== "json-pretty" {
302 let result
= result
.map_err(|err
: Error
| err
.to_string());
303 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
307 if output_format
== "json" {
308 let result
= result
.map_err(|err
: Error
| err
.to_string());
309 println
!("{}", serde_json
::to_string(&result
)?
);
313 if output_format
!= "text" {
314 bail
!("unknown output format '{}'", output_format
);
317 let status
= result?
;
319 for (i
, transport
) in status
.transports
.iter().enumerate() {
320 println
!("Transport Element (Griper) {:>3}: {:?}",i
, transport
.status
);
323 for (i
, drive
) in status
.drives
.iter().enumerate() {
324 let loaded_txt
= match drive
.loaded_slot
{
325 Some(slot
) => format
!(", Source: {}", slot
),
326 None
=> String
::new(),
328 let serial_txt
= match drive
.drive_serial_number
{
329 Some(ref serial
) => format
!(", Serial: {}", serial
),
330 None
=> String
::new(),
334 "Data Transfer Element (Drive) {:>3}: {:?}{}{}",
335 i
, drive
.status
, loaded_txt
, serial_txt
,
339 for (i
, slot
) in status
.slots
.iter().enumerate() {
340 if slot
.import_export
{
341 println
!(" Import/Export {:>3}: {:?}", i
+1, slot
.status
);
343 println
!(" Storage Element {:>3}: {:?}", i
+1, slot
.status
);
354 schema
: CHANGER_NAME_SCHEMA
,
358 schema
: SCSI_CHANGER_PATH_SCHEMA
,
362 description
: "Source storage slot number.",
366 description
: "Target storage slot number.",
377 ) -> Result
<(), Error
> {
379 let mut file
= get_changer_handle(¶m
)?
;
381 sg_pt_changer
::transfer_medium(&mut file
, from
, to
)?
;
390 schema
: OUTPUT_FORMAT
,
396 /// Scan for existing tape changer devices
397 fn scan(param
: Value
) -> Result
<(), Error
> {
399 let output_format
= get_output_format(¶m
);
401 let list
= linux_tape_changer_list();
403 if output_format
== "json-pretty" {
404 println
!("{}", serde_json
::to_string_pretty(&list
)?
);
408 if output_format
== "json" {
409 println
!("{}", serde_json
::to_string(&list
)?
);
413 if output_format
!= "text" {
414 bail
!("unknown output format '{}'", output_format
);
417 for item
in list
.iter() {
418 println
!("{} ({}/{}/{})", item
.path
, item
.vendor
, item
.model
, item
.serial
);
424 fn main() -> Result
<(), Error
> {
426 let uid
= nix
::unistd
::Uid
::current();
428 let username
= match nix
::unistd
::User
::from_uid(uid
)?
{
429 Some(user
) => user
.name
,
430 None
=> bail
!("unable to get user name"),
434 let cmd_def
= CliCommandMap
::new()
435 .usage_skip_options(&["device", "changer", "output-format"])
438 CliCommand
::new(&API_METHOD_INQUIRY
)
439 .completion_cb("changer", complete_changer_name
)
440 .completion_cb("device", complete_changer_path
)
444 CliCommand
::new(&API_METHOD_INVENTORY
)
445 .completion_cb("changer", complete_changer_name
)
446 .completion_cb("device", complete_changer_path
)
450 CliCommand
::new(&API_METHOD_LOAD
)
451 .arg_param(&["slot"])
452 .completion_cb("changer", complete_changer_name
)
453 .completion_cb("device", complete_changer_path
)
457 CliCommand
::new(&API_METHOD_UNLOAD
)
458 .completion_cb("changer", complete_changer_name
)
459 .completion_cb("device", complete_changer_path
)
461 .insert("scan", CliCommand
::new(&API_METHOD_SCAN
))
464 CliCommand
::new(&API_METHOD_STATUS
)
465 .completion_cb("changer", complete_changer_name
)
466 .completion_cb("device", complete_changer_path
)
470 CliCommand
::new(&API_METHOD_TRANSFER
)
471 .arg_param(&["from", "to"])
472 .completion_cb("changer", complete_changer_name
)
473 .completion_cb("device", complete_changer_path
)
477 let mut rpcenv
= CliEnvironment
::new();
478 rpcenv
.set_auth_id(Some(format
!("{}@pam", username
)));
480 run_cli_command(cmd_def
, rpcenv
, None
);