]> git.proxmox.com Git - proxmox-backup.git/blob - src/tape/drive/virtual_tape.rs
tape: extend MediaChange trait to return MtxStatus
[proxmox-backup.git] / src / tape / drive / virtual_tape.rs
1 // Note: This is only for test an debug
2
3 use std::fs::File;
4 use std::io;
5
6 use anyhow::{bail, format_err, Error};
7 use serde::{Serialize, Deserialize};
8
9 use proxmox::tools::{
10 fs::{replace_file, CreateOptions},
11 };
12
13 use crate::{
14 backup::KeyConfig,
15 tape::{
16 TapeWrite,
17 TapeRead,
18 changer::{
19 MediaChange,
20 MtxStatus,
21 DriveStatus,
22 ElementStatus,
23 StorageElementStatus,
24 },
25 drive::{
26 VirtualTapeDrive,
27 TapeDriver,
28 },
29 file_formats::{
30 MediaSetLabel,
31 MediaContentHeader,
32 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
33 BlockedReader,
34 BlockedWriter,
35 },
36 helpers::{
37 EmulateTapeReader,
38 EmulateTapeWriter,
39 },
40 },
41 };
42
43 impl VirtualTapeDrive {
44
45 /// This needs to lock the drive
46 pub fn open(&self) -> Result<VirtualTapeHandle, Error> {
47 proxmox::try_block!({
48 let mut lock_path = std::path::PathBuf::from(&self.path);
49 lock_path.push(".drive.lck");
50
51 let timeout = std::time::Duration::new(10, 0);
52 let lock = proxmox::tools::fs::open_file_locked(&lock_path, timeout, true)?;
53
54 Ok(VirtualTapeHandle {
55 _lock: lock,
56 drive_name: self.name.clone(),
57 max_size: self.max_size.unwrap_or(64*1024*1024),
58 path: std::path::PathBuf::from(&self.path),
59 })
60 }).map_err(|err: Error| format_err!("open drive '{}' ({}) failed - {}", self.name, self.path, err))
61 }
62 }
63
64 #[derive(Serialize,Deserialize)]
65 struct VirtualTapeStatus {
66 name: String,
67 pos: usize,
68 }
69
70 #[derive(Serialize,Deserialize)]
71 struct VirtualDriveStatus {
72 current_tape: Option<VirtualTapeStatus>,
73 }
74
75 #[derive(Serialize,Deserialize)]
76 struct TapeIndex {
77 files: usize,
78 }
79
80 pub struct VirtualTapeHandle {
81 drive_name: String,
82 path: std::path::PathBuf,
83 max_size: usize,
84 _lock: File,
85 }
86
87 impl VirtualTapeHandle {
88
89 fn status_file_path(&self) -> std::path::PathBuf {
90 let mut path = self.path.clone();
91 path.push("drive-status.json");
92 path
93 }
94
95 fn tape_index_path(&self, tape_name: &str) -> std::path::PathBuf {
96 let mut path = self.path.clone();
97 path.push(format!("tape-{}.json", tape_name));
98 path
99 }
100
101 fn tape_file_path(&self, tape_name: &str, pos: usize) -> std::path::PathBuf {
102 let mut path = self.path.clone();
103 path.push(format!("tapefile-{}-{}.json", pos, tape_name));
104 path
105 }
106
107 fn load_tape_index(&self, tape_name: &str) -> Result<TapeIndex, Error> {
108 let path = self.tape_index_path(tape_name);
109 let raw = proxmox::tools::fs::file_get_contents(&path)?;
110 if raw.is_empty() {
111 return Ok(TapeIndex { files: 0 });
112 }
113 let data: TapeIndex = serde_json::from_slice(&raw)?;
114 Ok(data)
115 }
116
117 fn store_tape_index(&self, tape_name: &str, index: &TapeIndex) -> Result<(), Error> {
118 let path = self.tape_index_path(tape_name);
119 let raw = serde_json::to_string_pretty(&serde_json::to_value(index)?)?;
120
121 let options = CreateOptions::new();
122 replace_file(&path, raw.as_bytes(), options)?;
123 Ok(())
124 }
125
126 fn truncate_tape(&self, tape_name: &str, pos: usize) -> Result<usize, Error> {
127 let mut index = self.load_tape_index(tape_name)?;
128
129 if index.files <= pos {
130 return Ok(index.files)
131 }
132
133 for i in pos..index.files {
134 let path = self.tape_file_path(tape_name, i);
135 let _ = std::fs::remove_file(path);
136 }
137
138 index.files = pos;
139
140 self.store_tape_index(tape_name, &index)?;
141
142 Ok(index.files)
143 }
144
145 fn load_status(&self) -> Result<VirtualDriveStatus, Error> {
146 let path = self.status_file_path();
147
148 let default = serde_json::to_value(VirtualDriveStatus {
149 current_tape: None,
150 })?;
151
152 let data = proxmox::tools::fs::file_get_json(&path, Some(default))?;
153 let status: VirtualDriveStatus = serde_json::from_value(data)?;
154 Ok(status)
155 }
156
157 fn store_status(&self, status: &VirtualDriveStatus) -> Result<(), Error> {
158 let path = self.status_file_path();
159 let raw = serde_json::to_string_pretty(&serde_json::to_value(status)?)?;
160
161 let options = CreateOptions::new();
162 replace_file(&path, raw.as_bytes(), options)?;
163 Ok(())
164 }
165
166 fn online_media_label_texts(&self) -> Result<Vec<String>, Error> {
167 let mut list = Vec::new();
168 for entry in std::fs::read_dir(&self.path)? {
169 let entry = entry?;
170 let path = entry.path();
171 if path.is_file() && path.extension() == Some(std::ffi::OsStr::new("json")) {
172 if let Some(name) = path.file_stem() {
173 if let Some(name) = name.to_str() {
174 if let Some(label) = name.strip_prefix("tape-") {
175 list.push(label.to_string());
176 }
177 }
178 }
179 }
180 }
181 Ok(list)
182 }
183
184 }
185
186 impl TapeDriver for VirtualTapeHandle {
187
188 fn sync(&mut self) -> Result<(), Error> {
189 Ok(()) // do nothing for now
190 }
191
192 fn current_file_number(&mut self) -> Result<u64, Error> {
193 let status = self.load_status()
194 .map_err(|err| format_err!("current_file_number failed: {}", err.to_string()))?;
195
196 match status.current_tape {
197 Some(VirtualTapeStatus { pos, .. }) => { Ok(pos as u64)},
198 None => bail!("current_file_number failed: drive is empty (no tape loaded)."),
199 }
200 }
201
202 fn read_next_file(&mut self) -> Result<Option<Box<dyn TapeRead>>, io::Error> {
203 let mut status = self.load_status()
204 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
205
206 match status.current_tape {
207 Some(VirtualTapeStatus { ref name, ref mut pos }) => {
208
209 let index = self.load_tape_index(name)
210 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
211
212 if *pos >= index.files {
213 return Ok(None); // EOM
214 }
215
216 let path = self.tape_file_path(name, *pos);
217 let file = std::fs::OpenOptions::new()
218 .read(true)
219 .open(path)?;
220
221 *pos += 1;
222 self.store_status(&status)
223 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
224
225 let reader = Box::new(file);
226 let reader = Box::new(EmulateTapeReader::new(reader));
227
228 match BlockedReader::open(reader)? {
229 Some(reader) => Ok(Some(Box::new(reader))),
230 None => Ok(None),
231 }
232 }
233 None => proxmox::io_bail!("drive is empty (no tape loaded)."),
234 }
235 }
236
237 fn write_file(&mut self) -> Result<Box<dyn TapeWrite>, io::Error> {
238 let mut status = self.load_status()
239 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
240
241 match status.current_tape {
242 Some(VirtualTapeStatus { ref name, ref mut pos }) => {
243
244 let mut index = self.load_tape_index(name)
245 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
246
247 for i in *pos..index.files {
248 let path = self.tape_file_path(name, i);
249 let _ = std::fs::remove_file(path);
250 }
251
252 let mut used_space = 0;
253 for i in 0..*pos {
254 let path = self.tape_file_path(name, i);
255 used_space += path.metadata()?.len() as usize;
256
257 }
258 index.files = *pos + 1;
259
260 self.store_tape_index(name, &index)
261 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
262
263 let path = self.tape_file_path(name, *pos);
264 let file = std::fs::OpenOptions::new()
265 .write(true)
266 .create(true)
267 .truncate(true)
268 .open(path)?;
269
270 *pos = index.files;
271
272 self.store_status(&status)
273 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
274
275 let mut free_space = 0;
276 if used_space < self.max_size {
277 free_space = self.max_size - used_space;
278 }
279
280 let writer = Box::new(file);
281 let writer = Box::new(EmulateTapeWriter::new(writer, free_space));
282 let writer = Box::new(BlockedWriter::new(writer));
283
284 Ok(writer)
285 }
286 None => proxmox::io_bail!("drive is empty (no tape loaded)."),
287 }
288 }
289
290 fn move_to_eom(&mut self) -> Result<(), Error> {
291 let mut status = self.load_status()?;
292 match status.current_tape {
293 Some(VirtualTapeStatus { ref name, ref mut pos }) => {
294
295 let index = self.load_tape_index(name)
296 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
297
298 *pos = index.files;
299 self.store_status(&status)
300 .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?;
301
302 Ok(())
303 }
304 None => bail!("drive is empty (no tape loaded)."),
305 }
306 }
307
308 fn rewind(&mut self) -> Result<(), Error> {
309 let mut status = self.load_status()?;
310 match status.current_tape {
311 Some(ref mut tape_status) => {
312 tape_status.pos = 0;
313 self.store_status(&status)?;
314 Ok(())
315 }
316 None => bail!("drive is empty (no tape loaded)."),
317 }
318 }
319
320 fn erase_media(&mut self, _fast: bool) -> Result<(), Error> {
321 let mut status = self.load_status()?;
322 match status.current_tape {
323 Some(VirtualTapeStatus { ref name, ref mut pos }) => {
324 *pos = self.truncate_tape(name, 0)?;
325 self.store_status(&status)?;
326 Ok(())
327 }
328 None => bail!("drive is empty (no tape loaded)."),
329 }
330 }
331
332 fn write_media_set_label(
333 &mut self,
334 media_set_label: &MediaSetLabel,
335 key_config: Option<&KeyConfig>,
336 ) -> Result<(), Error> {
337
338 self.set_encryption(None)?;
339
340 if key_config.is_some() {
341 bail!("encryption is not implemented - internal error");
342 }
343
344 let mut status = self.load_status()?;
345 match status.current_tape {
346 Some(VirtualTapeStatus { ref name, ref mut pos }) => {
347 *pos = self.truncate_tape(name, 1)?;
348 let pos = *pos;
349 self.store_status(&status)?;
350
351 if pos == 0 {
352 bail!("media is empty (no label).");
353 }
354 if pos != 1 {
355 bail!("write_media_set_label: truncate failed - got wrong pos '{}'", pos);
356 }
357
358 let raw = serde_json::to_string_pretty(&serde_json::to_value(media_set_label)?)?;
359 let header = MediaContentHeader::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, raw.len() as u32);
360
361 {
362 let mut writer = self.write_file()?;
363 writer.write_header(&header, raw.as_bytes())?;
364 writer.finish(false)?;
365 }
366
367 Ok(())
368 }
369 None => bail!("drive is empty (no tape loaded)."),
370 }
371 }
372
373 fn eject_media(&mut self) -> Result<(), Error> {
374 let status = VirtualDriveStatus {
375 current_tape: None,
376 };
377 self.store_status(&status)
378 }
379 }
380
381 impl MediaChange for VirtualTapeHandle {
382
383 fn drive_number(&self) -> u64 {
384 0
385 }
386
387 fn drive_name(&self) -> &str {
388 &self.drive_name
389 }
390
391 fn status(&mut self) -> Result<MtxStatus, Error> {
392
393 let drive_status = self.load_status()?;
394
395 let mut drives = Vec::new();
396
397 if let Some(current_tape) = &drive_status.current_tape {
398 drives.push(DriveStatus {
399 loaded_slot: None,
400 status: ElementStatus::VolumeTag(current_tape.name.clone()),
401 drive_serial_number: None,
402 vendor: None,
403 model: None,
404 element_address: 0,
405 });
406 }
407
408 // This implementation is lame, because we do not have fixed
409 // slot-assignment here.
410
411 let mut slots = Vec::new();
412 let label_texts = self.online_media_label_texts()?;
413 let max_slots = ((label_texts.len() + 7)/8) * 8;
414
415 for i in 0..max_slots {
416 let status = if let Some(label_text) = label_texts.get(i) {
417 ElementStatus::VolumeTag(label_text.clone())
418 } else {
419 ElementStatus::Empty
420 };
421 slots.push(StorageElementStatus {
422 import_export: false,
423 status,
424 element_address: (i + 1) as u16,
425 });
426 }
427
428 Ok(MtxStatus { drives, slots, transports: Vec::new() })
429 }
430
431 fn transfer_media(&mut self, _from: u64, _to: u64) -> Result<MtxStatus, Error> {
432 bail!("media tranfer is not implemented!");
433 }
434
435 fn export_media(&mut self, _label_text: &str) -> Result<Option<u64>, Error> {
436 bail!("media export is not implemented!");
437 }
438
439 fn load_media_from_slot(&mut self, slot: u64) -> Result<MtxStatus, Error> {
440 if slot < 1 {
441 bail!("invalid slot ID {}", slot);
442 }
443
444 let label_texts = self.online_media_label_texts()?;
445
446 if slot > label_texts.len() as u64 {
447 bail!("slot {} is empty", slot);
448 }
449
450 self.load_media(&label_texts[slot as usize - 1])
451 }
452
453 /// Try to load media
454 ///
455 /// We automatically create an empty virtual tape here (if it does
456 /// not exist already)
457 fn load_media(&mut self, label: &str) -> Result<MtxStatus, Error> {
458 let name = format!("tape-{}.json", label);
459 let mut path = self.path.clone();
460 path.push(&name);
461 if !path.exists() {
462 eprintln!("unable to find tape {} - creating file {:?}", label, path);
463 let index = TapeIndex { files: 0 };
464 self.store_tape_index(label, &index)?;
465 }
466
467 let status = VirtualDriveStatus {
468 current_tape: Some(VirtualTapeStatus {
469 name: label.to_string(),
470 pos: 0,
471 }),
472 };
473 self.store_status(&status)?;
474
475 self.status()
476 }
477
478 fn unload_media(&mut self, _target_slot: Option<u64>) -> Result<MtxStatus, Error> {
479 // Note: we currently simply ignore target_slot
480 self.eject_media()?;
481 self.status()
482 }
483
484 fn clean_drive(&mut self) -> Result<MtxStatus, Error> {
485 // do nothing
486 self.status()
487 }
488 }
489
490 impl MediaChange for VirtualTapeDrive {
491
492 fn drive_number(&self) -> u64 {
493 0
494 }
495
496 fn drive_name(&self) -> &str {
497 &self.name
498 }
499
500 fn status(&mut self) -> Result<MtxStatus, Error> {
501 let mut handle = self.open()?;
502 handle.status()
503 }
504
505 fn transfer_media(&mut self, from: u64, to: u64) -> Result<MtxStatus, Error> {
506 let mut handle = self.open()?;
507 handle.transfer_media(from, to)
508 }
509
510 fn export_media(&mut self, label_text: &str) -> Result<Option<u64>, Error> {
511 let mut handle = self.open()?;
512 handle.export_media(label_text)
513 }
514
515 fn load_media_from_slot(&mut self, slot: u64) -> Result<MtxStatus, Error> {
516 let mut handle = self.open()?;
517 handle.load_media_from_slot(slot)
518 }
519
520 fn load_media(&mut self, label_text: &str) -> Result<MtxStatus, Error> {
521 let mut handle = self.open()?;
522 handle.load_media(label_text)
523 }
524
525 fn unload_media(&mut self, target_slot: Option<u64>) -> Result<MtxStatus, Error> {
526 let mut handle = self.open()?;
527 handle.unload_media(target_slot)
528 }
529
530 fn online_media_label_texts(&mut self) -> Result<Vec<String>, Error> {
531 let handle = self.open()?;
532 handle.online_media_label_texts()
533 }
534
535 fn clean_drive(&mut self) -> Result<MtxStatus, Error> {
536 let mut handle = self.open()?;
537 handle.clean_drive()
538 }
539 }