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