8 use std
::path
::PathBuf
;
10 use anyhow
::{bail, format_err, Error}
;
11 use serde
::Deserialize
;
12 use serde_json
::Value
;
13 use nix
::fcntl
::OFlag
;
14 use nix
::sys
::stat
::Mode
;
20 atomic_open_or_create_file
,
21 file_read_optional_string
,
27 use proxmox_io
::ReadExt
;
28 use proxmox_section_config
::SectionConfigData
;
29 use proxmox_uuid
::Uuid
;
30 use proxmox_sys
::{task_log, worker_task_context::WorkerTaskContext}
;
32 use pbs_api_types
::{VirtualTapeDrive, LtoTapeDrive, Fingerprint}
;
33 use pbs_config
::key_config
::KeyConfig
;
36 TapeWrite
, TapeRead
, BlockReadError
, MediaContentHeader
,
37 sg_tape
::TapeAlertFlags
,
41 server
::send_load_media_email
,
45 virtual_tape
::open_virtual_tape_drive
,
48 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
49 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
60 /// Tape driver interface
61 pub trait TapeDriver
{
63 /// Flush all data to the tape
64 fn sync(&mut self) -> Result
<(), Error
>;
67 fn rewind(&mut self) -> Result
<(), Error
>;
69 /// Move to end of recorded data
71 /// We assume this flushes the tape write buffer. if
72 /// write_missing_eof is true, we verify that there is a filemark
73 /// at the end. If not, we write one.
74 fn move_to_eom(&mut self, write_missing_eof
: bool
) -> Result
<(), Error
>;
77 fn move_to_last_file(&mut self) -> Result
<(), Error
>;
79 /// Move to given file nr
80 fn move_to_file(&mut self, file
: u64) -> Result
<(), Error
>;
82 /// Current file number
83 fn current_file_number(&mut self) -> Result
<u64, Error
>;
85 /// Completely erase the media
86 fn format_media(&mut self, fast
: bool
) -> Result
<(), Error
>;
88 /// Read/Open the next file
89 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeRead
+ 'a
>, BlockReadError
>;
91 /// Write/Append a new file
92 fn write_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeWrite
+ 'a
>, std
::io
::Error
>;
94 /// Write label to tape (erase tape content)
95 fn label_tape(&mut self, label
: &MediaLabel
) -> Result
<(), Error
> {
97 self.set_encryption(None
)?
;
99 self.format_media(true)?
; // this rewinds the tape
101 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(&label
)?
)?
;
103 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, raw
.len() as u32);
106 let mut writer
= self.write_file()?
;
107 writer
.write_header(&header
, raw
.as_bytes())?
;
108 writer
.finish(false)?
;
111 self.sync()?
; // sync data to tape
116 /// Write the media set label to tape
118 /// If the media-set is encrypted, we also store the encryption
119 /// key_config, so that it is possible to restore the key.
120 fn write_media_set_label(
122 media_set_label
: &MediaSetLabel
,
123 key_config
: Option
<&KeyConfig
>,
124 ) -> Result
<(), Error
>;
126 /// Read the media label
128 /// This tries to read both media labels (label and
129 /// media_set_label). Also returns the optional encryption key configuration.
130 fn read_label(&mut self) -> Result
<(Option
<MediaId
>, Option
<KeyConfig
>), Error
> {
135 let mut reader
= match self.read_next_file() {
136 Err(BlockReadError
::EndOfStream
) => {
137 return Ok((None
, None
)); // tape is empty
139 Err(BlockReadError
::EndOfFile
) => {
140 bail
!("got unexpected filemark at BOT");
142 Err(BlockReadError
::Error(err
)) => {
143 return Err(err
.into());
145 Ok(reader
) => reader
,
148 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
149 header
.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
150 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
152 let label
: MediaLabel
= serde_json
::from_slice(&data
)
153 .map_err(|err
| format_err
!("unable to parse drive label - {}", err
))?
;
155 // make sure we read the EOF marker
156 if reader
.skip_to_end()?
!= 0 {
157 bail
!("got unexpected data after label");
163 let mut media_id
= MediaId { label, media_set_label: None }
;
165 // try to read MediaSet label
166 let mut reader
= match self.read_next_file() {
167 Err(BlockReadError
::EndOfStream
) => {
168 return Ok((Some(media_id
), None
));
170 Err(BlockReadError
::EndOfFile
) => {
171 bail
!("got unexpected filemark after label");
173 Err(BlockReadError
::Error(err
)) => {
174 return Err(err
.into());
176 Ok(reader
) => reader
,
179 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
180 header
.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
181 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
183 let mut data
: Value
= serde_json
::from_slice(&data
)
184 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
186 let key_config_value
= data
["key-config"].take();
187 let key_config
: Option
<KeyConfig
> = if !key_config_value
.is_null() {
188 Some(serde_json
::from_value(key_config_value
)?
)
193 let media_set_label
: MediaSetLabel
= serde_json
::from_value(data
)
194 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
196 // make sure we read the EOF marker
197 if reader
.skip_to_end()?
!= 0 {
198 bail
!("got unexpected data after media set label");
201 media_id
.media_set_label
= Some(media_set_label
);
203 Ok((Some(media_id
), key_config
))
207 fn eject_media(&mut self) -> Result
<(), Error
>;
209 /// Read Tape Alert Flags
211 /// This make only sense for real LTO drives. Virtual tape drives should
212 /// simply return empty flags (default).
213 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
214 Ok(TapeAlertFlags
::empty())
217 /// Set or clear encryption key
219 /// We use the media_set_uuid to XOR the secret key with the
220 /// uuid (first 16 bytes), so that each media set uses an unique
221 /// key for encryption.
224 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
225 ) -> Result
<(), Error
> {
226 if key_fingerprint
.is_some() {
227 bail
!("drive does not support encryption");
233 /// Get the media changer (MediaChange + name) associated with a tape drive.
235 /// Returns Ok(None) if the drive has no associated changer device.
237 /// Note: This may return the drive name as changer-name if the drive
238 /// implements some kind of internal changer (which is true for our
239 /// 'virtual' drive implementation).
240 pub fn media_changer(
241 config
: &SectionConfigData
,
243 ) -> Result
<Option
<(Box
<dyn MediaChange
>, String
)>, Error
> {
245 match config
.sections
.get(drive
) {
246 Some((section_type_name
, config
)) => {
247 match section_type_name
.as_ref() {
249 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
250 Ok(Some((Box
::new(tape
), drive
.to_string())))
253 let drive_config
= LtoTapeDrive
::deserialize(config
)?
;
254 match drive_config
.changer
{
255 Some(ref changer_name
) => {
256 let changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
257 let changer_name
= changer_name
.to_string();
258 Ok(Some((Box
::new(changer
), changer_name
)))
263 _
=> bail
!("unknown drive type '{}' - internal error"),
267 bail
!("no such drive '{}'", drive
);
272 /// Get the media changer (MediaChange + name) associated with a tape drive.
274 /// This fail if the drive has no associated changer device.
275 pub fn required_media_changer(
276 config
: &SectionConfigData
,
278 ) -> Result
<(Box
<dyn MediaChange
>, String
), Error
> {
279 match media_changer(config
, drive
) {
280 Ok(Some(result
)) => {
284 bail
!("drive '{}' has no associated changer device", drive
);
292 /// Opens a tape drive (this fails if there is no media loaded)
294 config
: &SectionConfigData
,
296 ) -> Result
<Box
<dyn TapeDriver
>, Error
> {
298 match config
.sections
.get(drive
) {
299 Some((section_type_name
, config
)) => {
300 match section_type_name
.as_ref() {
302 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
303 let handle
= open_virtual_tape_drive(&tape
)?
;
307 let tape
= LtoTapeDrive
::deserialize(config
)?
;
308 let handle
= open_lto_tape_drive(&tape
)?
;
311 _
=> bail
!("unknown drive type '{}' - internal error"),
315 bail
!("no such drive '{}'", drive
);
320 #[derive(PartialEq, Eq)]
321 enum TapeRequestError
{
329 impl std
::fmt
::Display
for TapeRequestError
{
330 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
332 TapeRequestError
::None
=> {
333 write
!(f
, "no error")
335 TapeRequestError
::OpenFailed(reason
) => {
336 write
!(f
, "tape open failed - {}", reason
)
338 TapeRequestError
::WrongLabel(label
) => {
339 write
!(f
, "wrong media label {}", label
)
341 TapeRequestError
::EmptyTape
=> {
342 write
!(f
, "found empty media without label (please label all tapes first)")
344 TapeRequestError
::ReadFailed(reason
) => {
345 write
!(f
, "tape read failed - {}", reason
)
351 /// Requests a specific 'media' to be inserted into 'drive'. Within a
352 /// loop, this then tries to read the media label and waits until it
353 /// finds the requested media.
355 /// Returns a handle to the opened drive and the media labels.
356 pub fn request_and_load_media(
357 worker
: &dyn WorkerTaskContext
,
358 config
: &SectionConfigData
,
361 notify_email
: &Option
<String
>,
367 let check_label
= |handle
: &mut dyn TapeDriver
, uuid
: &proxmox_uuid
::Uuid
| {
368 if let Ok((Some(media_id
), _
)) = handle
.read_label() {
371 "found media label {} ({})",
372 media_id
.label
.label_text
,
376 if media_id
.label
.uuid
== *uuid
{
380 bail
!("read label failed (please label all tapes first)");
383 match config
.sections
.get(drive
) {
384 Some((section_type_name
, config
)) => {
385 match section_type_name
.as_ref() {
387 let mut tape
= VirtualTapeDrive
::deserialize(config
)?
;
389 let label_text
= label
.label_text
.clone();
391 tape
.load_media(&label_text
)?
;
393 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(open_virtual_tape_drive(&tape
)?
);
395 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
397 Ok((handle
, media_id
))
400 let drive_config
= LtoTapeDrive
::deserialize(config
)?
;
402 let label_text
= label
.label_text
.clone();
404 if drive_config
.changer
.is_some() {
406 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
408 let mut changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
409 changer
.load_media(&label_text
)?
;
411 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(open_lto_tape_drive(&drive_config
)?
);
413 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
415 return Ok((handle
, media_id
));
418 let mut last_error
= TapeRequestError
::None
;
420 let update_and_log_request_error
=
421 |old
: &mut TapeRequestError
, new
: TapeRequestError
| -> Result
<(), Error
>
424 task_log
!(worker
, "{}", new
);
427 "Please insert media '{}' into drive '{}'",
431 if let Some(to
) = notify_email
{
432 send_load_media_email(
436 Some(new
.to_string()),
445 worker
.check_abort()?
;
447 if last_error
!= TapeRequestError
::None
{
448 for _
in 0..50 { // delay 5 seconds
449 worker
.check_abort()?
;
450 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
455 "Checking for media '{}' in drive '{}'",
461 let mut handle
= match open_lto_tape_drive(&drive_config
) {
462 Ok(handle
) => handle
,
464 update_and_log_request_error(
466 TapeRequestError
::OpenFailed(err
.to_string()),
472 let request_error
= match handle
.read_label() {
473 Ok((Some(media_id
), _
)) if media_id
.label
.uuid
== label
.uuid
=> {
476 "found media label {} ({})",
477 media_id
.label
.label_text
,
478 media_id
.label
.uuid
.to_string(),
480 return Ok((Box
::new(handle
), media_id
));
482 Ok((Some(media_id
), _
)) => {
483 let label_string
= format
!(
485 media_id
.label
.label_text
,
486 media_id
.label
.uuid
.to_string(),
488 TapeRequestError
::WrongLabel(label_string
)
491 TapeRequestError
::EmptyTape
494 TapeRequestError
::ReadFailed(err
.to_string())
498 update_and_log_request_error(&mut last_error
, request_error
)?
;
501 _
=> bail
!("drive type '{}' not implemented!"),
505 bail
!("no such drive '{}'", drive
);
510 #[derive(thiserror::Error, Debug)]
511 pub enum TapeLockError
{
512 #[error("timeout while trying to lock")]
515 Other(#[from] Error),
518 impl From
<std
::io
::Error
> for TapeLockError
{
519 fn from(error
: std
::io
::Error
) -> Self {
520 Self::Other(error
.into())
524 /// Acquires an exclusive lock for the tape device
526 /// Basically calls lock_device_path() using the configured drive path.
527 pub fn lock_tape_device(
528 config
: &SectionConfigData
,
530 ) -> Result
<DeviceLockGuard
, TapeLockError
> {
531 let path
= tape_device_path(config
, drive
)?
;
532 lock_device_path(&path
).map_err(|err
| match err
{
533 TapeLockError
::Other(err
) => {
534 TapeLockError
::Other(format_err
!("unable to lock drive '{}' - {}", drive
, err
))
540 /// Writes the given state for the specified drive
542 /// This function does not lock, so make sure the drive is locked
543 pub fn set_tape_device_state(
546 ) -> Result
<(), Error
> {
548 let mut path
= PathBuf
::from(crate::tape
::DRIVE_STATE_DIR
);
551 let backup_user
= pbs_config
::backup_user()?
;
552 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0644);
553 let options
= CreateOptions
::new()
555 .owner(backup_user
.uid
)
556 .group(backup_user
.gid
);
558 replace_file(path
, state
.as_bytes(), options
, false)
561 /// Get the device state
562 pub fn get_tape_device_state(
563 config
: &SectionConfigData
,
565 ) -> Result
<Option
<String
>, Error
> {
566 let path
= format
!("{}/{}", crate::tape
::DRIVE_STATE_DIR
, drive
);
567 let state
= file_read_optional_string(path
)?
;
569 let device_path
= tape_device_path(config
, drive
)?
;
570 if test_device_path_lock(&device_path
)?
{
578 config
: &SectionConfigData
,
580 ) -> Result
<String
, Error
> {
581 match config
.sections
.get(drive
) {
582 Some((section_type_name
, config
)) => {
583 let path
= match section_type_name
.as_ref() {
585 VirtualTapeDrive
::deserialize(config
)?
.path
588 LtoTapeDrive
::deserialize(config
)?
.path
590 _
=> bail
!("unknown drive type '{}' - internal error"),
595 bail
!("no such drive '{}'", drive
);
600 pub struct DeviceLockGuard(std
::fs
::File
);
602 // Uses systemd escape_unit to compute a file name from `device_path`, the try
603 // to lock `/var/lock/<name>`.
604 fn open_device_lock(device_path
: &str) -> Result
<std
::fs
::File
, Error
> {
605 let lock_name
= proxmox
::tools
::systemd
::escape_unit(device_path
, true);
607 let mut path
= std
::path
::PathBuf
::from(crate::tape
::DRIVE_LOCK_DIR
);
608 path
.push(lock_name
);
610 let user
= pbs_config
::backup_user()?
;
611 let options
= CreateOptions
::new()
612 .perm(Mode
::from_bits_truncate(0o660))
616 atomic_open_or_create_file(
618 OFlag
::O_RDWR
| OFlag
::O_CLOEXEC
| OFlag
::O_APPEND
,
625 // Acquires an exclusive lock on `device_path`
627 fn lock_device_path(device_path
: &str) -> Result
<DeviceLockGuard
, TapeLockError
> {
628 let mut file
= open_device_lock(device_path
)?
;
629 let timeout
= std
::time
::Duration
::new(10, 0);
630 if let Err(err
) = lock_file(&mut file
, true, Some(timeout
)) {
631 if err
.kind() == std
::io
::ErrorKind
::Interrupted
{
632 return Err(TapeLockError
::TimeOut
);
634 return Err(err
.into());
638 Ok(DeviceLockGuard(file
))
641 // Same logic as lock_device_path, but uses a timeout of 0, making it
642 // non-blocking, and returning if the file is locked or not
643 fn test_device_path_lock(device_path
: &str) -> Result
<bool
, Error
> {
645 let mut file
= open_device_lock(device_path
)?
;
647 let timeout
= std
::time
::Duration
::new(0, 0);
648 match lock_file(&mut file
, true, Some(timeout
)) {
649 // file was not locked, continue
651 // file was locked, return true
652 Err(err
) if err
.kind() == std
::io
::ErrorKind
::WouldBlock
=> return Ok(true),
653 Err(err
) => bail
!("{}", err
),