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