1 /// Control magnetic tape drive operation
3 /// This is a Rust implementation, using the Proxmox userspace tape
4 /// driver. This is meant as replacement fot the 'mt' command line
10 /// - use Proxmox userspace driver (using SG_IO)
11 /// - optional json output format
12 /// - support tape alert flags
13 /// - support volume statistics
14 /// - read cartridge memory
16 use std
::convert
::TryInto
;
18 use anyhow
::{bail, Error}
;
19 use serde_json
::Value
;
21 use proxmox_schema
::{api, ArraySchema, IntegerSchema, Schema, StringSchema}
;
22 use proxmox_router
::cli
::*;
23 use proxmox_router
::RpcEnvironment
;
26 LTO_DRIVE_PATH_SCHEMA
, DRIVE_NAME_SCHEMA
, LtoTapeDrive
,
28 use pbs_config
::drive
::complete_drive_name
;
31 linux_list_drives
::{complete_drive_path, lto_tape_device_list, open_lto_tape_device}
,
34 pub const FILE_MARK_COUNT_SCHEMA
: Schema
=
35 IntegerSchema
::new("File mark count.")
37 .maximum(i32::MAX
as isize)
40 pub const FILE_MARK_POSITION_SCHEMA
: Schema
=
41 IntegerSchema
::new("File mark position (0 is BOT).")
43 .maximum(i32::MAX
as isize)
46 pub const RECORD_COUNT_SCHEMA
: Schema
=
47 IntegerSchema
::new("Record count.")
49 .maximum(i32::MAX
as isize)
52 pub const DRIVE_OPTION_SCHEMA
: Schema
= StringSchema
::new(
53 "Lto Tape Driver Option, either numeric value or option name.")
56 pub const DRIVE_OPTION_LIST_SCHEMA
: Schema
=
57 ArraySchema
::new("Drive Option List.", &DRIVE_OPTION_SCHEMA
)
61 fn get_tape_handle(param
: &Value
) -> Result
<SgTape
, Error
> {
63 if let Some(name
) = param
["drive"].as_str() {
64 let (config
, _digest
) = pbs_config
::drive
::config()?
;
65 let drive
: LtoTapeDrive
= config
.lookup("lto", &name
)?
;
66 eprintln
!("using device {}", drive
.path
);
67 return SgTape
::new(open_lto_tape_device(&drive
.path
)?
);
70 if let Some(device
) = param
["device"].as_str() {
71 eprintln
!("using device {}", device
);
72 return SgTape
::new(open_lto_tape_device(&device
)?
);
75 if let Ok(name
) = std
::env
::var("PROXMOX_TAPE_DRIVE") {
76 let (config
, _digest
) = pbs_config
::drive
::config()?
;
77 let drive
: LtoTapeDrive
= config
.lookup("lto", &name
)?
;
78 eprintln
!("using device {}", drive
.path
);
79 return SgTape
::new(open_lto_tape_device(&drive
.path
)?
);
82 if let Ok(device
) = std
::env
::var("TAPE") {
83 eprintln
!("using device {}", device
);
84 return SgTape
::new(open_lto_tape_device(&device
)?
);
87 let (config
, _digest
) = pbs_config
::drive
::config()?
;
89 let mut drive_names
= Vec
::new();
90 for (name
, (section_type
, _
)) in config
.sections
.iter() {
91 if section_type
!= "lto" { continue; }
92 drive_names
.push(name
);
95 if drive_names
.len() == 1 {
96 let name
= drive_names
[0];
97 let drive
: LtoTapeDrive
= config
.lookup("lto", &name
)?
;
98 eprintln
!("using device {}", drive
.path
);
99 return SgTape
::new(open_lto_tape_device(&drive
.path
)?
);
102 bail
!("no drive/device specified");
109 schema
: DRIVE_NAME_SCHEMA
,
113 schema
: LTO_DRIVE_PATH_SCHEMA
,
117 schema
: FILE_MARK_POSITION_SCHEMA
,
122 /// Position the tape at the beginning of the count file (after
124 fn asf(count
: u64, param
: Value
) -> Result
<(), Error
> {
126 let mut handle
= get_tape_handle(¶m
)?
;
128 handle
.locate_file(count
)?
;
138 schema
: DRIVE_NAME_SCHEMA
,
142 schema
: LTO_DRIVE_PATH_SCHEMA
,
146 schema
: FILE_MARK_COUNT_SCHEMA
,
151 /// Backward space count files (position before file mark).
153 /// The tape is positioned on the last block of the previous file.
154 fn bsf(count
: usize, param
: Value
) -> Result
<(), Error
> {
156 let mut handle
= get_tape_handle(¶m
)?
;
158 handle
.space_filemarks(-count
.try_into()?
)?
;
168 schema
: DRIVE_NAME_SCHEMA
,
172 schema
: LTO_DRIVE_PATH_SCHEMA
,
176 schema
: FILE_MARK_COUNT_SCHEMA
,
181 /// Backward space count files, then forward space one record (position after file mark).
183 /// This leaves the tape positioned at the first block of the file
184 /// that is count - 1 files before the current file.
185 fn bsfm(count
: usize, param
: Value
) -> Result
<(), Error
> {
187 let mut handle
= get_tape_handle(¶m
)?
;
189 handle
.space_filemarks(-count
.try_into()?
)?
;
190 handle
.space_filemarks(1)?
;
200 schema
: DRIVE_NAME_SCHEMA
,
204 schema
: LTO_DRIVE_PATH_SCHEMA
,
208 schema
: RECORD_COUNT_SCHEMA
,
213 /// Backward space records.
214 fn bsr(count
: usize, param
: Value
) -> Result
<(), Error
> {
216 let mut handle
= get_tape_handle(¶m
)?
;
218 handle
.space_blocks(-count
.try_into()?
)?
;
228 schema
: DRIVE_NAME_SCHEMA
,
232 schema
: LTO_DRIVE_PATH_SCHEMA
,
236 schema
: OUTPUT_FORMAT
,
242 /// Read Cartridge Memory
243 fn cartridge_memory(param
: Value
) -> Result
<(), Error
> {
245 let output_format
= get_output_format(¶m
);
247 let mut handle
= get_tape_handle(¶m
)?
;
248 let result
= handle
.cartridge_memory();
250 if output_format
== "json-pretty" {
251 let result
= result
.map_err(|err
: Error
| err
.to_string());
252 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
256 if output_format
== "json" {
257 let result
= result
.map_err(|err
: Error
| err
.to_string());
258 println
!("{}", serde_json
::to_string(&result
)?
);
262 if output_format
!= "text" {
263 bail
!("unknown output format '{}'", output_format
);
269 println
!("{}|{}|{}", item
.id
, item
.name
, item
.value
);
279 schema
: DRIVE_NAME_SCHEMA
,
283 schema
: LTO_DRIVE_PATH_SCHEMA
,
287 schema
: OUTPUT_FORMAT
,
293 /// Read Tape Alert Flags
294 fn tape_alert_flags(param
: Value
) -> Result
<(), Error
> {
296 let output_format
= get_output_format(¶m
);
298 let mut handle
= get_tape_handle(¶m
)?
;
299 let result
= handle
.tape_alert_flags()
300 .map(|flags
| format
!("{:?}", flags
));
302 if output_format
== "json-pretty" {
303 let result
= result
.map_err(|err
: Error
| err
.to_string());
304 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
308 if output_format
== "json" {
309 let result
= result
.map_err(|err
: Error
| err
.to_string());
310 println
!("{}", serde_json
::to_string(&result
)?
);
314 if output_format
!= "text" {
315 bail
!("unknown output format '{}'", output_format
);
319 println
!("Tape Alert Flags: {}", flags
);
328 schema
: DRIVE_NAME_SCHEMA
,
332 schema
: LTO_DRIVE_PATH_SCHEMA
,
338 /// Eject drive media
339 fn eject(param
: Value
) -> Result
<(), Error
> {
341 let mut handle
= get_tape_handle(¶m
)?
;
352 schema
: DRIVE_NAME_SCHEMA
,
356 schema
: LTO_DRIVE_PATH_SCHEMA
,
362 /// Move to end of media
363 fn eod(param
: Value
) -> Result
<(), Error
> {
365 let mut handle
= get_tape_handle(¶m
)?
;
366 handle
.move_to_eom(false)?
;
376 schema
: DRIVE_NAME_SCHEMA
,
380 schema
: LTO_DRIVE_PATH_SCHEMA
,
384 description
: "Use fast erase.",
392 /// Erase media (from current position)
393 fn erase(fast
: Option
<bool
>, param
: Value
) -> Result
<(), Error
> {
395 let mut handle
= get_tape_handle(¶m
)?
;
396 handle
.erase_media(fast
.unwrap_or(true))?
;
405 schema
: DRIVE_NAME_SCHEMA
,
409 schema
: LTO_DRIVE_PATH_SCHEMA
,
413 description
: "Use fast erase.",
421 /// Format media, single partition
422 fn format(fast
: Option
<bool
>, param
: Value
) -> Result
<(), Error
> {
424 let mut handle
= get_tape_handle(¶m
)?
;
425 handle
.format_media(fast
.unwrap_or(true))?
;
434 schema
: DRIVE_NAME_SCHEMA
,
438 schema
: LTO_DRIVE_PATH_SCHEMA
,
442 schema
: FILE_MARK_COUNT_SCHEMA
,
447 /// Forward space count files (position after file mark).
449 /// The tape is positioned on the first block of the next file.
450 fn fsf(count
: usize, param
: Value
) -> Result
<(), Error
> {
452 let mut handle
= get_tape_handle(¶m
)?
;
454 handle
.space_filemarks(count
.try_into()?
)?
;
463 schema
: DRIVE_NAME_SCHEMA
,
467 schema
: LTO_DRIVE_PATH_SCHEMA
,
471 schema
: FILE_MARK_COUNT_SCHEMA
,
476 /// Forward space count files, then backward space one record (position before file mark).
478 /// This leaves the tape positioned at the last block of the file that
479 /// is count - 1 files past the current file.
480 fn fsfm(count
: usize, param
: Value
) -> Result
<(), Error
> {
482 let mut handle
= get_tape_handle(¶m
)?
;
484 handle
.space_filemarks(count
.try_into()?
)?
;
485 handle
.space_filemarks(-1)?
;
495 schema
: DRIVE_NAME_SCHEMA
,
499 schema
: LTO_DRIVE_PATH_SCHEMA
,
503 schema
: RECORD_COUNT_SCHEMA
,
508 /// Forward space records.
509 fn fsr(count
: usize, param
: Value
) -> Result
<(), Error
> {
511 let mut handle
= get_tape_handle(¶m
)?
;
513 handle
.space_blocks(count
.try_into()?
)?
;
523 schema
: DRIVE_NAME_SCHEMA
,
527 schema
: LTO_DRIVE_PATH_SCHEMA
,
534 fn load(param
: Value
) -> Result
<(), Error
> {
536 let mut handle
= get_tape_handle(¶m
)?
;
547 schema
: DRIVE_NAME_SCHEMA
,
551 schema
: LTO_DRIVE_PATH_SCHEMA
,
557 /// Lock the tape drive door
558 fn lock(param
: Value
) -> Result
<(), Error
> {
560 let mut handle
= get_tape_handle(¶m
)?
;
562 handle
.set_medium_removal(false)?
;
572 schema
: DRIVE_NAME_SCHEMA
,
576 schema
: LTO_DRIVE_PATH_SCHEMA
,
583 fn rewind(param
: Value
) -> Result
<(), Error
> {
585 let mut handle
= get_tape_handle(¶m
)?
;
596 schema
: OUTPUT_FORMAT
,
602 /// Scan for existing tape changer devices
603 fn scan(param
: Value
) -> Result
<(), Error
> {
605 let output_format
= get_output_format(¶m
);
607 let list
= lto_tape_device_list();
609 if output_format
== "json-pretty" {
610 println
!("{}", serde_json
::to_string_pretty(&list
)?
);
614 if output_format
== "json" {
615 println
!("{}", serde_json
::to_string(&list
)?
);
619 if output_format
!= "text" {
620 bail
!("unknown output format '{}'", output_format
);
623 for item
in list
.iter() {
624 println
!("{} ({}/{}/{})", item
.path
, item
.vendor
, item
.model
, item
.serial
);
634 schema
: DRIVE_NAME_SCHEMA
,
638 schema
: LTO_DRIVE_PATH_SCHEMA
,
642 schema
: OUTPUT_FORMAT
,
649 fn status(param
: Value
) -> Result
<(), Error
> {
651 let output_format
= get_output_format(¶m
);
653 let mut handle
= get_tape_handle(¶m
)?
;
655 let result
= handle
.get_drive_and_media_status();
657 if output_format
== "json-pretty" {
658 let result
= result
.map_err(|err
: Error
| err
.to_string());
659 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
663 if output_format
== "json" {
664 let result
= result
.map_err(|err
: Error
| err
.to_string());
665 println
!("{}", serde_json
::to_string(&result
)?
);
669 if output_format
!= "text" {
670 bail
!("unknown output format '{}'", output_format
);
673 let status
= result?
;
675 println
!("{}", serde_json
::to_string_pretty(&status
)?
);
685 schema
: DRIVE_NAME_SCHEMA
,
689 schema
: LTO_DRIVE_PATH_SCHEMA
,
695 /// Unlock the tape drive door
696 fn unlock(param
: Value
) -> Result
<(), Error
> {
698 let mut handle
= get_tape_handle(¶m
)?
;
700 handle
.set_medium_removal(true)?
;
710 schema
: DRIVE_NAME_SCHEMA
,
714 schema
: LTO_DRIVE_PATH_SCHEMA
,
718 schema
: OUTPUT_FORMAT
,
724 /// Volume Statistics
725 fn volume_statistics(param
: Value
) -> Result
<(), Error
> {
727 let output_format
= get_output_format(¶m
);
729 let mut handle
= get_tape_handle(¶m
)?
;
730 let result
= handle
.volume_statistics();
732 if output_format
== "json-pretty" {
733 let result
= result
.map_err(|err
: Error
| err
.to_string());
734 println
!("{}", serde_json
::to_string_pretty(&result
)?
);
738 if output_format
== "json" {
739 let result
= result
.map_err(|err
: Error
| err
.to_string());
740 println
!("{}", serde_json
::to_string(&result
)?
);
744 if output_format
!= "text" {
745 bail
!("unknown output format '{}'", output_format
);
750 println
!("{}", serde_json
::to_string_pretty(&data
)?
);
759 schema
: DRIVE_NAME_SCHEMA
,
763 schema
: LTO_DRIVE_PATH_SCHEMA
,
767 schema
: FILE_MARK_COUNT_SCHEMA
,
773 /// Write count (default 1) EOF marks at current position.
774 fn weof(count
: Option
<usize>, param
: Value
) -> Result
<(), Error
> {
776 let count
= count
.unwrap_or(1);
778 let mut handle
= get_tape_handle(¶m
)?
;
780 handle
.write_filemarks(count
, false)?
;
788 schema
: DRIVE_NAME_SCHEMA
,
792 schema
: LTO_DRIVE_PATH_SCHEMA
,
796 description
: "Enable/disable compression.",
801 description
: "Set tape drive block_length (0 is variable length).",
808 description
: "Use drive buffer.",
813 description
: "Set default options",
820 /// Set varios drive options
822 compression
: Option
<bool
>,
823 blocksize
: Option
<u32>,
824 buffer_mode
: Option
<bool
>,
825 defaults
: Option
<bool
>,
827 ) -> Result
<(), Error
> {
829 let mut handle
= get_tape_handle(¶m
)?
;
831 if let Some(true) = defaults
{
832 handle
.set_default_options()?
;
835 handle
.set_drive_options(compression
, blocksize
, buffer_mode
)?
;
840 fn main() -> Result
<(), Error
> {
842 let uid
= nix
::unistd
::Uid
::current();
844 let username
= match nix
::unistd
::User
::from_uid(uid
)?
{
845 Some(user
) => user
.name
,
846 None
=> bail
!("unable to get user name"),
849 let std_cmd
= |method
| {
850 CliCommand
::new(method
)
851 .completion_cb("drive", complete_drive_name
)
852 .completion_cb("device", complete_drive_path
)
855 let cmd_def
= CliCommandMap
::new()
856 .usage_skip_options(&["device", "drive", "output-format"])
857 .insert("asf", std_cmd(&API_METHOD_ASF
).arg_param(&["count"]))
858 .insert("bsf", std_cmd(&API_METHOD_BSF
).arg_param(&["count"]))
859 .insert("bsfm", std_cmd(&API_METHOD_BSFM
).arg_param(&["count"]))
860 .insert("bsr", std_cmd(&API_METHOD_BSR
).arg_param(&["count"]))
861 .insert("cartridge-memory", std_cmd(&API_METHOD_CARTRIDGE_MEMORY
))
862 .insert("eject", std_cmd(&API_METHOD_EJECT
))
863 .insert("eod", std_cmd(&API_METHOD_EOD
))
864 .insert("erase", std_cmd(&API_METHOD_ERASE
))
865 .insert("format", std_cmd(&API_METHOD_FORMAT
))
866 .insert("fsf", std_cmd(&API_METHOD_FSF
).arg_param(&["count"]))
867 .insert("fsfm", std_cmd(&API_METHOD_FSFM
).arg_param(&["count"]))
868 .insert("fsr", std_cmd(&API_METHOD_FSR
).arg_param(&["count"]))
869 .insert("load", std_cmd(&API_METHOD_LOAD
))
870 .insert("lock", std_cmd(&API_METHOD_LOCK
))
871 .insert("options", std_cmd(&API_METHOD_OPTIONS
))
872 .insert("rewind", std_cmd(&API_METHOD_REWIND
))
873 .insert("scan", CliCommand
::new(&API_METHOD_SCAN
))
874 .insert("status", std_cmd(&API_METHOD_STATUS
))
875 .insert("tape-alert-flags", std_cmd(&API_METHOD_TAPE_ALERT_FLAGS
))
876 .insert("unlock", std_cmd(&API_METHOD_UNLOCK
))
877 .insert("volume-statistics", std_cmd(&API_METHOD_VOLUME_STATISTICS
))
878 .insert("weof", std_cmd(&API_METHOD_WEOF
).arg_param(&["count"]))
881 let mut rpcenv
= CliEnvironment
::new();
882 rpcenv
.set_auth_id(Some(format
!("{}@pam", username
)));
884 run_cli_command(cmd_def
, rpcenv
, None
);