diff --git a/src/api2/tape/backup.rs b/src/api2/tape/backup.rs index 0a169ff6..349ef304 100644 --- a/src/api2/tape/backup.rs +++ b/src/api2/tape/backup.rs @@ -15,10 +15,7 @@ use proxmox::{ use crate::{ task_log, - config::{ - self, - drive::check_drive_exists, - }, + config, backup::{ DataStore, BackupDir, @@ -40,7 +37,10 @@ use crate::{ PoolWriter, MediaPool, SnapshotReader, - drive::media_changer, + drive::{ + media_changer, + lock_tape_device, + }, changer::update_changer_online_status, }, }; @@ -91,8 +91,9 @@ pub fn backup( let pool_config: MediaPoolConfig = config.lookup("pool", &pool)?; let (drive_config, _digest) = config::drive::config()?; - // early check before starting worker - check_drive_exists(&drive_config, &drive)?; + + // early check/lock before starting worker + let drive_lock = lock_tape_device(&drive_config, &drive)?; let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; @@ -105,6 +106,7 @@ pub fn backup( auth_id, to_stdout, move |worker| { + let _drive_lock = drive_lock; // keep lock guard backup_worker(&worker, datastore, &drive, &pool_config, eject_media, export_media_set)?; Ok(()) } diff --git a/src/api2/tape/drive.rs b/src/api2/tape/drive.rs index 494f28aa..29321601 100644 --- a/src/api2/tape/drive.rs +++ b/src/api2/tape/drive.rs @@ -12,6 +12,7 @@ use proxmox::{ sys::error::SysError, api::{ api, + section_config::SectionConfigData, RpcEnvironment, RpcEnvironmentType, Router, @@ -20,10 +21,7 @@ use proxmox::{ }; use crate::{ - config::{ - self, - drive::check_drive_exists, - }, + config, api2::{ types::{ UPID_SCHEMA, @@ -59,9 +57,10 @@ use crate::{ LinuxTapeHandle, Lp17VolumeStatistics, open_linux_tape_device, - media_changer, + media_changer, required_media_changer, open_drive, + lock_tape_device, }, changer::update_changer_online_status, }, @@ -205,7 +204,8 @@ pub fn erase_media( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -217,6 +217,8 @@ pub fn erase_media( auth_id, to_stdout, move |_worker| { + let _lock_guard = lock_guard; // keep lock guard + let mut drive = open_drive(&config, &drive)?; drive.erase_media(fast.unwrap_or(true))?; Ok(()) @@ -246,7 +248,8 @@ pub fn rewind( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -258,6 +261,7 @@ pub fn rewind( auth_id, to_stdout, move |_worker| { + let _lock_guard = lock_guard; // keep lock guard let mut drive = open_drive(&config, &drive)?; drive.rewind()?; Ok(()) @@ -287,7 +291,8 @@ pub fn eject_media( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -299,7 +304,9 @@ pub fn eject_media( auth_id, to_stdout, move |_worker| { - if let Some((mut changer, _)) = media_changer(&config, &drive)? { + let _lock_guard = lock_guard; // keep lock guard + + if let Some((mut changer, _)) = media_changer(&config, &drive)? { changer.unload_media(None)?; } else { let mut drive = open_drive(&config, &drive)?; @@ -355,6 +362,9 @@ pub fn label_media( let (config, _digest) = config::drive::config()?; + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; + let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; let upid_str = WorkerTask::new_thread( @@ -363,6 +373,7 @@ pub fn label_media( auth_id, to_stdout, move |worker| { + let _lock_guard = lock_guard; // keep lock guard let mut drive = open_drive(&config, &drive)?; @@ -479,7 +490,12 @@ pub async fn restore_key( let (config, _digest) = config::drive::config()?; + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; + tokio::task::spawn_blocking(move || { + let _lock_guard = lock_guard; // keep lock guard + let mut drive = open_drive(&config, &drive)?; let (_media_id, key_config) = drive.read_label()?; @@ -520,7 +536,12 @@ pub async fn read_label( let (config, _digest) = config::drive::config()?; + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; + tokio::task::spawn_blocking(move || { + let _lock_guard = lock_guard; // keep lock guard + let mut drive = open_drive(&config, &drive)?; let (media_id, _key_config) = drive.read_label()?; @@ -593,7 +614,8 @@ pub fn clean_drive( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -605,6 +627,7 @@ pub fn clean_drive( auth_id, to_stdout, move |worker| { + let _lock_guard = lock_guard; // keep lock guard let (mut changer, _changer_name) = required_media_changer(&config, &drive)?; @@ -649,7 +672,12 @@ pub async fn inventory( let (config, _digest) = config::drive::config()?; + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; + tokio::task::spawn_blocking(move || { + let _lock_guard = lock_guard; // keep lock guard + let (mut changer, changer_name) = required_media_changer(&config, &drive)?; let label_text_list = changer.online_media_label_texts()?; @@ -720,7 +748,8 @@ pub fn update_inventory( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -732,6 +761,7 @@ pub fn update_inventory( auth_id, to_stdout, move |worker| { + let _lock_guard = lock_guard; // keep lock guard let (mut changer, changer_name) = required_media_changer(&config, &drive)?; @@ -822,6 +852,11 @@ pub fn barcode_label_media( } } + let (drive_config, _digest) = config::drive::config()?; + + // early check/lock before starting worker + let lock_guard = lock_tape_device(&drive_config, &drive)?; + let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; @@ -832,7 +867,8 @@ pub fn barcode_label_media( auth_id, to_stdout, move |worker| { - barcode_label_media_worker(worker, drive, pool) + let _lock_guard = lock_guard; // keep lock guard + barcode_label_media_worker(worker, drive, &drive_config, pool) } )?; @@ -842,12 +878,11 @@ pub fn barcode_label_media( fn barcode_label_media_worker( worker: Arc, drive: String, + drive_config: &SectionConfigData, pool: Option, ) -> Result<(), Error> { - let (config, _digest) = config::drive::config()?; - - let (mut changer, changer_name) = required_media_changer(&config, &drive)?; + let (mut changer, changer_name) = required_media_changer(drive_config, &drive)?; let label_text_list = changer.online_media_label_texts()?; @@ -855,7 +890,7 @@ fn barcode_label_media_worker( let mut inventory = Inventory::load(state_path)?; - update_changer_online_status(&config, &mut inventory, &changer_name, &label_text_list)?; + update_changer_online_status(drive_config, &mut inventory, &changer_name, &label_text_list)?; if label_text_list.is_empty() { bail!("changer device does not list any media labels"); @@ -877,7 +912,7 @@ fn barcode_label_media_worker( continue; } - let mut drive = open_drive(&config, &drive)?; + let mut drive = open_drive(drive_config, &drive)?; drive.rewind()?; match drive.read_next_file() { @@ -930,6 +965,8 @@ pub fn cartridge_memory(drive: String) -> Result, Error> { let (config, _digest) = config::drive::config()?; + let _lock_guard = lock_tape_device(&config, &drive)?; + let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; let mut handle = drive_config.open()?; @@ -953,6 +990,8 @@ pub fn volume_statistics(drive: String) -> Result { let (config, _digest) = config::drive::config()?; + let _lock_guard = lock_tape_device(&config, &drive)?; + let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; let mut handle = drive_config.open()?; @@ -976,6 +1015,8 @@ pub fn status(drive: String) -> Result { let (config, _digest) = config::drive::config()?; + let _lock_guard = lock_tape_device(&config, &drive)?; + let drive_config: LinuxTapeDrive = config.lookup("linux", &drive)?; // Note: use open_linux_tape_device, because this also works if no medium loaded @@ -1021,7 +1062,8 @@ pub fn catalog_media( let (config, _digest) = config::drive::config()?; - check_drive_exists(&config, &drive)?; // early check before starting worker + // early check/lock before starting worker + let lock_guard = lock_tape_device(&config, &drive)?; let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?; @@ -1033,6 +1075,7 @@ pub fn catalog_media( auth_id, to_stdout, move |worker| { + let _lock_guard = lock_guard; // keep lock guard let mut drive = open_drive(&config, &drive)?; diff --git a/src/api2/tape/restore.rs b/src/api2/tape/restore.rs index f045465d..498b49df 100644 --- a/src/api2/tape/restore.rs +++ b/src/api2/tape/restore.rs @@ -34,10 +34,7 @@ use crate::{ Authid, MediaPoolConfig, }, - config::{ - self, - drive::check_drive_exists, - }, + config, backup::{ archive_type, MANIFEST_BLOB_NAME, @@ -71,7 +68,8 @@ use crate::{ drive::{ TapeDriver, request_and_load_media, - } + lock_tape_device, + }, }, }; @@ -122,8 +120,9 @@ pub fn restore( let _pool_config: MediaPoolConfig = config.lookup("pool", &pool)?; let (drive_config, _digest) = config::drive::config()?; - // early check before starting worker - check_drive_exists(&drive_config, &drive)?; + + // early check/lock before starting worker + let drive_lock = lock_tape_device(&drive_config, &drive)?; let to_stdout = rpcenv.env_type() == RpcEnvironmentType::CLI; @@ -133,6 +132,7 @@ pub fn restore( auth_id.clone(), to_stdout, move |worker| { + let _drive_lock = drive_lock; // keep lock guard let _lock = MediaPool::lock(status_path, &pool)?; diff --git a/src/bin/proxmox-tape.rs b/src/bin/proxmox-tape.rs index f30d902e..69db8f3f 100644 --- a/src/bin/proxmox-tape.rs +++ b/src/bin/proxmox-tape.rs @@ -40,7 +40,10 @@ use proxmox_backup::{ media_pool::complete_pool_name, }, tape::{ - drive::open_drive, + drive::{ + open_drive, + lock_tape_device, + }, complete_media_label_text, complete_media_set_uuid, file_formats::{ @@ -518,6 +521,9 @@ fn move_to_eom(param: Value) -> Result<(), Error> { let (config, _digest) = config::drive::config()?; let drive = lookup_drive_name(¶m, &config)?; + + let _lock = lock_tape_device(&config, &drive)?; + let mut drive = open_drive(&config, &drive)?; drive.move_to_eom()?; @@ -544,6 +550,9 @@ fn debug_scan(param: Value) -> Result<(), Error> { let (config, _digest) = config::drive::config()?; let drive = lookup_drive_name(¶m, &config)?; + + let _lock = lock_tape_device(&config, &drive)?; + let mut drive = open_drive(&config, &drive)?; println!("rewinding tape"); diff --git a/src/tape/drive/mod.rs b/src/tape/drive/mod.rs index 22e1ecd2..e44e0e36 100644 --- a/src/tape/drive/mod.rs +++ b/src/tape/drive/mod.rs @@ -19,6 +19,8 @@ pub use linux_tape::*; mod mam; pub use mam::*; +use std::os::unix::io::AsRawFd; + use anyhow::{bail, format_err, Error}; use ::serde::{Deserialize}; use serde_json::Value; @@ -27,6 +29,7 @@ use proxmox::{ tools::{ Uuid, io::ReadExt, + fs::fchown, }, api::section_config::SectionConfigData, }; @@ -442,3 +445,54 @@ pub fn request_and_load_media( } } } + +/// Aquires an exclusive lock for the tape device +/// +/// Basically calls lock_device_path() using the configured drive path. +pub fn lock_tape_device( + config: &SectionConfigData, + drive: &str, +) -> Result { + + match config.sections.get(drive) { + Some((section_type_name, config)) => { + let path = match section_type_name.as_ref() { + "virtual" => { + VirtualTapeDrive::deserialize(config)?.path + } + "linux" => { + LinuxTapeDrive::deserialize(config)?.path + } + _ => bail!("unknown drive type '{}' - internal error"), + }; + lock_device_path(&path) + .map_err(|err| format_err!("unable to lock drive '{}' - {}", drive, err)) + } + None => { + bail!("no such drive '{}'", drive); + } + } +} + +pub struct DeviceLockGuard(std::fs::File); + +// Aquires an exclusive lock on `device_path` +// +// Uses systemd escape_unit to compute a file name from `device_path`, the try +// to lock `/var/lock/`. +fn lock_device_path(device_path: &str) -> Result { + + let lock_name = crate::tools::systemd::escape_unit(device_path, true); + + let mut path = std::path::PathBuf::from("/var/lock"); + path.push(lock_name); + + let timeout = std::time::Duration::new(10, 0); + let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)?; + proxmox::tools::fs::lock_file(&mut file, true, Some(timeout))?; + + let backup_user = crate::backup::backup_user()?; + fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?; + + Ok(DeviceLockGuard(file)) +}