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)
17 use std
::fs
::{OpenOptions, File}
;
18 use std
::os
::unix
::fs
::OpenOptionsExt
;
19 use std
::os
::unix
::io
::{AsRawFd, FromRawFd, RawFd}
;
20 use std
::convert
::TryInto
;
22 use anyhow
::{bail, format_err, Error}
;
23 use nix
::fcntl
::{fcntl, FcntlArg, OFlag}
;
27 sys
::error
::SysResult
,
30 use pbs_api_types
::Fingerprint
;
31 use pbs_datastore
::key_derivation
::KeyConfig
;
32 use pbs_tools
::run_command
;
38 LtoDriveAndMediaStatus
,
50 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
57 /// Open a tape device
59 /// This does additional checks:
61 /// - check if it is a non-rewinding tape device
62 /// - check if drive is ready (tape loaded)
63 /// - check block size
64 /// - for autoloader only, try to reload ejected tapes
65 pub fn open_lto_tape_drive(config
: &LtoTapeDrive
) -> Result
<LtoTapeHandle
, Error
> {
68 let file
= open_lto_tape_device(&config
.path
)?
;
70 let mut handle
= LtoTapeHandle
::new(file
)?
;
72 if !handle
.sg_tape
.test_unit_ready().is_ok() {
73 // for autoloader only, try to reload ejected tapes
74 if config
.changer
.is_some() {
75 let _
= handle
.sg_tape
.load(); // just try, ignore error
79 handle
.sg_tape
.wait_until_ready()?
;
81 handle
.set_default_options()?
;
84 }).map_err(|err
: Error
| format_err
!("open drive '{}' ({}) failed - {}", config
.name
, config
.path
, err
))
87 /// Lto Tape device handle
88 pub struct LtoTapeHandle
{
94 /// Creates a new instance
95 pub fn new(file
: File
) -> Result
<Self, Error
> {
96 let sg_tape
= SgTape
::new(file
)?
;
100 /// Set all options we need/want
101 pub fn set_default_options(&mut self) -> Result
<(), Error
> {
103 let compression
= Some(true);
104 let block_length
= Some(0); // variable length mode
105 let buffer_mode
= Some(true); // Always use drive buffer
107 self.set_drive_options(compression
, block_length
, buffer_mode
)?
;
112 /// Set driver options
113 pub fn set_drive_options(
115 compression
: Option
<bool
>,
116 block_length
: Option
<u32>,
117 buffer_mode
: Option
<bool
>,
118 ) -> Result
<(), Error
> {
119 self.sg_tape
.set_drive_options(compression
, block_length
, buffer_mode
)
122 /// Write a single EOF mark without flushing buffers
123 pub fn write_filemarks(&mut self, count
: usize) -> Result
<(), std
::io
::Error
> {
124 self.sg_tape
.write_filemarks(count
, false)
127 /// Get Tape and Media status
128 pub fn get_drive_and_media_status(&mut self) -> Result
<LtoDriveAndMediaStatus
, Error
> {
130 let drive_status
= self.sg_tape
.read_drive_status()?
;
132 let alert_flags
= self.tape_alert_flags()
133 .map(|flags
| format
!("{:?}", flags
))
136 let mut status
= LtoDriveAndMediaStatus
{
137 vendor
: self.sg_tape
.info().vendor
.clone(),
138 product
: self.sg_tape
.info().product
.clone(),
139 revision
: self.sg_tape
.info().revision
.clone(),
140 blocksize
: drive_status
.block_length
,
141 compression
: drive_status
.compression
,
142 buffer_mode
: drive_status
.buffer_mode
,
143 density
: drive_status
.density_code
.try_into()?
,
152 medium_wearout
: None
,
156 if self.sg_tape
.test_unit_ready().is_ok() {
158 if drive_status
.write_protect
{
159 status
.write_protect
= Some(drive_status
.write_protect
);
162 let position
= self.sg_tape
.position()?
;
164 status
.file_number
= Some(position
.logical_file_id
);
165 status
.block_number
= Some(position
.logical_object_number
);
167 if let Ok(mam
) = self.cartridge_memory() {
169 let usage
= mam_extract_media_usage(&mam
)?
;
171 status
.manufactured
= Some(usage
.manufactured
);
172 status
.bytes_read
= Some(usage
.bytes_read
);
173 status
.bytes_written
= Some(usage
.bytes_written
);
175 if let Ok(volume_stats
) = self.volume_statistics() {
177 let passes
= std
::cmp
::max(
178 volume_stats
.beginning_of_medium_passes
,
179 volume_stats
.middle_of_tape_passes
,
182 // assume max. 16000 medium passes
183 // see: https://en.wikipedia.org/wiki/Linear_Tape-Open
184 let wearout
: f64 = (passes
as f64)/(16000.0 as f64);
186 status
.medium_passes
= Some(passes
);
187 status
.medium_wearout
= Some(wearout
);
189 status
.volume_mounts
= Some(volume_stats
.volume_mounts
);
197 pub fn forward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
198 self.sg_tape
.space_filemarks(count
.try_into()?
)
201 pub fn backward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
202 self.sg_tape
.space_filemarks(-count
.try_into()?
)
205 pub fn forward_space_count_records(&mut self, count
: usize) -> Result
<(), Error
> {
206 self.sg_tape
.space_blocks(count
.try_into()?
)
209 pub fn backward_space_count_records(&mut self, count
: usize) -> Result
<(), Error
> {
210 self.sg_tape
.space_blocks(-count
.try_into()?
)
213 /// Position the tape after filemark count. Count 0 means BOT.
214 pub fn locate_file(&mut self, position
: u64) -> Result
<(), Error
> {
215 self.sg_tape
.locate_file(position
)
218 pub fn erase_media(&mut self, fast
: bool
) -> Result
<(), Error
> {
219 self.sg_tape
.erase_media(fast
)
222 pub fn load(&mut self) -> Result
<(), Error
> {
226 /// Read Cartridge Memory (MAM Attributes)
227 pub fn cartridge_memory(&mut self) -> Result
<Vec
<MamAttribute
>, Error
> {
228 self.sg_tape
.cartridge_memory()
231 /// Read Volume Statistics
232 pub fn volume_statistics(&mut self) -> Result
<Lp17VolumeStatistics
, Error
> {
233 self.sg_tape
.volume_statistics()
236 /// Lock the drive door
237 pub fn lock(&mut self) -> Result
<(), Error
> {
238 self.sg_tape
.set_medium_removal(false)
239 .map_err(|err
| format_err
!("lock door failed - {}", err
))
242 /// Unlock the drive door
243 pub fn unlock(&mut self) -> Result
<(), Error
> {
244 self.sg_tape
.set_medium_removal(true)
245 .map_err(|err
| format_err
!("unlock door failed - {}", err
))
250 impl TapeDriver
for LtoTapeHandle
{
252 fn sync(&mut self) -> Result
<(), Error
> {
253 self.sg_tape
.sync()?
;
257 /// Go to the end of the recorded media (for appending files).
258 fn move_to_eom(&mut self, write_missing_eof
: bool
) -> Result
<(), Error
> {
259 self.sg_tape
.move_to_eom(write_missing_eof
)
262 fn move_to_last_file(&mut self) -> Result
<(), Error
> {
264 self.move_to_eom(false)?
;
266 self.sg_tape
.check_filemark()?
;
268 let pos
= self.current_file_number()?
;
271 bail
!("move_to_last_file failed - media contains no data");
279 self.backward_space_count_files(2)?
;
280 self.forward_space_count_files(1)?
;
285 fn move_to_file(&mut self, file
: u64) -> Result
<(), Error
> {
286 self.locate_file(file
)
289 fn rewind(&mut self) -> Result
<(), Error
> {
290 self.sg_tape
.rewind()
293 fn current_file_number(&mut self) -> Result
<u64, Error
> {
294 self.sg_tape
.current_file_number()
297 fn format_media(&mut self, fast
: bool
) -> Result
<(), Error
> {
298 self.sg_tape
.format_media(fast
)
301 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeRead
+ 'a
>, BlockReadError
> {
302 let reader
= self.sg_tape
.open_reader()?
;
303 let handle
: Box
<dyn TapeRead
> = Box
::new(reader
);
307 fn write_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeWrite
+ 'a
>, std
::io
::Error
> {
308 let handle
= self.sg_tape
.open_writer();
312 fn write_media_set_label(
314 media_set_label
: &MediaSetLabel
,
315 key_config
: Option
<&KeyConfig
>,
316 ) -> Result
<(), Error
> {
318 let file_number
= self.current_file_number()?
;
319 if file_number
!= 1 {
321 self.forward_space_count_files(1)?
; // skip label
324 let file_number
= self.current_file_number()?
;
325 if file_number
!= 1 {
326 bail
!("write_media_set_label failed - got wrong file number ({} != 1)", file_number
);
329 self.set_encryption(None
)?
;
331 { // limit handle scope
332 let mut handle
= self.write_file()?
;
334 let mut value
= serde_json
::to_value(media_set_label
)?
;
335 if media_set_label
.encryption_key_fingerprint
.is_some() {
337 Some(key_config
) => {
338 value
["key-config"] = serde_json
::to_value(key_config
)?
;
341 bail
!("missing encryption key config");
346 let raw
= serde_json
::to_string_pretty(&value
)?
;
348 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, raw
.len() as u32);
349 handle
.write_header(&header
, raw
.as_bytes())?
;
350 handle
.finish(false)?
;
353 self.sync()?
; // sync data to tape
358 /// Rewind and put the drive off line (Eject media).
359 fn eject_media(&mut self) -> Result
<(), Error
> {
363 /// Read Tape Alert Flags
364 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
365 self.sg_tape
.tape_alert_flags()
368 /// Set or clear encryption key
370 /// Note: Only 'root' can read secret encryption keys, so we need
371 /// to spawn setuid binary 'sg-tape-cmd'.
374 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
375 ) -> Result
<(), Error
> {
377 if nix
::unistd
::Uid
::effective().is_root() {
379 if let Some((ref key_fingerprint
, ref uuid
)) = key_fingerprint
{
381 let (key_map
, _digest
) = config
::tape_encryption_keys
::load_keys()?
;
382 match key_map
.get(key_fingerprint
) {
385 // derive specialized key for each media-set
387 let mut tape_key
= [0u8; 32];
389 let uuid_bytes
: [u8; 16] = uuid
.as_bytes().clone();
391 openssl
::pkcs5
::pbkdf2_hmac(
395 openssl
::hash
::MessageDigest
::sha256(),
398 return self.sg_tape
.set_encryption(Some(tape_key
));
400 None
=> bail
!("unknown tape encryption key '{}'", key_fingerprint
),
403 return self.sg_tape
.set_encryption(None
);
407 let output
= if let Some((fingerprint
, uuid
)) = key_fingerprint
{
408 let fingerprint
= pbs_tools
::format
::as_fingerprint(fingerprint
.bytes());
409 run_sg_tape_cmd("encryption", &[
410 "--fingerprint", &fingerprint
,
411 "--uuid", &uuid
.to_string(),
412 ], self.sg_tape
.file_mut().as_raw_fd())?
414 run_sg_tape_cmd("encryption", &[], self.sg_tape
.file_mut().as_raw_fd())?
416 let result
: Result
<(), String
> = serde_json
::from_str(&output
)?
;
417 result
.map_err(|err
| format_err
!("{}", err
))
421 /// Check for correct Major/Minor numbers
422 pub fn check_tape_is_lto_tape_device(file
: &File
) -> Result
<(), Error
> {
424 let stat
= nix
::sys
::stat
::fstat(file
.as_raw_fd())?
;
426 let devnum
= stat
.st_rdev
;
428 let major
= unsafe { libc::major(devnum) }
;
429 let _minor
= unsafe { libc::minor(devnum) }
;
432 bail
!("not a scsi-generic tape device (cannot use linux tape devices)");
436 bail
!("not a scsi-generic tape device");
442 /// Opens a Lto tape device
444 /// The open call use O_NONBLOCK, but that flag is cleard after open
445 /// succeeded. This also checks if the device is a non-rewinding tape
447 pub fn open_lto_tape_device(
449 ) -> Result
<File
, Error
> {
451 let file
= OpenOptions
::new()
454 .custom_flags(libc
::O_NONBLOCK
)
457 // clear O_NONBLOCK from now on.
459 let flags
= fcntl(file
.as_raw_fd(), FcntlArg
::F_GETFL
)
462 let mut flags
= OFlag
::from_bits_truncate(flags
);
463 flags
.remove(OFlag
::O_NONBLOCK
);
465 fcntl(file
.as_raw_fd(), FcntlArg
::F_SETFL(flags
))
468 check_tape_is_lto_tape_device(&file
)
469 .map_err(|err
| format_err
!("device type check {:?} failed - {}", path
, err
))?
;
474 fn run_sg_tape_cmd(subcmd
: &str, args
: &[&str], fd
: RawFd
) -> Result
<String
, Error
> {
475 let mut command
= std
::process
::Command
::new(
476 "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd");
477 command
.args(&[subcmd
]);
478 command
.args(&["--stdin"]);
480 let device_fd
= nix
::unistd
::dup(fd
)?
;
481 command
.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)}
);
482 run_command(command
, None
)