]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/drive/mod.rs
tape: create tmp dirs early at server startup
[proxmox-backup.git] / src / tape / drive / mod.rs
1 //! Tape drivers
2
3 mod virtual_tape;
4
5 pub mod linux_mtio;
6
7 mod tape_alert_flags;
8 pub use tape_alert_flags::*;
9
10 mod volume_statistics;
11 pub use volume_statistics::*;
12
13 mod encryption;
14 pub use encryption::*;
15
16 mod linux_tape;
17 pub use linux_tape::*;
18
19 mod mam;
20 pub use mam::*;
21
22 use std::os::unix::io::AsRawFd;
23 use std::path::PathBuf;
24
25 use anyhow::{bail, format_err, Error};
26 use ::serde::{Deserialize};
27 use serde_json::Value;
28
29 use proxmox::{
30 tools::{
31 Uuid,
32 io::ReadExt,
33 fs::{
34 fchown,
35 file_read_optional_string,
36 replace_file,
37 CreateOptions,
38 }
39 },
40 api::section_config::SectionConfigData,
41 };
42
43 use crate::{
44 task_log,
45 task::TaskState,
46 backup::{
47 Fingerprint,
48 KeyConfig,
49 },
50 api2::types::{
51 VirtualTapeDrive,
52 LinuxTapeDrive,
53 },
54 server::WorkerTask,
55 tape::{
56 TapeWrite,
57 TapeRead,
58 MediaId,
59 file_formats::{
60 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
61 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
62 MediaLabel,
63 MediaSetLabel,
64 MediaContentHeader,
65 },
66 changer::{
67 MediaChange,
68 MtxMediaChanger,
69 send_load_media_email,
70 },
71 },
72 };
73
74 /// Tape driver interface
75 pub trait TapeDriver {
76
77 /// Flush all data to the tape
78 fn sync(&mut self) -> Result<(), Error>;
79
80 /// Rewind the tape
81 fn rewind(&mut self) -> Result<(), Error>;
82
83 /// Move to end of recorded data
84 ///
85 /// We assume this flushes the tape write buffer.
86 fn move_to_eom(&mut self) -> Result<(), Error>;
87
88 /// Current file number
89 fn current_file_number(&mut self) -> Result<u64, Error>;
90
91 /// Completely erase the media
92 fn erase_media(&mut self, fast: bool) -> Result<(), Error>;
93
94 /// Read/Open the next file
95 fn read_next_file<'a>(&'a mut self) -> Result<Option<Box<dyn TapeRead + 'a>>, std::io::Error>;
96
97 /// Write/Append a new file
98 fn write_file<'a>(&'a mut self) -> Result<Box<dyn TapeWrite + 'a>, std::io::Error>;
99
100 /// Write label to tape (erase tape content)
101 fn label_tape(&mut self, label: &MediaLabel) -> Result<(), Error> {
102
103 self.rewind()?;
104
105 self.set_encryption(None)?;
106
107 self.erase_media(true)?;
108
109 let raw = serde_json::to_string_pretty(&serde_json::to_value(&label)?)?;
110
111 let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, raw.len() as u32);
112
113 {
114 let mut writer = self.write_file()?;
115 writer.write_header(&header, raw.as_bytes())?;
116 writer.finish(false)?;
117 }
118
119 self.sync()?; // sync data to tape
120
121 Ok(())
122 }
123
124 /// Write the media set label to tape
125 ///
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(
129 &mut self,
130 media_set_label: &MediaSetLabel,
131 key_config: Option<&KeyConfig>,
132 ) -> Result<(), Error>;
133
134 /// Read the media label
135 ///
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> {
139
140 self.rewind()?;
141
142 let label = {
143 let mut reader = match self.read_next_file()? {
144 None => return Ok((None, None)), // tape is empty
145 Some(reader) => reader,
146 };
147
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)?;
151
152 let label: MediaLabel = serde_json::from_slice(&data)
153 .map_err(|err| format_err!("unable to parse drive label - {}", err))?;
154
155 // make sure we read the EOF marker
156 if reader.skip_to_end()? != 0 {
157 bail!("got unexpected data after label");
158 }
159
160 label
161 };
162
163 let mut media_id = MediaId { label, media_set_label: None };
164
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,
169 };
170
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)?;
174
175 let mut data: Value = serde_json::from_slice(&data)
176 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
177
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)?)
181 } else {
182 None
183 };
184
185 let media_set_label: MediaSetLabel = serde_json::from_value(data)
186 .map_err(|err| format_err!("unable to parse media set label - {}", err))?;
187
188 // make sure we read the EOF marker
189 if reader.skip_to_end()? != 0 {
190 bail!("got unexpected data after media set label");
191 }
192
193 media_id.media_set_label = Some(media_set_label);
194
195 Ok((Some(media_id), key_config))
196 }
197
198 /// Eject media
199 fn eject_media(&mut self) -> Result<(), Error>;
200
201 /// Read Tape Alert Flags
202 ///
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())
207 }
208
209 /// Set or clear encryption key
210 ///
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.
214 fn set_encryption(
215 &mut self,
216 key_fingerprint: Option<(Fingerprint, Uuid)>,
217 ) -> Result<(), Error> {
218 if key_fingerprint.is_some() {
219 bail!("drive does not support encryption");
220 }
221 Ok(())
222 }
223 }
224
225 /// Get the media changer (MediaChange + name) associated with a tape drive.
226 ///
227 /// Returns Ok(None) if the drive has no associated changer device.
228 ///
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,
234 drive: &str,
235 ) -> Result<Option<(Box<dyn MediaChange>, String)>, Error> {
236
237 match config.sections.get(drive) {
238 Some((section_type_name, config)) => {
239 match section_type_name.as_ref() {
240 "virtual" => {
241 let tape = VirtualTapeDrive::deserialize(config)?;
242 Ok(Some((Box::new(tape), drive.to_string())))
243 }
244 "linux" => {
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)))
251 }
252 None => Ok(None),
253 }
254 }
255 _ => bail!("unknown drive type '{}' - internal error"),
256 }
257 }
258 None => {
259 bail!("no such drive '{}'", drive);
260 }
261 }
262 }
263
264 /// Get the media changer (MediaChange + name) associated with a tape drive.
265 ///
266 /// This fail if the drive has no associated changer device.
267 pub fn required_media_changer(
268 config: &SectionConfigData,
269 drive: &str,
270 ) -> Result<(Box<dyn MediaChange>, String), Error> {
271 match media_changer(config, drive) {
272 Ok(Some(result)) => {
273 Ok(result)
274 }
275 Ok(None) => {
276 bail!("drive '{}' has no associated changer device", drive);
277 },
278 Err(err) => {
279 Err(err)
280 }
281 }
282 }
283
284 /// Opens a tape drive (this fails if there is no media loaded)
285 pub fn open_drive(
286 config: &SectionConfigData,
287 drive: &str,
288 ) -> Result<Box<dyn TapeDriver>, Error> {
289
290 match config.sections.get(drive) {
291 Some((section_type_name, config)) => {
292 match section_type_name.as_ref() {
293 "virtual" => {
294 let tape = VirtualTapeDrive::deserialize(config)?;
295 let handle = tape.open()?;
296 Ok(Box::new(handle))
297 }
298 "linux" => {
299 let tape = LinuxTapeDrive::deserialize(config)?;
300 let handle = tape.open()?;
301 Ok(Box::new(handle))
302 }
303 _ => bail!("unknown drive type '{}' - internal error"),
304 }
305 }
306 None => {
307 bail!("no such drive '{}'", drive);
308 }
309 }
310 }
311
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.
315 ///
316 /// Returns a handle to the opened drive and the media labels.
317 pub fn request_and_load_media(
318 worker: &WorkerTask,
319 config: &SectionConfigData,
320 drive: &str,
321 label: &MediaLabel,
322 ) -> Result<(
323 Box<dyn TapeDriver>,
324 MediaId,
325 ), Error> {
326
327 let check_label = |handle: &mut dyn TapeDriver, uuid: &proxmox::tools::Uuid| {
328 if let Ok((Some(media_id), _)) = handle.read_label() {
329 task_log!(
330 worker,
331 "found media label {} ({})",
332 media_id.label.label_text,
333 media_id.label.uuid,
334 );
335
336 if media_id.label.uuid == *uuid {
337 return Ok(media_id);
338 }
339 }
340 bail!("read label failed (please label all tapes first)");
341 };
342
343 match config.sections.get(drive) {
344 Some((section_type_name, config)) => {
345 match section_type_name.as_ref() {
346 "virtual" => {
347 let mut tape = VirtualTapeDrive::deserialize(config)?;
348
349 let label_text = label.label_text.clone();
350
351 tape.load_media(&label_text)?;
352
353 let mut handle: Box<dyn TapeDriver> = Box::new(tape.open()?);
354
355 let media_id = check_label(handle.as_mut(), &label.uuid)?;
356
357 Ok((handle, media_id))
358 }
359 "linux" => {
360 let drive_config = LinuxTapeDrive::deserialize(config)?;
361
362 let label_text = label.label_text.clone();
363
364 if drive_config.changer.is_some() {
365
366 let mut changer = MtxMediaChanger::with_drive_config(&drive_config)?;
367 changer.load_media(&label_text)?;
368
369 let mut handle: Box<dyn TapeDriver> = Box::new(drive_config.open()?);
370
371 let media_id = check_label(handle.as_mut(), &label.uuid)?;
372
373 return Ok((handle, media_id));
374 }
375
376 task_log!(worker, "Please insert media '{}' into drive '{}'", label_text, drive);
377
378 let to = "root@localhost"; // fixme
379
380 send_load_media_email(drive, &label_text, to)?;
381
382 let mut last_media_uuid = None;
383 let mut last_error = None;
384
385 loop {
386 worker.check_abort()?;
387
388 let mut handle = match drive_config.open() {
389 Ok(handle) => handle,
390 Err(err) => {
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);
395 }
396 for _ in 0..50 { // delay 5 seconds
397 worker.check_abort()?;
398 std::thread::sleep(std::time::Duration::from_millis(100));
399 }
400 continue;
401 }
402 };
403
404 match handle.read_label() {
405 Ok((Some(media_id), _)) => {
406 if media_id.label.uuid == label.uuid {
407 task_log!(
408 worker,
409 "found media label {} ({})",
410 media_id.label.label_text,
411 media_id.label.uuid.to_string(),
412 );
413 return Ok((Box::new(handle), media_id));
414 } else if Some(media_id.label.uuid.clone()) != last_media_uuid {
415 task_log!(
416 worker,
417 "wrong media label {} ({})",
418 media_id.label.label_text,
419 media_id.label.uuid.to_string(),
420 );
421 last_media_uuid = Some(media_id.label.uuid);
422 }
423 }
424 Ok((None, _)) => {
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;
428 }
429 }
430 Err(err) => {
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);
435 }
436 }
437 }
438
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));
443 }
444 }
445 }
446 _ => bail!("drive type '{}' not implemented!"),
447 }
448 }
449 None => {
450 bail!("no such drive '{}'", drive);
451 }
452 }
453 }
454
455 /// Aquires an exclusive lock for the tape device
456 ///
457 /// Basically calls lock_device_path() using the configured drive path.
458 pub fn lock_tape_device(
459 config: &SectionConfigData,
460 drive: &str,
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))
465 }
466
467 /// Writes the given state for the specified drive
468 ///
469 /// This function does not lock, so make sure the drive is locked
470 pub fn set_tape_device_state(
471 drive: &str,
472 state: &str,
473 ) -> Result<(), Error> {
474
475 let mut path = PathBuf::from(crate::tape::DRIVE_STATE_DIR);
476 path.push(drive);
477
478 let backup_user = crate::backup::backup_user()?;
479 let mode = nix::sys::stat::Mode::from_bits_truncate(0o0644);
480 let options = CreateOptions::new()
481 .perm(mode)
482 .owner(backup_user.uid)
483 .group(backup_user.gid);
484
485 replace_file(path, state.as_bytes(), options)
486 }
487
488 /// Get the device state
489 pub fn get_tape_device_state(
490 config: &SectionConfigData,
491 drive: &str,
492 ) -> Result<Option<String>, Error> {
493 let path = format!("/run/proxmox-backup/drive-state/{}", drive);
494 let state = file_read_optional_string(path)?;
495
496 let device_path = tape_device_path(config, drive)?;
497 if test_device_path_lock(&device_path)? {
498 Ok(state)
499 } else {
500 Ok(None)
501 }
502 }
503
504 fn tape_device_path(
505 config: &SectionConfigData,
506 drive: &str,
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() {
511 "virtual" => {
512 VirtualTapeDrive::deserialize(config)?.path
513 }
514 "linux" => {
515 LinuxTapeDrive::deserialize(config)?.path
516 }
517 _ => bail!("unknown drive type '{}' - internal error"),
518 };
519 Ok(path)
520 }
521 None => {
522 bail!("no such drive '{}'", drive);
523 }
524 }
525 }
526
527 pub struct DeviceLockGuard(std::fs::File);
528
529 // Aquires an exclusive lock on `device_path`
530 //
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> {
534
535 let lock_name = crate::tools::systemd::escape_unit(device_path, true);
536
537 let mut path = std::path::PathBuf::from("/var/lock");
538 path.push(lock_name);
539
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))?;
543
544 let backup_user = crate::backup::backup_user()?;
545 fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?;
546
547 Ok(DeviceLockGuard(file))
548 }
549
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> {
553
554 let lock_name = crate::tools::systemd::escape_unit(device_path, true);
555
556 let mut path = std::path::PathBuf::from("/var/lock");
557 path.push(lock_name);
558
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
563 Ok(()) => {},
564 // file was locked, return true
565 Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => return Ok(true),
566 Err(err) => bail!("{}", err),
567 }
568
569 let backup_user = crate::backup::backup_user()?;
570 fchown(file.as_raw_fd(), Some(backup_user.uid), Some(backup_user.gid))?;
571
572 Ok(false)
573 }