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
;
24 use anyhow
::{bail, format_err, Error}
;
25 use ::serde
::{Deserialize}
;
26 use serde_json
::Value
;
34 file_read_optional_string
,
39 api
::section_config
::SectionConfigData
,
59 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
60 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
68 send_load_media_email
,
73 /// Tape driver interface
74 pub trait TapeDriver
{
76 /// Flush all data to the tape
77 fn sync(&mut self) -> Result
<(), Error
>;
80 fn rewind(&mut self) -> Result
<(), Error
>;
82 /// Move to end of recorded data
84 /// We assume this flushes the tape write buffer.
85 fn move_to_eom(&mut self) -> Result
<(), Error
>;
87 /// Current file number
88 fn current_file_number(&mut self) -> Result
<u64, Error
>;
90 /// Completely erase the media
91 fn erase_media(&mut self, fast
: bool
) -> Result
<(), Error
>;
93 /// Read/Open the next file
94 fn read_next_file
<'a
>(&'a
mut self) -> Result
<Option
<Box
<dyn TapeRead
+ 'a
>>, std
::io
::Error
>;
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
> {
104 self.set_encryption(None
)?
;
106 self.erase_media(true)?
;
108 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(&label
)?
)?
;
110 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, raw
.len() as u32);
113 let mut writer
= self.write_file()?
;
114 writer
.write_header(&header
, raw
.as_bytes())?
;
115 writer
.finish(false)?
;
118 self.sync()?
; // sync data to tape
123 /// Write the media set label to tape
125 /// If the media-set is encrypted, we also store the encryption
126 /// key_config, so that it is possible to restore the key.
127 fn write_media_set_label(
129 media_set_label
: &MediaSetLabel
,
130 key_config
: Option
<&KeyConfig
>,
131 ) -> Result
<(), Error
>;
133 /// Read the media label
135 /// This tries to read both media labels (label and
136 /// media_set_label). Also returns the optional encryption key configuration.
137 fn read_label(&mut self) -> Result
<(Option
<MediaId
>, Option
<KeyConfig
>), Error
> {
142 let mut reader
= match self.read_next_file()?
{
143 None
=> return Ok((None
, None
)), // tape is empty
144 Some(reader
) => reader
,
147 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
148 header
.check(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
149 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
151 let label
: MediaLabel
= serde_json
::from_slice(&data
)
152 .map_err(|err
| format_err
!("unable to parse drive label - {}", err
))?
;
154 // make sure we read the EOF marker
155 if reader
.skip_to_end()?
!= 0 {
156 bail
!("got unexpected data after label");
162 let mut media_id
= MediaId { label, media_set_label: None }
;
164 // try to read MediaSet label
165 let mut reader
= match self.read_next_file()?
{
166 None
=> return Ok((Some(media_id
), None
)),
167 Some(reader
) => reader
,
170 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
171 header
.check(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, 1, 64*1024)?
;
172 let data
= reader
.read_exact_allocated(header
.size
as usize)?
;
174 let mut data
: Value
= serde_json
::from_slice(&data
)
175 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
177 let key_config_value
= data
["key-config"].take();
178 let key_config
: Option
<KeyConfig
> = if !key_config_value
.is_null() {
179 Some(serde_json
::from_value(key_config_value
)?
)
184 let media_set_label
: MediaSetLabel
= serde_json
::from_value(data
)
185 .map_err(|err
| format_err
!("unable to parse media set label - {}", err
))?
;
187 // make sure we read the EOF marker
188 if reader
.skip_to_end()?
!= 0 {
189 bail
!("got unexpected data after media set label");
192 media_id
.media_set_label
= Some(media_set_label
);
194 Ok((Some(media_id
), key_config
))
198 fn eject_media(&mut self) -> Result
<(), Error
>;
200 /// Read Tape Alert Flags
202 /// This make only sense for real LTO drives. Virtual tape drives should
203 /// simply return empty flags (default).
204 fn tape_alert_flags(&mut self) -> Result
<TapeAlertFlags
, Error
> {
205 Ok(TapeAlertFlags
::empty())
208 /// Set or clear encryption key
210 /// We use the media_set_uuid to XOR the secret key with the
211 /// uuid (first 16 bytes), so that each media set uses an uique
212 /// key for encryption.
215 key_fingerprint
: Option
<(Fingerprint
, Uuid
)>,
216 ) -> Result
<(), Error
> {
217 if key_fingerprint
.is_some() {
218 bail
!("drive does not support encryption");
224 /// Get the media changer (MediaChange + name) associated with a tape drive.
226 /// Returns Ok(None) if the drive has no associated changer device.
228 /// Note: This may return the drive name as changer-name if the drive
229 /// implements some kind of internal changer (which is true for our
230 /// 'virtual' drive implementation).
231 pub fn media_changer(
232 config
: &SectionConfigData
,
234 ) -> Result
<Option
<(Box
<dyn MediaChange
>, String
)>, Error
> {
236 match config
.sections
.get(drive
) {
237 Some((section_type_name
, config
)) => {
238 match section_type_name
.as_ref() {
240 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
241 Ok(Some((Box
::new(tape
), drive
.to_string())))
244 let drive_config
= LinuxTapeDrive
::deserialize(config
)?
;
245 match drive_config
.changer
{
246 Some(ref changer_name
) => {
247 let changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
248 let changer_name
= changer_name
.to_string();
249 Ok(Some((Box
::new(changer
), changer_name
)))
254 _
=> bail
!("unknown drive type '{}' - internal error"),
258 bail
!("no such drive '{}'", drive
);
263 /// Get the media changer (MediaChange + name) associated with a tape drive.
265 /// This fail if the drive has no associated changer device.
266 pub fn required_media_changer(
267 config
: &SectionConfigData
,
269 ) -> Result
<(Box
<dyn MediaChange
>, String
), Error
> {
270 match media_changer(config
, drive
) {
271 Ok(Some(result
)) => {
275 bail
!("drive '{}' has no associated changer device", drive
);
283 /// Opens a tape drive (this fails if there is no media loaded)
285 config
: &SectionConfigData
,
287 ) -> Result
<Box
<dyn TapeDriver
>, Error
> {
289 match config
.sections
.get(drive
) {
290 Some((section_type_name
, config
)) => {
291 match section_type_name
.as_ref() {
293 let tape
= VirtualTapeDrive
::deserialize(config
)?
;
294 let handle
= tape
.open()?
;
298 let tape
= LinuxTapeDrive
::deserialize(config
)?
;
299 let handle
= tape
.open()?
;
302 _
=> bail
!("unknown drive type '{}' - internal error"),
306 bail
!("no such drive '{}'", drive
);
311 /// Requests a specific 'media' to be inserted into 'drive'. Within a
312 /// loop, this then tries to read the media label and waits until it
313 /// finds the requested media.
315 /// Returns a handle to the opened drive and the media labels.
316 pub fn request_and_load_media(
318 config
: &SectionConfigData
,
326 let check_label
= |handle
: &mut dyn TapeDriver
, uuid
: &proxmox
::tools
::Uuid
| {
327 if let Ok((Some(media_id
), _
)) = handle
.read_label() {
330 "found media label {} ({})",
331 media_id
.label
.label_text
,
335 if media_id
.label
.uuid
== *uuid
{
339 bail
!("read label failed (please label all tapes first)");
342 match config
.sections
.get(drive
) {
343 Some((section_type_name
, config
)) => {
344 match section_type_name
.as_ref() {
346 let mut tape
= VirtualTapeDrive
::deserialize(config
)?
;
348 let label_text
= label
.label_text
.clone();
350 tape
.load_media(&label_text
)?
;
352 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(tape
.open()?
);
354 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
356 Ok((handle
, media_id
))
359 let drive_config
= LinuxTapeDrive
::deserialize(config
)?
;
361 let label_text
= label
.label_text
.clone();
363 if drive_config
.changer
.is_some() {
365 let mut changer
= MtxMediaChanger
::with_drive_config(&drive_config
)?
;
366 changer
.load_media(&label_text
)?
;
368 let mut handle
: Box
<dyn TapeDriver
> = Box
::new(drive_config
.open()?
);
370 let media_id
= check_label(handle
.as_mut(), &label
.uuid
)?
;
372 return Ok((handle
, media_id
));
375 task_log
!(worker
, "Please insert media '{}' into drive '{}'", label_text
, drive
);
377 let to
= "root@localhost"; // fixme
379 send_load_media_email(drive
, &label_text
, to
)?
;
381 let mut last_media_uuid
= None
;
382 let mut last_error
= None
;
385 worker
.check_abort()?
;
387 let mut handle
= match drive_config
.open() {
388 Ok(handle
) => handle
,
390 let err
= err
.to_string();
391 if Some(err
.clone()) != last_error
{
392 task_log
!(worker
, "tape open failed - {}", err
);
393 last_error
= Some(err
);
395 for _
in 0..50 { // delay 5 seconds
396 worker
.check_abort()?
;
397 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
403 match handle
.read_label() {
404 Ok((Some(media_id
), _
)) => {
405 if media_id
.label
.uuid
== label
.uuid
{
408 "found media label {} ({})",
409 media_id
.label
.label_text
,
410 media_id
.label
.uuid
.to_string(),
412 return Ok((Box
::new(handle
), media_id
));
413 } else if Some(media_id
.label
.uuid
.clone()) != last_media_uuid
{
416 "wrong media label {} ({})",
417 media_id
.label
.label_text
,
418 media_id
.label
.uuid
.to_string(),
420 last_media_uuid
= Some(media_id
.label
.uuid
);
424 if last_media_uuid
.is_some() {
425 task_log
!(worker
, "found empty media without label (please label all tapes first)");
426 last_media_uuid
= None
;
430 let err
= err
.to_string();
431 if Some(err
.clone()) != last_error
{
432 task_log
!(worker
, "tape open failed - {}", err
);
433 last_error
= Some(err
);
438 // eprintln!("read label failed - test again in 5 secs");
439 for _
in 0..50 { // delay 5 seconds
440 worker
.check_abort()?
;
441 std
::thread
::sleep(std
::time
::Duration
::from_millis(100));
445 _
=> bail
!("drive type '{}' not implemented!"),
449 bail
!("no such drive '{}'", drive
);
454 /// Aquires an exclusive lock for the tape device
456 /// Basically calls lock_device_path() using the configured drive path.
457 pub fn lock_tape_device(
458 config
: &SectionConfigData
,
460 ) -> Result
<DeviceLockGuard
, Error
> {
461 let path
= tape_device_path(config
, drive
)?
;
462 lock_device_path(&path
)
463 .map_err(|err
| format_err
!("unable to lock drive '{}' - {}", drive
, err
))
466 /// Writes the given state for the specified drive
468 /// This function does not lock, so make sure the drive is locked
469 pub fn set_tape_device_state(
472 ) -> Result
<(), Error
> {
473 let mut path
= "/run/proxmox-backup/drive-state".to_string();
474 std
::fs
::create_dir_all(&path
)?
;
476 write
!(path
, "/{}", drive
)?
;
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
))?
;