diff --git a/src/tape/drive/linux_mtio.rs b/src/tape/drive/linux_mtio.rs deleted file mode 100644 index 0621bb01..00000000 --- a/src/tape/drive/linux_mtio.rs +++ /dev/null @@ -1,153 +0,0 @@ -//! Linux Magnetic Tape Driver ioctl definitions -//! -//! from: /usr/include/x86_64-linux-gnu/sys/mtio.h -//! -//! also see: man 4 st - -#[repr(C)] -pub struct mtop { - pub mt_op: MTCmd, /* Operations defined below. */ - pub mt_count: libc::c_int, /* How many of them. */ -} - -#[repr(i16)] -#[allow(dead_code)] // do not warn about unused command -pub enum MTCmd { - MTRESET = 0, /* +reset drive in case of problems */ - MTFSF = 1, /* forward space over FileMark, - * position at first record of next file - */ - MTBSF = 2, /* backward space FileMark (position before FM) */ - MTFSR = 3, /* forward space record */ - MTBSR = 4, /* backward space record */ - MTWEOF = 5, /* write an end-of-file record (mark) */ - MTREW = 6, /* rewind */ - MTOFFL = 7, /* rewind and put the drive offline (eject?) */ - MTNOP = 8, /* no op, set status only (read with MTIOCGET) */ - MTRETEN = 9, /* retension tape */ - MTBSFM = 10, /* +backward space FileMark, position at FM */ - MTFSFM = 11, /* +forward space FileMark, position at FM */ - MTEOM = 12, /* goto end of recorded media (for appending files). - * MTEOM positions after the last FM, ready for - * appending another file. - */ - MTERASE = 13, /* erase tape -- be careful! */ - MTRAS1 = 14, /* run self test 1 (nondestructive) */ - MTRAS2 = 15, /* run self test 2 (destructive) */ - MTRAS3 = 16, /* reserved for self test 3 */ - MTSETBLK = 20, /* set block length (SCSI) */ - MTSETDENSITY = 21, /* set tape density (SCSI) */ - MTSEEK = 22, /* seek to block (Tandberg, etc.) */ - MTTELL = 23, /* tell block (Tandberg, etc.) */ - MTSETDRVBUFFER = 24,/* set the drive buffering according to SCSI-2 */ - - /* ordinary buffered operation with code 1 */ - MTFSS = 25, /* space forward over setmarks */ - MTBSS = 26, /* space backward over setmarks */ - MTWSM = 27, /* write setmarks */ - - MTLOCK = 28, /* lock the drive door */ - MTUNLOCK = 29, /* unlock the drive door */ - MTLOAD = 30, /* execute the SCSI load command */ - MTUNLOAD = 31, /* execute the SCSI unload command */ - MTCOMPRESSION = 32, /* control compression with SCSI mode page 15 */ - MTSETPART = 33, /* Change the active tape partition */ - MTMKPART = 34, /* Format the tape with one or two partitions */ - MTWEOFI = 35, /* write an end-of-file record (mark) in immediate mode */ -} - -//#define MTIOCTOP _IOW('m', 1, struct mtop) /* Do a mag tape op. */ -nix::ioctl_write_ptr!(mtioctop, b'm', 1, mtop); - -// from: /usr/include/x86_64-linux-gnu/sys/mtio.h -#[derive(Default, Debug)] -#[repr(C)] -pub struct mtget { - pub mt_type: libc::c_long, /* Type of magtape device. */ - pub mt_resid: libc::c_long, /* Residual count: (not sure) - number of bytes ignored, or - number of files not skipped, or - number of records not skipped. */ - /* The following registers are device dependent. */ - pub mt_dsreg: libc::c_long, /* Status register. */ - pub mt_gstat: libc::c_long, /* Generic (device independent) status. */ - pub mt_erreg: libc::c_long, /* Error register. */ - /* The next two fields are not always used. */ - pub mt_fileno: i32 , /* Number of current file on tape. */ - pub mt_blkno: i32, /* Current block number. */ -} - -//#define MTIOCGET _IOR('m', 2, struct mtget) /* Get tape status. */ -nix::ioctl_read!(mtiocget, b'm', 2, mtget); - -#[repr(C)] -#[allow(dead_code)] -pub struct mtpos { - pub mt_blkno: libc::c_long, /* current block number */ -} - -//#define MTIOCPOS _IOR('m', 3, struct mtpos) /* Get tape position.*/ -nix::ioctl_read!(mtiocpos, b'm', 3, mtpos); - -pub const MT_ST_BLKSIZE_MASK: libc::c_long = 0x0ffffff; -pub const MT_ST_BLKSIZE_SHIFT: usize = 0; -pub const MT_ST_DENSITY_MASK: libc::c_long = 0xff000000; -pub const MT_ST_DENSITY_SHIFT: usize = 24; - -pub const MT_TYPE_ISSCSI1: libc::c_long = 0x71; /* Generic ANSI SCSI-1 tape unit. */ -pub const MT_TYPE_ISSCSI2: libc::c_long = 0x72; /* Generic ANSI SCSI-2 tape unit. */ - -// Generic Mag Tape (device independent) status macros for examining mt_gstat -- HP-UX compatible -// from: /usr/include/x86_64-linux-gnu/sys/mtio.h -bitflags::bitflags!{ - pub struct GMTStatusFlags: libc::c_long { - const EOF = 0x80000000; - const BOT = 0x40000000; - const EOT = 0x20000000; - const SM = 0x10000000; /* DDS setmark */ - const EOD = 0x08000000; /* DDS EOD */ - const WR_PROT = 0x04000000; - - const ONLINE = 0x01000000; - const D_6250 = 0x00800000; - const D_1600 = 0x00400000; - const D_800 = 0x00200000; - const DRIVE_OPEN = 0x00040000; /* Door open (no tape). */ - const IM_REP_EN = 0x00010000; /* Immediate report mode.*/ - const END_OF_STREAM = 0b00000001; - } -} - -#[repr(i32)] -#[allow(non_camel_case_types, dead_code)] -pub enum SetDrvBufferCmd { - MT_ST_BOOLEANS = 0x10000000, - MT_ST_SETBOOLEANS = 0x30000000, - MT_ST_CLEARBOOLEANS = 0x40000000, - MT_ST_WRITE_THRESHOLD = 0x20000000, - MT_ST_DEF_BLKSIZE = 0x50000000, - MT_ST_DEF_OPTIONS = 0x60000000, - MT_ST_SET_TIMEOUT = 0x70000000, - MT_ST_SET_LONG_TIMEOUT = 0x70100000, - MT_ST_SET_CLN = 0x80000000u32 as i32, -} - -bitflags::bitflags!{ - pub struct SetDrvBufferOptions: i32 { - const BUFFER_WRITES = 0x1; - const ASYNC_WRITES = 0x2; - const READ_AHEAD = 0x4; - const DEBUGGING = 0x8; - const TWO_FM = 0x10; - const FAST_MTEOM = 0x20; - const AUTO_LOCK = 0x40; - const DEF_WRITES = 0x80; - const CAN_BSR = 0x100; - const NO_BLKLIMS = 0x200; - const CAN_PARTITIONS = 0x400; - const SCSI2LOGICAL = 0x800; - const SYSV = 0x1000; - const NOWAIT = 0x2000; - const SILI = 0x4000; - } -} diff --git a/src/tape/drive/linux_tape.rs b/src/tape/drive/linux_tape.rs deleted file mode 100644 index d556f0f5..00000000 --- a/src/tape/drive/linux_tape.rs +++ /dev/null @@ -1,857 +0,0 @@ -//! Driver for Linux SCSI tapes - -use std::fs::{OpenOptions, File}; -use std::io::{Read, Write}; -use std::os::unix::fs::OpenOptionsExt; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::convert::TryFrom; - -use anyhow::{bail, format_err, Error}; -use nix::fcntl::{fcntl, FcntlArg, OFlag}; - -use proxmox::{ - tools::Uuid, - sys::error::{SysError, SysResult}, -}; - -use crate::{ - config, - backup::{ - Fingerprint, - KeyConfig, - }, - tools::run_command, - api2::types::{ - TapeDensity, - MamAttribute, - LinuxDriveAndMediaStatus, - }, - tape::{ - BlockWrite, - BlockRead, - BlockReadStatus, - TapeRead, - TapeWrite, - drive::{ - linux_mtio::*, - LinuxTapeDrive, - TapeDriver, - TapeAlertFlags, - Lp17VolumeStatistics, - read_mam_attributes, - mam_extract_media_usage, - read_tape_alert_flags, - read_volume_statistics, - set_encryption, - }, - file_formats::{ - PROXMOX_TAPE_BLOCK_SIZE, - PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, - MediaSetLabel, - MediaContentHeader, - BlockedReader, - BlockedWriter, - }, - }, -}; - -fn run_sg_tape_cmd(subcmd: &str, args: &[&str], fd: RawFd) -> Result { - let mut command = std::process::Command::new( - "/usr/lib/x86_64-linux-gnu/proxmox-backup/sg-tape-cmd"); - command.args(&[subcmd]); - command.args(&["--stdin"]); - command.args(args); - let device_fd = nix::unistd::dup(fd)?; - command.stdin(unsafe { std::process::Stdio::from_raw_fd(device_fd)}); - run_command(command, None) -} - -/// Linux tape drive status -#[derive(Debug)] -pub struct LinuxDriveStatus { - /// Size 0 is variable block size mode (default) - pub blocksize: u32, - /// Drive status flags - pub status: GMTStatusFlags, - /// Tape densitiy code (if drive media loaded) - pub density: Option, - /// Current file position if known (or -1) - pub file_number: Option, - /// Current block number if known (or -1) - pub block_number: Option, -} - -impl LinuxDriveStatus { - pub fn tape_is_ready(&self) -> bool { - self.status.contains(GMTStatusFlags::ONLINE) && - !self.status.contains(GMTStatusFlags::DRIVE_OPEN) - } -} - -impl LinuxTapeDrive { - - /// Open a tape device - /// - /// This does additional checks: - /// - /// - check if it is a non-rewinding tape device - /// - check if drive is ready (tape loaded) - /// - check block size - /// - for autoloader only, try to reload ejected tapes - pub fn open(&self) -> Result { - - proxmox::try_block!({ - let file = open_linux_tape_device(&self.path)?; - - let mut handle = LinuxTapeHandle::new(file); - - let mut drive_status = handle.get_drive_status()?; - - if !drive_status.tape_is_ready() { - // for autoloader only, try to reload ejected tapes - if self.changer.is_some() { - let _ = handle.mtload(); // just try, ignore error - drive_status = handle.get_drive_status()?; - } - } - - if !drive_status.tape_is_ready() { - bail!("tape not ready (no tape loaded)"); - } - - if drive_status.blocksize == 0 { - // device is variable block size - OK - } else if drive_status.blocksize != PROXMOX_TAPE_BLOCK_SIZE as u32 { - eprintln!("device is in fixed block size mode with wrong size ({} bytes)", drive_status.blocksize); - eprintln!("trying to set variable block size mode..."); - if handle.set_block_size(0).is_err() { - bail!("set variable block size mod failed - device uses wrong blocksize."); - } - } else { - // device is in fixed block size mode with correct block size - } - - // Only root can set driver options, so we cannot - // handle.set_default_options()?; - - Ok(handle) - }).map_err(|err| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err)) - } -} - -/// Linux Tape device handle -pub struct LinuxTapeHandle { - file: File, - //_lock: File, -} - -impl LinuxTapeHandle { - - /// Creates a new instance - pub fn new(file: File) -> Self { - Self { file } - } - - /// Set all options we need/want - pub fn set_default_options(&self) -> Result<(), Error> { - - let mut opts = SetDrvBufferOptions::empty(); - - // fixme: ? man st(4) claims we need to clear this for reliable multivolume - opts.set(SetDrvBufferOptions::BUFFER_WRITES, true); - - // fixme: ?man st(4) claims we need to clear this for reliable multivolume - opts.set(SetDrvBufferOptions::ASYNC_WRITES, true); - - opts.set(SetDrvBufferOptions::READ_AHEAD, true); - - self.set_drive_buffer_options(opts) - } - - /// call MTSETDRVBUFFER to set boolean options - /// - /// Note: this uses MT_ST_BOOLEANS, so missing options are cleared! - pub fn set_drive_buffer_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { - - let cmd = mtop { - mt_op: MTCmd::MTSETDRVBUFFER, - mt_count: (SetDrvBufferCmd::MT_ST_BOOLEANS as i32) | opts.bits(), - }; - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; - - Ok(()) - } - - /// call MTSETDRVBUFFER to set boolean options - /// - /// Note: this uses MT_ST_SETBOOLEANS - pub fn drive_buffer_set_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { - - let cmd = mtop { - mt_op: MTCmd::MTSETDRVBUFFER, - mt_count: (SetDrvBufferCmd::MT_ST_SETBOOLEANS as i32) | opts.bits(), - }; - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; - - Ok(()) - } - - /// call MTSETDRVBUFFER to clear boolean options - pub fn drive_buffer_clear_options(&self, opts: SetDrvBufferOptions) -> Result<(), Error> { - - let cmd = mtop { - mt_op: MTCmd::MTSETDRVBUFFER, - mt_count: (SetDrvBufferCmd::MT_ST_CLEARBOOLEANS as i32) | opts.bits(), - }; - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTSETDRVBUFFER options failed - {}", err))?; - - Ok(()) - } - - /// This flushes the driver's buffer as a side effect. Should be - /// used before reading status with MTIOCGET. - fn mtnop(&self) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTNOP, mt_count: 1, }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTNOP failed - {}", err))?; - - Ok(()) - } - - pub fn mtop(&mut self, mt_op: MTCmd, mt_count: i32, msg: &str) -> Result<(), Error> { - let cmd = mtop { mt_op, mt_count }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("{} failed (count {}) - {}", msg, mt_count, err))?; - - Ok(()) - } - - pub fn mtload(&mut self) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTLOAD, mt_count: 1, }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTLOAD failed - {}", err))?; - - Ok(()) - } - - /// Set tape compression feature - pub fn set_compression(&self, on: bool) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTCOMPRESSION, mt_count: if on { 1 } else { 0 } }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("set compression to {} failed - {}", on, err))?; - - Ok(()) - } - - /// Write a single EOF mark without flushing buffers - pub fn write_eof_mark(&mut self) -> Result<(), std::io::Error> { - tape_write_eof_mark(&mut self.file) - } - - /// Set the drive's block length to the value specified. - /// - /// A block length of zero sets the drive to variable block - /// size mode. - pub fn set_block_size(&self, block_length: usize) -> Result<(), Error> { - - if block_length > 256*1024*1024 { - bail!("block_length too large (> max linux scsii block length)"); - } - - let cmd = mtop { mt_op: MTCmd::MTSETBLK, mt_count: block_length as i32 }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTSETBLK failed - {}", err))?; - - Ok(()) - } - - /// Get Tape and Media status - pub fn get_drive_and_media_status(&mut self) -> Result { - - let drive_status = self.get_drive_status()?; - - let options = read_tapedev_options(&self.file)?; - - let alert_flags = self.tape_alert_flags() - .map(|flags| format!("{:?}", flags)) - .ok(); - - let mut status = LinuxDriveAndMediaStatus { - blocksize: drive_status.blocksize, - density: drive_status.density, - status: format!("{:?}", drive_status.status), - options: format!("{:?}", options), - alert_flags, - file_number: drive_status.file_number, - block_number: drive_status.block_number, - manufactured: None, - bytes_read: None, - bytes_written: None, - medium_passes: None, - medium_wearout: None, - volume_mounts: None, - }; - - if drive_status.tape_is_ready() { - - if let Ok(mam) = self.cartridge_memory() { - - let usage = mam_extract_media_usage(&mam)?; - - status.manufactured = Some(usage.manufactured); - status.bytes_read = Some(usage.bytes_read); - status.bytes_written = Some(usage.bytes_written); - - if let Ok(volume_stats) = self.volume_statistics() { - - let passes = std::cmp::max( - volume_stats.beginning_of_medium_passes, - volume_stats.middle_of_tape_passes, - ); - - // assume max. 16000 medium passes - // see: https://en.wikipedia.org/wiki/Linear_Tape-Open - let wearout: f64 = (passes as f64)/(16000.0 as f64); - - status.medium_passes = Some(passes); - status.medium_wearout = Some(wearout); - - status.volume_mounts = Some(volume_stats.volume_mounts); - } - } - } - - Ok(status) - } - - /// Get Tape status/configuration with MTIOCGET ioctl - pub fn get_drive_status(&mut self) -> Result { - - let _ = self.mtnop(); // ignore errors (i.e. no tape loaded) - - let mut status = mtget::default(); - - if let Err(err) = unsafe { mtiocget(self.file.as_raw_fd(), &mut status) } { - bail!("MTIOCGET failed - {}", err); - } - - let gmt = GMTStatusFlags::from_bits_truncate(status.mt_gstat); - - let blocksize; - - if status.mt_type == MT_TYPE_ISSCSI1 || status.mt_type == MT_TYPE_ISSCSI2 { - blocksize = ((status.mt_dsreg & MT_ST_BLKSIZE_MASK) >> MT_ST_BLKSIZE_SHIFT) as u32; - } else { - bail!("got unsupported tape type {}", status.mt_type); - } - - let density = ((status.mt_dsreg & MT_ST_DENSITY_MASK) >> MT_ST_DENSITY_SHIFT) as u8; - - Ok(LinuxDriveStatus { - blocksize, - status: gmt, - density: if density != 0 { - Some(TapeDensity::try_from(density)?) - } else { - None - }, - file_number: if status.mt_fileno > 0 { - Some(status.mt_fileno as u32) - } else { - None - }, - block_number: if status.mt_blkno > 0 { - Some(status.mt_blkno as u32) - } else { - None - }, - }) - } - - /// Read Cartridge Memory (MAM Attributes) - /// - /// Note: Only 'root' user may run RAW SG commands, so we need to - /// spawn setuid binary 'sg-tape-cmd'. - pub fn cartridge_memory(&mut self) -> Result, Error> { - - if nix::unistd::Uid::effective().is_root() { - return read_mam_attributes(&mut self.file); - } - - let output = run_sg_tape_cmd("cartridge-memory", &[], self.file.as_raw_fd())?; - let result: Result, String> = serde_json::from_str(&output)?; - result.map_err(|err| format_err!("{}", err)) - } - - /// Read Volume Statistics - /// - /// Note: Only 'root' user may run RAW SG commands, so we need to - /// spawn setuid binary 'sg-tape-cmd'. - pub fn volume_statistics(&mut self) -> Result { - - if nix::unistd::Uid::effective().is_root() { - return read_volume_statistics(&mut self.file); - } - - let output = run_sg_tape_cmd("volume-statistics", &[], self.file.as_raw_fd())?; - let result: Result = serde_json::from_str(&output)?; - result.map_err(|err| format_err!("{}", err)) - } -} - - -impl TapeDriver for LinuxTapeHandle { - - fn sync(&mut self) -> Result<(), Error> { - - // MTWEOF with count 0 => flush - let cmd = mtop { mt_op: MTCmd::MTWEOF, mt_count: 0 }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| proxmox::io_format_err!("MT sync failed - {}", err))?; - - Ok(()) - } - - /// Go to the end of the recorded media (for appending files). - fn move_to_eom(&mut self) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTEOM, mt_count: 1, }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTEOM failed - {}", err))?; - - - Ok(()) - } - - fn forward_space_count_files(&mut self, count: usize) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTFSF, mt_count: i32::try_from(count)? }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| { - format_err!("forward space {} files failed - {}", count, err) - })?; - - Ok(()) - } - - fn backward_space_count_files(&mut self, count: usize) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTBSF, mt_count: i32::try_from(count)? }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| { - format_err!("backward space {} files failed - {}", count, err) - })?; - - Ok(()) - } - - fn rewind(&mut self) -> Result<(), Error> { - - let cmd = mtop { mt_op: MTCmd::MTREW, mt_count: 1, }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("tape rewind failed - {}", err))?; - - Ok(()) - } - - fn current_file_number(&mut self) -> Result { - let mut status = mtget::default(); - - self.mtnop()?; - - if let Err(err) = unsafe { mtiocget(self.file.as_raw_fd(), &mut status) } { - bail!("current_file_number MTIOCGET failed - {}", err); - } - - if status.mt_fileno < 0 { - bail!("current_file_number failed (got {})", status.mt_fileno); - } - Ok(status.mt_fileno as u64) - } - - fn erase_media(&mut self, fast: bool) -> Result<(), Error> { - - self.rewind()?; // important - erase from BOT - - let cmd = mtop { mt_op: MTCmd::MTERASE, mt_count: if fast { 0 } else { 1 } }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTERASE failed - {}", err))?; - - Ok(()) - } - - fn read_next_file<'a>(&'a mut self) -> Result>, std::io::Error> { - let reader = LinuxTapeReader::new(&mut self.file); - match BlockedReader::open(reader)? { - Some(reader) => Ok(Some(Box::new(reader))), - None => Ok(None), - } - } - - fn write_file<'a>(&'a mut self) -> Result, std::io::Error> { - - let writer = LinuxTapeWriter::new(&mut self.file); - let handle = BlockedWriter::new(writer); - Ok(Box::new(handle)) - } - - fn write_media_set_label( - &mut self, - media_set_label: &MediaSetLabel, - key_config: Option<&KeyConfig>, - ) -> Result<(), Error> { - - let file_number = self.current_file_number()?; - if file_number != 1 { - self.rewind()?; - self.forward_space_count_files(1)?; // skip label - } - - let file_number = self.current_file_number()?; - if file_number != 1 { - bail!("write_media_set_label failed - got wrong file number ({} != 1)", file_number); - } - - self.set_encryption(None)?; - - { // limit handle scope - let mut handle = self.write_file()?; - - let mut value = serde_json::to_value(media_set_label)?; - if media_set_label.encryption_key_fingerprint.is_some() { - match key_config { - Some(key_config) => { - value["key-config"] = serde_json::to_value(key_config)?; - } - None => { - bail!("missing encryption key config"); - } - } - } - - let raw = serde_json::to_string_pretty(&value)?; - - let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32); - handle.write_header(&header, raw.as_bytes())?; - handle.finish(false)?; - } - - self.sync()?; // sync data to tape - - Ok(()) - } - - /// Rewind and put the drive off line (Eject media). - fn eject_media(&mut self) -> Result<(), Error> { - let cmd = mtop { mt_op: MTCmd::MTOFFL, mt_count: 1 }; - - unsafe { - mtioctop(self.file.as_raw_fd(), &cmd) - }.map_err(|err| format_err!("MTOFFL failed - {}", err))?; - - Ok(()) - } - - /// Read Tape Alert Flags - /// - /// Note: Only 'root' user may run RAW SG commands, so we need to - /// spawn setuid binary 'sg-tape-cmd'. - fn tape_alert_flags(&mut self) -> Result { - - if nix::unistd::Uid::effective().is_root() { - return read_tape_alert_flags(&mut self.file); - } - - let output = run_sg_tape_cmd("tape-alert-flags", &[], self.file.as_raw_fd())?; - let result: Result = serde_json::from_str(&output)?; - result - .map_err(|err| format_err!("{}", err)) - .map(TapeAlertFlags::from_bits_truncate) - } - - /// Set or clear encryption key - /// - /// Note: Only 'root' user may run RAW SG commands, so we need to - /// spawn setuid binary 'sg-tape-cmd'. Also, encryption key file - /// is only readable by root. - fn set_encryption( - &mut self, - key_fingerprint: Option<(Fingerprint, Uuid)>, - ) -> Result<(), Error> { - - if nix::unistd::Uid::effective().is_root() { - - if let Some((ref key_fingerprint, ref uuid)) = key_fingerprint { - - let (key_map, _digest) = config::tape_encryption_keys::load_keys()?; - match key_map.get(key_fingerprint) { - Some(item) => { - - // derive specialized key for each media-set - - let mut tape_key = [0u8; 32]; - - let uuid_bytes: [u8; 16] = uuid.as_bytes().clone(); - - openssl::pkcs5::pbkdf2_hmac( - &item.key, - &uuid_bytes, - 10, - openssl::hash::MessageDigest::sha256(), - &mut tape_key)?; - - return set_encryption(&mut self.file, Some(tape_key)); - } - None => bail!("unknown tape encryption key '{}'", key_fingerprint), - } - } else { - return set_encryption(&mut self.file, None); - } - } - - let output = if let Some((fingerprint, uuid)) = key_fingerprint { - let fingerprint = crate::tools::format::as_fingerprint(fingerprint.bytes()); - run_sg_tape_cmd("encryption", &[ - "--fingerprint", &fingerprint, - "--uuid", &uuid.to_string(), - ], self.file.as_raw_fd())? - } else { - run_sg_tape_cmd("encryption", &[], self.file.as_raw_fd())? - }; - let result: Result<(), String> = serde_json::from_str(&output)?; - result.map_err(|err| format_err!("{}", err)) - } -} - -/// Write a single EOF mark without flushing buffers -fn tape_write_eof_mark(file: &mut File) -> Result<(), std::io::Error> { - - let cmd = mtop { mt_op: MTCmd::MTWEOFI, mt_count: 1 }; - - unsafe { - mtioctop(file.as_raw_fd(), &cmd) - }.map_err(|err| proxmox::io_format_err!("MTWEOFI failed - {}", err))?; - - Ok(()) -} - -/// Check for correct Major/Minor numbers -pub fn check_tape_is_linux_tape_device(file: &File) -> Result<(), Error> { - - let stat = nix::sys::stat::fstat(file.as_raw_fd())?; - - let devnum = stat.st_rdev; - - let major = unsafe { libc::major(devnum) }; - let minor = unsafe { libc::minor(devnum) }; - - if major != 9 { - bail!("not a tape device"); - } - if (minor & 128) == 0 { - bail!("Detected rewinding tape. Please use non-rewinding tape devices (/dev/nstX)."); - } - - Ok(()) -} - -/// Opens a Linux tape device -/// -/// The open call use O_NONBLOCK, but that flag is cleard after open -/// succeeded. This also checks if the device is a non-rewinding tape -/// device. -pub fn open_linux_tape_device( - path: &str, -) -> Result { - - let file = OpenOptions::new() - .read(true) - .write(true) - .custom_flags(libc::O_NONBLOCK) - .open(path)?; - - // clear O_NONBLOCK from now on. - - let flags = fcntl(file.as_raw_fd(), FcntlArg::F_GETFL) - .into_io_result()?; - - let mut flags = OFlag::from_bits_truncate(flags); - flags.remove(OFlag::O_NONBLOCK); - - fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(flags)) - .into_io_result()?; - - check_tape_is_linux_tape_device(&file) - .map_err(|err| format_err!("device type check {:?} failed - {}", path, err))?; - - Ok(file) -} - -/// Read Linux tape device options from /sys -pub fn read_tapedev_options(file: &File) -> Result { - - let stat = nix::sys::stat::fstat(file.as_raw_fd())?; - - let devnum = stat.st_rdev; - - let major = unsafe { libc::major(devnum) }; - let minor = unsafe { libc::minor(devnum) }; - - let path = format!("/sys/dev/char/{}:{}/options", major, minor); - - let options = proxmox::tools::fs::file_read_firstline(&path)?; - - let options = options.trim(); - - let options = match options.strip_prefix("0x") { - Some(rest) => rest, - None => bail!("unable to parse '{}'", path), - }; - - let options = i32::from_str_radix(&options, 16)?; - - Ok(SetDrvBufferOptions::from_bits_truncate(options)) -} - - -struct LinuxTapeWriter<'a> { - /// Assumes that 'file' is a linux tape device. - file: &'a mut File, -} - -impl <'a> LinuxTapeWriter<'a> { - pub fn new(file: &'a mut File) -> Self { - Self { file } - } -} - -impl <'a> BlockWrite for LinuxTapeWriter<'a> { - - /// Write a single block to a linux tape device - /// - /// EOM Behaviour on Linux: When the end of medium early warning is - /// encountered, the current write is finished and the number of bytes - /// is returned. The next write returns -1 and errno is set to - /// ENOSPC. To enable writing a trailer, the next write is allowed to - /// proceed and, if successful, the number of bytes is returned. After - /// this, -1 and the number of bytes are alternately returned until - /// the physical end of medium (or some other error) is encountered. - /// - /// See: https://github.com/torvalds/linux/blob/master/Documentation/scsi/st.rst - /// - /// On success, this returns if we en countered a EOM condition. - fn write_block(&mut self, data: &[u8]) -> Result { - - let mut leof = false; - - loop { - match self.file.write(data) { - Ok(count) if count == data.len() => return Ok(leof), - Ok(count) if count > 0 => { - proxmox::io_bail!( - "short block write ({} < {}). Tape drive uses wrong block size.", - count, data.len()); - } - Ok(_) => { // count is 0 here, assume EOT - return Err(std::io::Error::from_raw_os_error(nix::errno::Errno::ENOSPC as i32)); - } - // handle interrupted system call - Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { - continue; - } - // detect and handle LEOM (early warning) - Err(err) if err.is_errno(nix::errno::Errno::ENOSPC) => { - if leof { - return Err(err); - } else { - leof = true; - continue; // next write will succeed - } - } - Err(err) => return Err(err), - } - } - } - - fn write_filemark(&mut self) -> Result<(), std::io::Error> { - tape_write_eof_mark(&mut self.file) - } -} - -pub struct LinuxTapeReader<'a> { - /// Assumes that 'file' is a linux tape device. - file: &'a mut File, - got_eof: bool, -} - -impl <'a> LinuxTapeReader<'a> { - - pub fn new(file: &'a mut File) -> Self { - Self { file, got_eof: false } - } -} - -impl <'a> BlockRead for LinuxTapeReader<'a> { - - /// Read a single block from a linux tape device - /// - /// Return true on success, false on EOD - fn read_block(&mut self, buffer: &mut [u8]) -> Result { - loop { - match self.file.read(buffer) { - Ok(0) => { - let eod = self.got_eof; - self.got_eof = true; - if eod { - return Ok(BlockReadStatus::EndOfStream); - } else { - return Ok(BlockReadStatus::EndOfFile); - } - } - Ok(count) => { - if count == buffer.len() { - return Ok(BlockReadStatus::Ok(count)); - } - proxmox::io_bail!("short block read ({} < {}). Tape drive uses wrong block size.", - count, buffer.len()); - } - // handle interrupted system call - Err(err) if err.kind() == std::io::ErrorKind::Interrupted => { - continue; - } - Err(err) => return Err(err), - } - } - } -} diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index cd02e16d..920ae89f 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -2,8 +2,6 @@ mod virtual_tape; -pub mod linux_mtio; - mod tape_alert_flags; pub use tape_alert_flags::*;