1 // Note: This is only for test an debug
6 use anyhow
::{bail, format_err, Error}
;
7 use serde
::{Serialize, Deserialize}
;
10 fs
::{replace_file, CreateOptions}
,
13 use pbs_config
::key_config
::KeyConfig
;
38 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
43 /// This needs to lock the drive
44 pub fn open_virtual_tape_drive(config
: &VirtualTapeDrive
) -> Result
<VirtualTapeHandle
, Error
> {
45 proxmox_lang
::try_block
!({
46 let mut lock_path
= std
::path
::PathBuf
::from(&config
.path
);
47 lock_path
.push(".drive.lck");
49 let options
= CreateOptions
::new();
50 let timeout
= std
::time
::Duration
::new(10, 0);
51 let lock
= proxmox
::tools
::fs
::open_file_locked(&lock_path
, timeout
, true, options
)?
;
53 Ok(VirtualTapeHandle
{
55 drive_name
: config
.name
.clone(),
56 max_size
: config
.max_size
.unwrap_or(64*1024*1024),
57 path
: std
::path
::PathBuf
::from(&config
.path
),
59 }).map_err(|err
: Error
| format_err
!("open drive '{}' ({}) failed - {}", config
.name
, config
.path
, err
))
62 #[derive(Serialize,Deserialize)]
63 struct VirtualTapeStatus
{
68 #[derive(Serialize,Deserialize)]
69 struct VirtualDriveStatus
{
70 current_tape
: Option
<VirtualTapeStatus
>,
73 #[derive(Serialize,Deserialize)]
78 pub struct VirtualTapeHandle
{
80 path
: std
::path
::PathBuf
,
85 impl VirtualTapeHandle
{
87 fn status_file_path(&self) -> std
::path
::PathBuf
{
88 let mut path
= self.path
.clone();
89 path
.push("drive-status.json");
93 fn tape_index_path(&self, tape_name
: &str) -> std
::path
::PathBuf
{
94 let mut path
= self.path
.clone();
95 path
.push(format
!("tape-{}.json", tape_name
));
99 fn tape_file_path(&self, tape_name
: &str, pos
: usize) -> std
::path
::PathBuf
{
100 let mut path
= self.path
.clone();
101 path
.push(format
!("tapefile-{}-{}.json", pos
, tape_name
));
105 fn load_tape_index(&self, tape_name
: &str) -> Result
<TapeIndex
, Error
> {
106 let path
= self.tape_index_path(tape_name
);
107 let raw
= proxmox
::tools
::fs
::file_get_contents(&path
)?
;
109 return Ok(TapeIndex { files: 0 }
);
111 let data
: TapeIndex
= serde_json
::from_slice(&raw
)?
;
115 fn store_tape_index(&self, tape_name
: &str, index
: &TapeIndex
) -> Result
<(), Error
> {
116 let path
= self.tape_index_path(tape_name
);
117 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(index
)?
)?
;
119 let options
= CreateOptions
::new();
120 replace_file(&path
, raw
.as_bytes(), options
, false)?
;
124 fn truncate_tape(&self, tape_name
: &str, pos
: usize) -> Result
<usize, Error
> {
125 let mut index
= self.load_tape_index(tape_name
)?
;
127 if index
.files
<= pos
{
128 return Ok(index
.files
)
131 for i
in pos
..index
.files
{
132 let path
= self.tape_file_path(tape_name
, i
);
133 let _
= std
::fs
::remove_file(path
);
138 self.store_tape_index(tape_name
, &index
)?
;
143 fn load_status(&self) -> Result
<VirtualDriveStatus
, Error
> {
144 let path
= self.status_file_path();
146 let default = serde_json
::to_value(VirtualDriveStatus
{
150 let data
= proxmox
::tools
::fs
::file_get_json(&path
, Some(default))?
;
151 let status
: VirtualDriveStatus
= serde_json
::from_value(data
)?
;
155 fn store_status(&self, status
: &VirtualDriveStatus
) -> Result
<(), Error
> {
156 let path
= self.status_file_path();
157 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(status
)?
)?
;
159 let options
= CreateOptions
::new();
160 replace_file(&path
, raw
.as_bytes(), options
, false)?
;
164 fn online_media_label_texts(&self) -> Result
<Vec
<String
>, Error
> {
165 let mut list
= Vec
::new();
166 for entry
in std
::fs
::read_dir(&self.path
)?
{
168 let path
= entry
.path();
169 if path
.is_file() && path
.extension() == Some(std
::ffi
::OsStr
::new("json")) {
170 if let Some(name
) = path
.file_stem() {
171 if let Some(name
) = name
.to_str() {
172 if let Some(label
) = name
.strip_prefix("tape-") {
173 list
.push(label
.to_string());
183 fn forward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
184 let mut status
= self.load_status()?
;
185 match status
.current_tape
{
186 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
188 let index
= self.load_tape_index(name
)
189 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
191 let new_pos
= *pos
+ count
;
192 if new_pos
<= index
.files
{
195 bail
!("forward_space_count_files failed: move beyond EOT");
198 self.store_status(&status
)
199 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
203 None
=> bail
!("drive is empty (no tape loaded)."),
207 // Note: behavior differs from LTO, because we always position at
209 fn backward_space_count_files(&mut self, count
: usize) -> Result
<(), Error
> {
210 let mut status
= self.load_status()?
;
211 match status
.current_tape
{
212 Some(VirtualTapeStatus { ref mut pos, .. }
) => {
217 bail
!("backward_space_count_files failed: move before BOT");
220 self.store_status(&status
)
221 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
225 None
=> bail
!("drive is empty (no tape loaded)."),
231 impl TapeDriver
for VirtualTapeHandle
{
233 fn sync(&mut self) -> Result
<(), Error
> {
234 Ok(()) // do nothing for now
237 fn current_file_number(&mut self) -> Result
<u64, Error
> {
238 let status
= self.load_status()
239 .map_err(|err
| format_err
!("current_file_number failed: {}", err
.to_string()))?
;
241 match status
.current_tape
{
242 Some(VirtualTapeStatus { pos, .. }
) => { Ok(pos as u64)}
,
243 None
=> bail
!("current_file_number failed: drive is empty (no tape loaded)."),
247 /// Move to last file
248 fn move_to_last_file(&mut self) -> Result
<(), Error
> {
250 self.move_to_eom(false)?
;
252 if self.current_file_number()?
== 0 {
253 bail
!("move_to_last_file failed - media contains no data");
256 self.backward_space_count_files(1)?
;
261 fn move_to_file(&mut self, file
: u64) -> Result
<(), Error
> {
262 let mut status
= self.load_status()?
;
263 match status
.current_tape
{
264 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
266 let index
= self.load_tape_index(name
)
267 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
269 if file
as usize > index
.files
{
270 bail
!("invalid file nr");
273 *pos
= file
as usize;
275 self.store_status(&status
)
276 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
280 None
=> bail
!("drive is empty (no tape loaded)."),
284 fn read_next_file(&mut self) -> Result
<Box
<dyn TapeRead
>, BlockReadError
> {
285 let mut status
= self.load_status()
286 .map_err(|err
| BlockReadError
::Error(io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string())))?
;
288 match status
.current_tape
{
289 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
291 let index
= self.load_tape_index(name
)
292 .map_err(|err
| BlockReadError
::Error(io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string())))?
;
294 if *pos
>= index
.files
{
295 return Err(BlockReadError
::EndOfStream
);
298 let path
= self.tape_file_path(name
, *pos
);
299 let file
= std
::fs
::OpenOptions
::new()
304 self.store_status(&status
)
305 .map_err(|err
| BlockReadError
::Error(io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string())))?
;
307 let reader
= EmulateTapeReader
::new(file
);
308 let reader
= BlockedReader
::open(reader
)?
;
312 return Err(BlockReadError
::Error(proxmox
::io_format_err
!("drive is empty (no tape loaded).")));
317 fn write_file(&mut self) -> Result
<Box
<dyn TapeWrite
>, io
::Error
> {
318 let mut status
= self.load_status()
319 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
321 match status
.current_tape
{
322 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
324 let mut index
= self.load_tape_index(name
)
325 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
327 for i
in *pos
..index
.files
{
328 let path
= self.tape_file_path(name
, i
);
329 let _
= std
::fs
::remove_file(path
);
332 let mut used_space
= 0;
334 let path
= self.tape_file_path(name
, i
);
335 used_space
+= path
.metadata()?
.len() as usize;
338 index
.files
= *pos
+ 1;
340 self.store_tape_index(name
, &index
)
341 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
343 let path
= self.tape_file_path(name
, *pos
);
344 let file
= std
::fs
::OpenOptions
::new()
352 self.store_status(&status
)
353 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
355 let mut free_space
= 0;
356 if used_space
< self.max_size
{
357 free_space
= self.max_size
- used_space
;
360 let writer
= EmulateTapeWriter
::new(file
, free_space
);
361 let writer
= Box
::new(BlockedWriter
::new(writer
));
365 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
369 fn move_to_eom(&mut self, _write_missing_eof
: bool
) -> Result
<(), Error
> {
370 let mut status
= self.load_status()?
;
371 match status
.current_tape
{
372 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
374 let index
= self.load_tape_index(name
)
375 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
379 self.store_status(&status
)
380 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
384 None
=> bail
!("drive is empty (no tape loaded)."),
388 fn rewind(&mut self) -> Result
<(), Error
> {
389 let mut status
= self.load_status()?
;
390 match status
.current_tape
{
391 Some(ref mut tape_status
) => {
393 self.store_status(&status
)?
;
396 None
=> bail
!("drive is empty (no tape loaded)."),
400 fn format_media(&mut self, _fast
: bool
) -> Result
<(), Error
> {
401 let mut status
= self.load_status()?
;
402 match status
.current_tape
{
403 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
404 *pos
= self.truncate_tape(name
, 0)?
;
405 self.store_status(&status
)?
;
408 None
=> bail
!("drive is empty (no tape loaded)."),
412 fn write_media_set_label(
414 media_set_label
: &MediaSetLabel
,
415 key_config
: Option
<&KeyConfig
>,
416 ) -> Result
<(), Error
> {
418 self.set_encryption(None
)?
;
420 if key_config
.is_some() {
421 bail
!("encryption is not implemented - internal error");
424 let mut status
= self.load_status()?
;
425 match status
.current_tape
{
426 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
427 *pos
= self.truncate_tape(name
, 1)?
;
429 self.store_status(&status
)?
;
432 bail
!("media is empty (no label).");
435 bail
!("write_media_set_label: truncate failed - got wrong pos '{}'", pos
);
438 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(media_set_label
)?
)?
;
439 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, raw
.len() as u32);
442 let mut writer
= self.write_file()?
;
443 writer
.write_header(&header
, raw
.as_bytes())?
;
444 writer
.finish(false)?
;
449 None
=> bail
!("drive is empty (no tape loaded)."),
453 fn eject_media(&mut self) -> Result
<(), Error
> {
454 let status
= VirtualDriveStatus
{
457 self.store_status(&status
)
461 impl MediaChange
for VirtualTapeHandle
{
463 fn drive_number(&self) -> u64 {
467 fn drive_name(&self) -> &str {
471 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
473 let drive_status
= self.load_status()?
;
475 let mut drives
= Vec
::new();
477 if let Some(current_tape
) = &drive_status
.current_tape
{
478 drives
.push(DriveStatus
{
480 status
: ElementStatus
::VolumeTag(current_tape
.name
.clone()),
481 drive_serial_number
: None
,
488 // This implementation is lame, because we do not have fixed
489 // slot-assignment here.
491 let mut slots
= Vec
::new();
492 let label_texts
= self.online_media_label_texts()?
;
493 let max_slots
= ((label_texts
.len() + 7)/8) * 8;
495 for i
in 0..max_slots
{
496 let status
= if let Some(label_text
) = label_texts
.get(i
) {
497 ElementStatus
::VolumeTag(label_text
.clone())
501 slots
.push(StorageElementStatus
{
502 import_export
: false,
504 element_address
: (i
+ 1) as u16,
508 Ok(MtxStatus { drives, slots, transports: Vec::new() }
)
511 fn transfer_media(&mut self, _from
: u64, _to
: u64) -> Result
<MtxStatus
, Error
> {
512 bail
!("media transfer is not implemented!");
515 fn export_media(&mut self, _label_text
: &str) -> Result
<Option
<u64>, Error
> {
516 bail
!("media export is not implemented!");
519 fn load_media_from_slot(&mut self, slot
: u64) -> Result
<MtxStatus
, Error
> {
521 bail
!("invalid slot ID {}", slot
);
524 let label_texts
= self.online_media_label_texts()?
;
526 if slot
> label_texts
.len() as u64 {
527 bail
!("slot {} is empty", slot
);
530 self.load_media(&label_texts
[slot
as usize - 1])
533 /// Try to load media
535 /// We automatically create an empty virtual tape here (if it does
536 /// not exist already)
537 fn load_media(&mut self, label
: &str) -> Result
<MtxStatus
, Error
> {
538 let name
= format
!("tape-{}.json", label
);
539 let mut path
= self.path
.clone();
542 eprintln
!("unable to find tape {} - creating file {:?}", label
, path
);
543 let index
= TapeIndex { files: 0 }
;
544 self.store_tape_index(label
, &index
)?
;
547 let status
= VirtualDriveStatus
{
548 current_tape
: Some(VirtualTapeStatus
{
549 name
: label
.to_string(),
553 self.store_status(&status
)?
;
558 fn unload_media(&mut self, _target_slot
: Option
<u64>) -> Result
<MtxStatus
, Error
> {
559 // Note: we currently simply ignore target_slot
564 fn clean_drive(&mut self) -> Result
<MtxStatus
, Error
> {
570 impl MediaChange
for VirtualTapeDrive
{
572 fn drive_number(&self) -> u64 {
576 fn drive_name(&self) -> &str {
580 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
581 let mut handle
= open_virtual_tape_drive(self)?
;
585 fn transfer_media(&mut self, from
: u64, to
: u64) -> Result
<MtxStatus
, Error
> {
586 let mut handle
= open_virtual_tape_drive(self)?
;
587 handle
.transfer_media(from
, to
)
590 fn export_media(&mut self, label_text
: &str) -> Result
<Option
<u64>, Error
> {
591 let mut handle
= open_virtual_tape_drive(self)?
;
592 handle
.export_media(label_text
)
595 fn load_media_from_slot(&mut self, slot
: u64) -> Result
<MtxStatus
, Error
> {
596 let mut handle
= open_virtual_tape_drive(self)?
;
597 handle
.load_media_from_slot(slot
)
600 fn load_media(&mut self, label_text
: &str) -> Result
<MtxStatus
, Error
> {
601 let mut handle
= open_virtual_tape_drive(self)?
;
602 handle
.load_media(label_text
)
605 fn unload_media(&mut self, target_slot
: Option
<u64>) -> Result
<MtxStatus
, Error
> {
606 let mut handle
= open_virtual_tape_drive(self)?
;
607 handle
.unload_media(target_slot
)
610 fn online_media_label_texts(&mut self) -> Result
<Vec
<String
>, Error
> {
611 let handle
= open_virtual_tape_drive(self)?
;
612 handle
.online_media_label_texts()
615 fn clean_drive(&mut self) -> Result
<MtxStatus
, Error
> {
616 let mut handle
= open_virtual_tape_drive(self)?
;