1 //! Driver for LTO SCSI tapes
3 //! This is a userspace drive implementation using SG_IO.
5 //! Why we do not use the Linux tape driver:
7 //! - missing features (MAM, Encryption, ...)
9 //! - strange permission handling - only root (or CAP_SYS_RAWIO) can
10 //! do SG_IO (SYS_RAW_IO)
12 //! - unability to detect EOT (you just get EIO)
15 use std
::os
::unix
::io
::{AsRawFd, FromRawFd, RawFd}
;
17 use anyhow
::{bail, format_err, Error}
;
19 use proxmox_uuid
::Uuid
;
22 Fingerprint
, Lp17VolumeStatistics
, LtoDriveAndMediaStatus
, LtoTapeDrive
, MamAttribute
,
24 use pbs_key_config
::KeyConfig
;
26 linux_list_drives
::open_lto_tape_device
,
27 sg_tape
::{SgTape, TapeAlertFlags}
,
28 BlockReadError
, MediaContentHeader
, TapeRead
, TapeWrite
,
30 use proxmox_sys
::command
::run_command
;
34 file_formats
::{MediaSetLabel, PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0}
,
37 /// Open a tape device
39 /// This does additional checks:
41 /// - check if it is a non-rewinding tape device
42 /// - check if drive is ready (tape loaded)
43 /// - check block size
44 /// - for autoloader only, try to reload ejected tapes
45 pub fn open_lto_tape_drive(config
: &LtoTapeDrive
) -> Result
<LtoTapeHandle
, Error
> {
46 proxmox_lang
::try_block
!({
47 let file
= open_lto_tape_device(&config
.path
)?
;
49 let mut handle
= LtoTapeHandle
::new(file
)?
;
51 if handle
.sg_tape
.test_unit_ready().is_err() {
52 // for autoloader only, try to reload ejected tapes
53 if config
.changer
.is_some() {
54 let _
= handle
.sg_tape
.load(); // just try, ignore error
58 handle
.sg_tape
.wait_until_ready(None
)?
;
60 handle
.set_default_options()?
;
64 .map_err(|err
: Error
| {
66 "open drive '{}' ({}) failed - {}",
74 /// Lto Tape device handle
75 pub struct LtoTapeHandle
{
80 /// Creates a new instance
81 pub fn new(file
: File
) -> Result
<Self, Error
> {
82 let sg_tape
= SgTape
::new(file
)?
;
86 /// Set all options we need/want
87 pub fn set_default_options(&mut self) -> Result
<(), Error
> {
88 self.sg_tape
.set_default_options()?
;
92 /// Set driver options
93 pub fn set_drive_options(
95 compression
: Option
<bool
>,
96 block_length
: Option
<u32>,
97 buffer_mode
: Option
<bool
>,
98 ) -> Result
<(), Error
> {
100 .set_drive_options(compression
, block_length
, buffer_mode
)
103 /// Write a single EOF mark without flushing buffers
104 pub fn write_filemarks(&mut self, count
: usize) -> Result
<(), std
::io
::Error
> {
105 self.sg_tape
.write_filemarks(count
, false)
108 /// Get Tape and Media status
109 pub fn get_drive_and_media_status(&mut self) -> Result
<LtoDriveAndMediaStatus
, Error
> {
110 self.sg_tape
.get_drive_and_media_status()
113 pub fn forward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
114 self.sg_tape
.space_filemarks(count
.try_into()?
)
117 pub fn backward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
118 self.sg_tape
.space_filemarks(-count
.try_into()?
)
121 pub fn forward_space_count_records(&mut self, count
: usize) -> Result
<(), Error
> {
122 self.sg_tape
.space_blocks(count
.try_into()?
)
125 pub fn backward_space_count_records(&mut self, count
: usize) -> Result
<(), Error
> {
126 self.sg_tape
.space_blocks(-count
.try_into()?
)
129 /// Position the tape after filemark count. Count 0 means BOT.
130 pub fn locate_file(&mut self, position
: u64) -> Result
<(), Error
> {
131 self.sg_tape
.locate_file(position
)
134 pub fn erase_media(&mut self, fast
: bool
) -> Result
<(), Error
> {
135 self.sg_tape
.erase_media(fast
)
138 pub fn load(&mut self) -> Result
<(), Error
> {
142 /// Read Cartridge Memory (MAM Attributes)
143 pub fn cartridge_memory(&mut self) -> Result
<Vec
<MamAttribute
>, Error
> {
144 self.sg_tape
.cartridge_memory()
147 /// Read Volume Statistics
148 pub fn volume_statistics(&mut self) -> Result
<Lp17VolumeStatistics
, Error
> {
149 self.sg_tape
.volume_statistics()
152 /// Lock the drive door
153 pub fn lock(&mut self) -> Result
<(), Error
> {
155 .set_medium_removal(false)
156 .map_err(|err
| format_err
!("lock door failed - {}", err
))
159 /// Unlock the drive door
160 pub fn unlock(&mut self) -> Result
<(), Error
> {
162 .set_medium_removal(true)
163 .map_err(|err
| format_err
!("unlock door failed - {}", err
))
166 /// Returns if a medium is present
167 pub fn medium_present(&mut self) -> bool
{
168 self.sg_tape
.test_unit_ready().is_ok()
172 impl TapeDriver
for LtoTapeHandle
{
173 fn sync(&mut self) -> Result
<(), Error
> {
174 self.sg_tape
.sync()?
;
178 /// Go to the end of the recorded media (for appending files).
179 fn move_to_eom(&mut self, write_missing_eof
: bool
) -> Result
<(), Error
> {
180 self.sg_tape
.move_to_eom(write_missing_eof
)
183 fn move_to_last_file(&mut self) -> Result
<(), Error
> {
184 self.move_to_eom(false)?
;
186 self.sg_tape
.check_filemark()?
;
188 let pos
= self.current_file_number()?
;
191 bail
!("move_to_last_file failed - media contains no data");
199 self.backward_space_count_files(2)?
;
200 self.forward_space_count_files(1)?
;
205 fn move_to_file(&mut self, file
: u64) -> Result
<(), Error
> {
206 self.locate_file(file
)
209 fn rewind(&mut self) -> Result
<(), Error
> {
210 self.sg_tape
.rewind()
213 fn current_file_number(&mut self) -> Result
<u64, Error
> {
214 self.sg_tape
.current_file_number()
217 fn format_media(&mut self, fast
: bool
) -> Result
<(), Error
> {
218 self.sg_tape
.format_media(fast
)
221 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeRead
+ 'a
>, BlockReadError
> {
222 let reader
= self.sg_tape
.open_reader()?
;
223 let handle
: Box
<dyn TapeRead
> = Box
::new(reader
);
227 fn write_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeWrite
+ 'a
>, std
::io
::Error
> {
228 let handle
= self.sg_tape
.open_writer();
232 fn write_media_set_label(
234 media_set_label
: &MediaSetLabel
,
235 key_config
: Option
<&KeyConfig
>,
236 ) -> Result
<(), Error
> {
237 let file_number
= self.current_file_number()?
;
238 if file_number
!= 1 {
240 self.forward_space_count_files(1)?
; // skip label
243 let file_number
= self.current_file_number()?
;
244 if file_number
!= 1 {
246 "write_media_set_label failed - got wrong file number ({} != 1)",
251 self.set_encryption(None
)?
;
254 // limit handle scope
255 let mut handle
= self.write_file()?
;
257 let mut value
= serde_json
::to_value(media_set_label
)?
;
258 if media_set_label
.encryption_key_fingerprint
.is_some() {
260 Some(key_config
) => {
261 value
["key-config"] = serde_json
::to_value(key_config
)?
;
264 bail
!("missing encryption key config");
269 let raw
= serde_json
::to_string_pretty(&value
)?
;
272 MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, raw
.len() as u32);
273 handle
.write_header(&header
, raw
.as_bytes())?
;
274 handle
.finish(false)?
;
277 self.sync()?
; // sync data to tape
282 /// Rewind and put the drive off line (Eject media).
283 fn eject_media(&mut self) -> Result
<(), Error
> {
287 /// Read Tape Alert Flags
288 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
289 self.sg_tape
.tape_alert_flags()
292 /// Set or clear encryption key
294 /// Note: Only 'root' can read secret encryption keys, so we need
295 /// to spawn setuid binary 'sg-tape-cmd'.
298 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
299 ) -> Result
<(), Error
> {
300 if nix
::unistd
::Uid
::effective().is_root() {
301 if let Some((ref key_fingerprint
, ref uuid
)) = key_fingerprint
{
302 let (key_map
, _digest
) = crate::tape
::encryption_keys
::load_keys()?
;
303 match key_map
.get(key_fingerprint
) {
305 // derive specialized key for each media-set
307 let mut tape_key
= [0u8; 32];
309 let uuid_bytes
: [u8; 16] = *uuid
.as_bytes();
311 openssl
::pkcs5
::pbkdf2_hmac(
315 openssl
::hash
::MessageDigest
::sha256(),
319 return self.sg_tape
.set_encryption(Some(tape_key
));
321 None
=> bail
!("unknown tape encryption key '{}'", key_fingerprint
),
324 return self.sg_tape
.set_encryption(None
);
328 let output
= if let Some((fingerprint
, uuid
)) = key_fingerprint
{
329 let fingerprint
= fingerprint
.signature();
332 &["--fingerprint", &fingerprint
, "--uuid", &uuid
.to_string()],
333 self.sg_tape
.file_mut().as_raw_fd(),
336 run_sg_tape_cmd("encryption", &[], self.sg_tape
.file_mut().as_raw_fd())?
338 let result
: Result
<(), String
> = serde_json
::from_str(&output
)?
;
339 result
.map_err(|err
| format_err
!("{}", err
))
343 fn run_sg_tape_cmd(subcmd
: &str, args
: &[&str], fd
: RawFd
) -> Result
<String
, Error
> {
345 std
::process
::Command
::new("/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
346 command
.args([subcmd
]);
347 command
.args(["--stdin"]);
349 let device_fd
= nix
::unistd
::dup(fd
)?
;
350 command
.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd) }
);
351 run_command(command
, None
)