8 pub use tape_alert_flags
::*;
10 mod volume_statistics
;
11 pub use volume_statistics
::*;
14 pub use encryption
::*;
17 pub use linux_tape
::*;
22 use std
::os
::unix
::io
::AsRawFd
;
23 use std
::path
::PathBuf
;
25 use anyhow
::{bail, format_err, Error}
;
26 use ::serde
::{Deserialize}
;
27 use serde_json
::Value
;
35 file_read_optional_string
,
40 api
::section_config
::SectionConfigData
,
60 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
61 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
69 send_load_media_email
,
74 /// Tape driver interface
75 pub trait TapeDriver
{
77 /// Flush all data to the tape
78 fn sync(&mut self) -> Result
<(), Error
>;
81 fn rewind(&mut self) -> Result
<(), Error
>;
83 /// Move to end of recorded data
85 /// We assume this flushes the tape write buffer.
86 fn move_to_eom(&mut self) -> Result
<(), Error
>;
88 /// Current file number
89 fn current_file_number(&mut self) -> Result
<u64, Error
>;
91 /// Completely erase the media
92 fn erase_media(&mut self, fast
: bool
) -> Result
<(), Error
>;
94 /// Read/Open the next file
95 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Option
<Box
<dyn TapeRead
+ 'a
>>, std
::io
::Error
>;
97 /// Write/Append a new file
98 fn write_file
<'a
>(&'a
mut self) -> Result
<Box
<dyn TapeWrite
+ 'a
>, std
::io
::Error
>;
100 /// Write label to tape (erase tape content)
101 fn label_tape(&mut self, label
: &MediaLabel
) -> Result
<(), Error
> {
105 self.set_encryption(None
)?
;
107 self.erase_media(true)?
;
109 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(&label
)?
)?
;
111 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, raw
.len() as u32);
114 let mut writer
= self.write_file()?
;
115 writer
.write_header(&header
, raw
.as_bytes())?
;
116 writer
.finish(false)?
;
119 self.sync()?
; // sync data to tape
124 /// Write the media set label to tape
126 /// If the media-set is encrypted, we also store the encryption
127 /// key_config, so that it is possible to restore the key.
128 fn write_media_set_label(
130 media_set_label
: &MediaSetLabel
,
131 key_config
: Option
<&KeyConfig
>,
132 ) -> Result
<(), Error
>;
134 /// Read the media label
136 /// This tries to read both media labels (label and
137 /// media_set_label). Also returns the optional encryption key configuration.
138 fn read_label(&mut self) -> Result
<(Option
<MediaId
>, Option
<KeyConfig
>), Error
> {
143 let mut reader
= match self.read_next_file()?
{
144 None
=> return Ok((None
, None
)), // tape is empty
145 Some(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 None
=> return Ok((Some(media_id
), None
)),
168 Some(reader
) => reader
,
171 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
172 header
.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
173 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
175 let mut data
: Value
= serde_json
::from_slice(&data
)
176 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
178 let key_config_value
= data
["key-config"].take();
179 let key_config
: Option
<KeyConfig
> = if !key_config_value
.is_null() {
180 Some(serde_json
::from_value(key_config_value
)?
)
185 let media_set_label
: MediaSetLabel
= serde_json
::from_value(data
)
186 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
188 // make sure we read the EOF marker
189 if reader
.skip_to_end()?
!= 0 {
190 bail
!("got unexpected data after media set label");
193 media_id
.media_set_label
= Some(media_set_label
);
195 Ok((Some(media_id
), key_config
))
199 fn eject_media(&mut self) -> Result
<(), Error
>;
201 /// Read Tape Alert Flags
203 /// This make only sense for real LTO drives. Virtual tape drives should
204 /// simply return empty flags (default).
205 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
206 Ok(TapeAlertFlags
::empty())
209 /// Set or clear encryption key
211 /// We use the media_set_uuid to XOR the secret key with the
212 /// uuid (first 16 bytes), so that each media set uses an uique
213 /// key for encryption.
216 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
217 ) -> Result
<(), Error
> {
218 if key_fingerprint
.is_some() {
219 bail
!("drive does not support encryption");
225 /// Get the media changer (MediaChange + name) associated with a tape drive.
227 /// Returns Ok(None) if the drive has no associated changer device.
229 /// Note: This may return the drive name as changer-name if the drive
230 /// implements some kind of internal changer (which is true for our
231 /// 'virtual' drive implementation).
232 pub fn media_changer(
233 config
: &SectionConfigData
,
235 ) -> Result
<Option
<(Box
<dyn MediaChange
>, String
)>, Error
> {
237 match config
.sections
.get(drive
) {
238 Some((section_type_name
, config
)) => {
239 match section_type_name
.as_ref() {
241 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
242 Ok(Some((Box
::new(tape
), drive
.to_string())))
245 let drive_config
= LinuxTapeDrive
::deserialize(config
)?
;
246 match drive_config
.changer
{
247 Some(ref changer_name
) => {
248 let changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
249 let changer_name
= changer_name
.to_string();
250 Ok(Some((Box
::new(changer
), changer_name
)))
255 _
=> bail
!("unknown drive type '{}' - internal error"),
259 bail
!("no such drive '{}'", drive
);
264 /// Get the media changer (MediaChange + name) associated with a tape drive.
266 /// This fail if the drive has no associated changer device.
267 pub fn required_media_changer(
268 config
: &SectionConfigData
,
270 ) -> Result
<(Box
<dyn MediaChange
>, String
), Error
> {
271 match media_changer(config
, drive
) {
272 Ok(Some(result
)) => {
276 bail
!("drive '{}' has no associated changer device", drive
);
284 /// Opens a tape drive (this fails if there is no media loaded)
286 config
: &SectionConfigData
,
288 ) -> Result
<Box
<dyn TapeDriver
>, Error
> {
290 match config
.sections
.get(drive
) {
291 Some((section_type_name
, config
)) => {
292 match section_type_name
.as_ref() {
294 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
295 let handle
= tape
.open()?
;
299 let tape
= LinuxTapeDrive
::deserialize(config
)?
;
300 let handle
= tape
.open()?
;
303 _
=> bail
!("unknown drive type '{}' - internal error"),
307 bail
!("no such drive '{}'", drive
);
312 /// Requests a specific 'media' to be inserted into 'drive'. Within a
313 /// loop, this then tries to read the media label and waits until it
314 /// finds the requested media.
316 /// Returns a handle to the opened drive and the media labels.
317 pub fn request_and_load_media(
319 config
: &SectionConfigData
,
327 let check_label
= |handle
: &mut dyn TapeDriver
, uuid
: &proxmox
::tools
::Uuid
| {
328 if let Ok((Some(media_id
), _
)) = handle
.read_label() {
331 "found media label {} ({})",
332 media_id
.label
.label_text
,
336 if media_id
.label
.uuid
== *uuid
{
340 bail
!("read label failed (please label all tapes first)");
343 match config
.sections
.get(drive
) {
344 Some((section_type_name
, config
)) => {
345 match section_type_name
.as_ref() {
347 let mut tape
= VirtualTapeDrive
::deserialize(config
)?
;
349 let label_text
= label
.label_text
.clone();
351 tape
.load_media(&label_text
)?
;
353 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(tape
.open()?
);
355 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
357 Ok((handle
, media_id
))
360 let drive_config
= LinuxTapeDrive
::deserialize(config
)?
;
362 let label_text
= label
.label_text
.clone();
364 if drive_config
.changer
.is_some() {
366 let mut changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
367 changer
.load_media(&label_text
)?
;
369 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(drive_config
.open()?
);
371 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
373 return Ok((handle
, media_id
));
376 task_log
!(worker
, "Please insert media '{}' into drive '{}'", label_text
, drive
);
378 let to
= "root@localhost"; // fixme
380 send_load_media_email(drive
, &label_text
, to
)?
;
382 let mut last_media_uuid
= None
;
383 let mut last_error
= None
;
386 worker
.check_abort()?
;
388 let mut handle
= match drive_config
.open() {
389 Ok(handle
) => handle
,
391 let err
= err
.to_string();
392 if Some(err
.clone()) != last_error
{
393 task_log
!(worker
, "tape open failed - {}", err
);
394 last_error
= Some(err
);
396 for _
in 0..50 { // delay 5 seconds
397 worker
.check_abort()?
;
398 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
404 match handle
.read_label() {
405 Ok((Some(media_id
), _
)) => {
406 if media_id
.label
.uuid
== label
.uuid
{
409 "found media label {} ({})",
410 media_id
.label
.label_text
,
411 media_id
.label
.uuid
.to_string(),
413 return Ok((Box
::new(handle
), media_id
));
414 } else if Some(media_id
.label
.uuid
.clone()) != last_media_uuid
{
417 "wrong media label {} ({})",
418 media_id
.label
.label_text
,
419 media_id
.label
.uuid
.to_string(),
421 last_media_uuid
= Some(media_id
.label
.uuid
);
425 if last_media_uuid
.is_some() {
426 task_log
!(worker
, "found empty media without label (please label all tapes first)");
427 last_media_uuid
= None
;
431 let err
= err
.to_string();
432 if Some(err
.clone()) != last_error
{
433 task_log
!(worker
, "tape open failed - {}", err
);
434 last_error
= Some(err
);
439 // eprintln!("read label failed - test again in 5 secs");
440 for _
in 0..50 { // delay 5 seconds
441 worker
.check_abort()?
;
442 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
446 _
=> bail
!("drive type '{}' not implemented!"),
450 bail
!("no such drive '{}'", drive
);
455 /// Aquires an exclusive lock for the tape device
457 /// Basically calls lock_device_path() using the configured drive path.
458 pub fn lock_tape_device(
459 config
: &SectionConfigData
,
461 ) -> Result
<DeviceLockGuard
, Error
> {
462 let path
= tape_device_path(config
, drive
)?
;
463 lock_device_path(&path
)
464 .map_err(|err
| format_err
!("unable to lock drive '{}' - {}", drive
, err
))
467 /// Writes the given state for the specified drive
469 /// This function does not lock, so make sure the drive is locked
470 pub fn set_tape_device_state(
473 ) -> Result
<(), Error
> {
475 let mut path
= PathBuf
::from(crate::tape
::DRIVE_STATE_DIR
);
478 let backup_user
= crate::backup
::backup_user()?
;
479 let mode
= nix
::sys
::stat
::Mode
::from_bits_truncate(0o0644);
480 let options
= CreateOptions
::new()
482 .owner(backup_user
.uid
)
483 .group(backup_user
.gid
);
485 replace_file(path
, state
.as_bytes(), options
)
488 /// Get the device state
489 pub fn get_tape_device_state(
490 config
: &SectionConfigData
,
492 ) -> Result
<Option
<String
>, Error
> {
493 let path
= format
!("/run/proxmox-backup/drive-state/{}", drive
);
494 let state
= file_read_optional_string(path
)?
;
496 let device_path
= tape_device_path(config
, drive
)?
;
497 if test_device_path_lock(&device_path
)?
{
505 config
: &SectionConfigData
,
507 ) -> Result
<String
, Error
> {
508 match config
.sections
.get(drive
) {
509 Some((section_type_name
, config
)) => {
510 let path
= match section_type_name
.as_ref() {
512 VirtualTapeDrive
::deserialize(config
)?
.path
515 LinuxTapeDrive
::deserialize(config
)?
.path
517 _
=> bail
!("unknown drive type '{}' - internal error"),
522 bail
!("no such drive '{}'", drive
);
527 pub struct DeviceLockGuard(std
::fs
::File
);
529 // Aquires an exclusive lock on `device_path`
531 // Uses systemd escape_unit to compute a file name from `device_path`, the try
532 // to lock `/var/lock/<name>`.
533 fn lock_device_path(device_path
: &str) -> Result
<DeviceLockGuard
, Error
> {
535 let lock_name
= crate::tools
::systemd
::escape_unit(device_path
, true);
537 let mut path
= std
::path
::PathBuf
::from("/var/lock");
538 path
.push(lock_name
);
540 let timeout
= std
::time
::Duration
::new(10, 0);
541 let mut file
= std
::fs
::OpenOptions
::new().create(true).append(true).open(path
)?
;
542 proxmox
::tools
::fs
::lock_file(&mut file
, true, Some(timeout
))?
;
544 let backup_user
= crate::backup
::backup_user()?
;
545 fchown(file
.as_raw_fd(), Some(backup_user
.uid
), Some(backup_user
.gid
))?
;
547 Ok(DeviceLockGuard(file
))
550 // Same logic as lock_device_path, but uses a timeout of 0, making it
551 // non-blocking, and returning if the file is locked or not
552 fn test_device_path_lock(device_path
: &str) -> Result
<bool
, Error
> {
554 let lock_name
= crate::tools
::systemd
::escape_unit(device_path
, true);
556 let mut path
= std
::path
::PathBuf
::from("/var/lock");
557 path
.push(lock_name
);
559 let timeout
= std
::time
::Duration
::new(0, 0);
560 let mut file
= std
::fs
::OpenOptions
::new().create(true).append(true).open(path
)?
;
561 match proxmox
::tools
::fs
::lock_file(&mut file
, true, Some(timeout
)) {
562 // file was not locked, continue
564 // file was locked, return true
565 Err(err
) if err
.kind() == std
::io
::ErrorKind
::WouldBlock
=> return Ok(true),
566 Err(err
) => bail
!("{}", err
),
569 let backup_user
= crate::backup
::backup_user()?
;
570 fchown(file
.as_raw_fd(), Some(backup_user
.uid
), Some(backup_user
.gid
))?
;