8 use std
::os
::unix
::io
::AsRawFd
;
9 use std
::path
::PathBuf
;
11 use anyhow
::{bail, format_err, Error}
;
12 use ::serde
::{Deserialize}
;
13 use serde_json
::Value
;
21 file_read_optional_string
,
26 api
::section_config
::SectionConfigData
,
29 use pbs_datastore
::task_log
;
30 use pbs_datastore
::task
::TaskState
;
42 send_load_media_email
,
50 drive
::lto
::TapeAlertFlags
,
52 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
53 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
65 /// Tape driver interface
66 pub trait TapeDriver
{
68 /// Flush all data to the tape
69 fn sync(&mut self) -> Result
<(), Error
>;
72 fn rewind(&mut self) -> Result
<(), Error
>;
74 /// Move to end of recorded data
76 /// We assume this flushes the tape write buffer. if
77 /// write_missing_eof is true, we verify that there is a filemark
78 /// at the end. If not, we write one.
79 fn move_to_eom(&mut self, write_missing_eof
: bool
) -> Result
<(), Error
>;
82 fn move_to_last_file(&mut self) -> Result
<(), Error
>;
84 /// Move to given file nr
85 fn move_to_file(&mut self, file
: u64) -> Result
<(), Error
>;
87 /// Current file number
88 fn current_file_number(&mut self) -> Result
<u64, Error
>;
90 /// Completely erase the media
91 fn format_media(&mut self, fast
: bool
) -> Result
<(), Error
>;
93 /// Read/Open the next file
94 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeRead
+ 'a
>, BlockReadError
>;
96 /// Write/Append a new file
97 fn write_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeWrite
+ 'a
>, std
::io
::Error
>;
99 /// Write label to tape (erase tape content)
100 fn label_tape(&mut self, label
: &MediaLabel
) -> Result
<(), Error
> {
102 self.set_encryption(None
)?
;
104 self.format_media(true)?
; // this rewinds the tape
106 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(&label
)?
)?
;
108 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, raw
.len() as u32);
111 let mut writer
= self.write_file()?
;
112 writer
.write_header(&header
, raw
.as_bytes())?
;
113 writer
.finish(false)?
;
116 self.sync()?
; // sync data to tape
121 /// Write the media set label to tape
123 /// If the media-set is encrypted, we also store the encryption
124 /// key_config, so that it is possible to restore the key.
125 fn write_media_set_label(
127 media_set_label
: &MediaSetLabel
,
128 key_config
: Option
<&KeyConfig
>,
129 ) -> Result
<(), Error
>;
131 /// Read the media label
133 /// This tries to read both media labels (label and
134 /// media_set_label). Also returns the optional encryption key configuration.
135 fn read_label(&mut self) -> Result
<(Option
<MediaId
>, Option
<KeyConfig
>), Error
> {
140 let mut reader
= match self.read_next_file() {
141 Err(BlockReadError
::EndOfStream
) => {
142 return Ok((None
, None
)); // tape is empty
144 Err(BlockReadError
::EndOfFile
) => {
145 bail
!("got unexpected filemark at BOT");
147 Err(BlockReadError
::Error(err
)) => {
148 return Err(err
.into());
150 Ok(reader
) => reader
,
153 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
154 header
.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
155 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
157 let label
: MediaLabel
= serde_json
::from_slice(&data
)
158 .map_err(|err
| format_err
!("unable to parse drive label - {}", err
))?
;
160 // make sure we read the EOF marker
161 if reader
.skip_to_end()?
!= 0 {
162 bail
!("got unexpected data after label");
168 let mut media_id
= MediaId { label, media_set_label: None }
;
170 // try to read MediaSet label
171 let mut reader
= match self.read_next_file() {
172 Err(BlockReadError
::EndOfStream
) => {
173 return Ok((Some(media_id
), None
));
175 Err(BlockReadError
::EndOfFile
) => {
176 bail
!("got unexpected filemark after label");
178 Err(BlockReadError
::Error(err
)) => {
179 return Err(err
.into());
181 Ok(reader
) => reader
,
184 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
185 header
.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
186 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
188 let mut data
: Value
= serde_json
::from_slice(&data
)
189 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
191 let key_config_value
= data
["key-config"].take();
192 let key_config
: Option
<KeyConfig
> = if !key_config_value
.is_null() {
193 Some(serde_json
::from_value(key_config_value
)?
)
198 let media_set_label
: MediaSetLabel
= serde_json
::from_value(data
)
199 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
201 // make sure we read the EOF marker
202 if reader
.skip_to_end()?
!= 0 {
203 bail
!("got unexpected data after media set label");
206 media_id
.media_set_label
= Some(media_set_label
);
208 Ok((Some(media_id
), key_config
))
212 fn eject_media(&mut self) -> Result
<(), Error
>;
214 /// Read Tape Alert Flags
216 /// This make only sense for real LTO drives. Virtual tape drives should
217 /// simply return empty flags (default).
218 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
219 Ok(TapeAlertFlags
::empty())
222 /// Set or clear encryption key
224 /// We use the media_set_uuid to XOR the secret key with the
225 /// uuid (first 16 bytes), so that each media set uses an unique
226 /// key for encryption.
229 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
230 ) -> Result
<(), Error
> {
231 if key_fingerprint
.is_some() {
232 bail
!("drive does not support encryption");
238 /// Get the media changer (MediaChange + name) associated with a tape drive.
240 /// Returns Ok(None) if the drive has no associated changer device.
242 /// Note: This may return the drive name as changer-name if the drive
243 /// implements some kind of internal changer (which is true for our
244 /// 'virtual' drive implementation).
245 pub fn media_changer(
246 config
: &SectionConfigData
,
248 ) -> Result
<Option
<(Box
<dyn MediaChange
>, String
)>, Error
> {
250 match config
.sections
.get(drive
) {
251 Some((section_type_name
, config
)) => {
252 match section_type_name
.as_ref() {
254 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
255 Ok(Some((Box
::new(tape
), drive
.to_string())))
258 let drive_config
= LtoTapeDrive
::deserialize(config
)?
;
259 match drive_config
.changer
{
260 Some(ref changer_name
) => {
261 let changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
262 let changer_name
= changer_name
.to_string();
263 Ok(Some((Box
::new(changer
), changer_name
)))
268 _
=> bail
!("unknown drive type '{}' - internal error"),
272 bail
!("no such drive '{}'", drive
);
277 /// Get the media changer (MediaChange + name) associated with a tape drive.
279 /// This fail if the drive has no associated changer device.
280 pub fn required_media_changer(
281 config
: &SectionConfigData
,
283 ) -> Result
<(Box
<dyn MediaChange
>, String
), Error
> {
284 match media_changer(config
, drive
) {
285 Ok(Some(result
)) => {
289 bail
!("drive '{}' has no associated changer device", drive
);
297 /// Opens a tape drive (this fails if there is no media loaded)
299 config
: &SectionConfigData
,
301 ) -> Result
<Box
<dyn TapeDriver
>, Error
> {
303 match config
.sections
.get(drive
) {
304 Some((section_type_name
, config
)) => {
305 match section_type_name
.as_ref() {
307 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
308 let handle
= tape
.open()?
;
312 let tape
= LtoTapeDrive
::deserialize(config
)?
;
313 let handle
= tape
.open()?
;
316 _
=> bail
!("unknown drive type '{}' - internal error"),
320 bail
!("no such drive '{}'", drive
);
325 #[derive(PartialEq, Eq)]
326 enum TapeRequestError
{
334 impl std
::fmt
::Display
for TapeRequestError
{
335 fn fmt(&self, f
: &mut std
::fmt
::Formatter
<'_
>) -> std
::fmt
::Result
{
337 TapeRequestError
::None
=> {
338 write
!(f
, "no error")
340 TapeRequestError
::OpenFailed(reason
) => {
341 write
!(f
, "tape open failed - {}", reason
)
343 TapeRequestError
::WrongLabel(label
) => {
344 write
!(f
, "wrong media label {}", label
)
346 TapeRequestError
::EmptyTape
=> {
347 write
!(f
, "found empty media without label (please label all tapes first)")
349 TapeRequestError
::ReadFailed(reason
) => {
350 write
!(f
, "tape read failed - {}", reason
)
356 /// Requests a specific 'media' to be inserted into 'drive'. Within a
357 /// loop, this then tries to read the media label and waits until it
358 /// finds the requested media.
360 /// Returns a handle to the opened drive and the media labels.
361 pub fn request_and_load_media(
363 config
: &SectionConfigData
,
366 notify_email
: &Option
<String
>,
372 let check_label
= |handle
: &mut dyn TapeDriver
, uuid
: &proxmox
::tools
::Uuid
| {
373 if let Ok((Some(media_id
), _
)) = handle
.read_label() {
376 "found media label {} ({})",
377 media_id
.label
.label_text
,
381 if media_id
.label
.uuid
== *uuid
{
385 bail
!("read label failed (please label all tapes first)");
388 match config
.sections
.get(drive
) {
389 Some((section_type_name
, config
)) => {
390 match section_type_name
.as_ref() {
392 let mut tape
= VirtualTapeDrive
::deserialize(config
)?
;
394 let label_text
= label
.label_text
.clone();
396 tape
.load_media(&label_text
)?
;
398 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(tape
.open()?
);
400 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
402 Ok((handle
, media_id
))
405 let drive_config
= LtoTapeDrive
::deserialize(config
)?
;
407 let label_text
= label
.label_text
.clone();
409 if drive_config
.changer
.is_some() {
411 task_log
!(worker
, "loading media '{}' into drive '{}'", label_text
, drive
);
413 let mut changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
414 changer
.load_media(&label_text
)?
;
416 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(drive_config
.open()?
);
418 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
420 return Ok((handle
, media_id
));
423 let mut last_error
= TapeRequestError
::None
;
425 let update_and_log_request_error
=
426 |old
: &mut TapeRequestError
, new
: TapeRequestError
| -> Result
<(), Error
>
429 task_log
!(worker
, "{}", new
);
432 "Please insert media '{}' into drive '{}'",
436 if let Some(to
) = notify_email
{
437 send_load_media_email(
441 Some(new
.to_string()),
450 worker
.check_abort()?
;
452 if last_error
!= TapeRequestError
::None
{
453 for _
in 0..50 { // delay 5 seconds
454 worker
.check_abort()?
;
455 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
460 "Checking for media '{}' in drive '{}'",
466 let mut handle
= match drive_config
.open() {
467 Ok(handle
) => handle
,
469 update_and_log_request_error(
471 TapeRequestError
::OpenFailed(err
.to_string()),
477 let request_error
= match handle
.read_label() {
478 Ok((Some(media_id
), _
)) if media_id
.label
.uuid
== label
.uuid
=> {
481 "found media label {} ({})",
482 media_id
.label
.label_text
,
483 media_id
.label
.uuid
.to_string(),
485 return Ok((Box
::new(handle
), media_id
));
487 Ok((Some(media_id
), _
)) => {
488 let label_string
= format
!(
490 media_id
.label
.label_text
,
491 media_id
.label
.uuid
.to_string(),
493 TapeRequestError
::WrongLabel(label_string
)
496 TapeRequestError
::EmptyTape
499 TapeRequestError
::ReadFailed(err
.to_string())
503 update_and_log_request_error(&mut last_error
, request_error
)?
;
506 _
=> bail
!("drive type '{}' not implemented!"),
510 bail
!("no such drive '{}'", drive
);
515 #[derive(thiserror::Error, Debug)]
516 pub enum TapeLockError
{
517 #[error("timeout while trying to lock")]
520 Other(#[from] Error),
523 impl From
<std
::io
::Error
> for TapeLockError
{
524 fn from(error
: std
::io
::Error
) -> Self {
525 Self::Other(error
.into())
529 /// Acquires an exclusive lock for the tape device
531 /// Basically calls lock_device_path() using the configured drive path.
532 pub fn lock_tape_device(
533 config
: &SectionConfigData
,
535 ) -> Result
<DeviceLockGuard
, TapeLockError
> {
536 let path
= tape_device_path(config
, drive
)?
;
537 lock_device_path(&path
).map_err(|err
| match err
{
538 TapeLockError
::Other(err
) => {
539 TapeLockError
::Other(format_err
!("unable to lock drive '{}' - {}", drive
, err
))
545 /// Writes the given state for the specified drive
547 /// This function does not lock, so make sure the drive is locked
548 pub fn set_tape_device_state(
551 ) -> Result
<(), Error
> {
553 let mut path
= PathBuf
::from(crate::tape
::DRIVE_STATE_DIR
);
556 let backup_user
= crate::backup
::backup_user()?
;
557 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0644);
558 let options
= CreateOptions
::new()
560 .owner(backup_user
.uid
)
561 .group(backup_user
.gid
);
563 replace_file(path
, state
.as_bytes(), options
)
566 /// Get the device state
567 pub fn get_tape_device_state(
568 config
: &SectionConfigData
,
570 ) -> Result
<Option
<String
>, Error
> {
571 let path
= format
!("{}/{}", crate::tape
::DRIVE_STATE_DIR
, drive
);
572 let state
= file_read_optional_string(path
)?
;
574 let device_path
= tape_device_path(config
, drive
)?
;
575 if test_device_path_lock(&device_path
)?
{
583 config
: &SectionConfigData
,
585 ) -> Result
<String
, Error
> {
586 match config
.sections
.get(drive
) {
587 Some((section_type_name
, config
)) => {
588 let path
= match section_type_name
.as_ref() {
590 VirtualTapeDrive
::deserialize(config
)?
.path
593 LtoTapeDrive
::deserialize(config
)?
.path
595 _
=> bail
!("unknown drive type '{}' - internal error"),
600 bail
!("no such drive '{}'", drive
);
605 pub struct DeviceLockGuard(std
::fs
::File
);
607 // Acquires an exclusive lock on `device_path`
609 // Uses systemd escape_unit to compute a file name from `device_path`, the try
610 // to lock `/var/lock/<name>`.
611 fn lock_device_path(device_path
: &str) -> Result
<DeviceLockGuard
, TapeLockError
> {
613 let lock_name
= crate::tools
::systemd
::escape_unit(device_path
, true);
615 let mut path
= std
::path
::PathBuf
::from(crate::tape
::DRIVE_LOCK_DIR
);
616 path
.push(lock_name
);
618 let timeout
= std
::time
::Duration
::new(10, 0);
619 let mut file
= std
::fs
::OpenOptions
::new().create(true).append(true).open(path
)?
;
620 if let Err(err
) = proxmox
::tools
::fs
::lock_file(&mut file
, true, Some(timeout
)) {
621 if err
.kind() == std
::io
::ErrorKind
::Interrupted
{
622 return Err(TapeLockError
::TimeOut
);
624 return Err(err
.into());
628 let backup_user
= crate::backup
::backup_user()?
;
629 fchown(file
.as_raw_fd(), Some(backup_user
.uid
), Some(backup_user
.gid
))?
;
631 Ok(DeviceLockGuard(file
))
634 // Same logic as lock_device_path, but uses a timeout of 0, making it
635 // non-blocking, and returning if the file is locked or not
636 fn test_device_path_lock(device_path
: &str) -> Result
<bool
, Error
> {
638 let lock_name
= crate::tools
::systemd
::escape_unit(device_path
, true);
640 let mut path
= std
::path
::PathBuf
::from(crate::tape
::DRIVE_LOCK_DIR
);
641 path
.push(lock_name
);
643 let timeout
= std
::time
::Duration
::new(0, 0);
644 let mut file
= std
::fs
::OpenOptions
::new().create(true).append(true).open(path
)?
;
645 match proxmox
::tools
::fs
::lock_file(&mut file
, true, Some(timeout
)) {
646 // file was not locked, continue
648 // file was locked, return true
649 Err(err
) if err
.kind() == std
::io
::ErrorKind
::WouldBlock
=> return Ok(true),
650 Err(err
) => bail
!("{}", err
),
653 let backup_user
= crate::backup
::backup_user()?
;
654 fchown(file
.as_raw_fd(), Some(backup_user
.uid
), Some(backup_user
.gid
))?
;