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}
,
32 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
43 impl VirtualTapeDrive
{
45 /// This needs to lock the drive
46 pub fn open(&self) -> Result
<VirtualTapeHandle
, Error
> {
48 let mut lock_path
= std
::path
::PathBuf
::from(&self.path
);
49 lock_path
.push(".drive.lck");
51 let timeout
= std
::time
::Duration
::new(10, 0);
52 let lock
= proxmox
::tools
::fs
::open_file_locked(&lock_path
, timeout
, true)?
;
54 Ok(VirtualTapeHandle
{
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
),
60 }).map_err(|err
: Error
| format_err
!("open drive '{}' ({}) failed - {}", self.name
, self.path
, err
))
64 #[derive(Serialize,Deserialize)]
65 struct VirtualTapeStatus
{
70 #[derive(Serialize,Deserialize)]
71 struct VirtualDriveStatus
{
72 current_tape
: Option
<VirtualTapeStatus
>,
75 #[derive(Serialize,Deserialize)]
80 pub struct VirtualTapeHandle
{
82 path
: std
::path
::PathBuf
,
87 impl VirtualTapeHandle
{
89 fn status_file_path(&self) -> std
::path
::PathBuf
{
90 let mut path
= self.path
.clone();
91 path
.push("drive-status.json");
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
));
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
));
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
)?
;
111 return Ok(TapeIndex { files: 0 }
);
113 let data
: TapeIndex
= serde_json
::from_slice(&raw
)?
;
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
)?
)?
;
121 let options
= CreateOptions
::new();
122 replace_file(&path
, raw
.as_bytes(), options
)?
;
126 fn truncate_tape(&self, tape_name
: &str, pos
: usize) -> Result
<usize, Error
> {
127 let mut index
= self.load_tape_index(tape_name
)?
;
129 if index
.files
<= pos
{
130 return Ok(index
.files
)
133 for i
in pos
..index
.files
{
134 let path
= self.tape_file_path(tape_name
, i
);
135 let _
= std
::fs
::remove_file(path
);
140 self.store_tape_index(tape_name
, &index
)?
;
145 fn load_status(&self) -> Result
<VirtualDriveStatus
, Error
> {
146 let path
= self.status_file_path();
148 let default = serde_json
::to_value(VirtualDriveStatus
{
152 let data
= proxmox
::tools
::fs
::file_get_json(&path
, Some(default))?
;
153 let status
: VirtualDriveStatus
= serde_json
::from_value(data
)?
;
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
)?
)?
;
161 let options
= CreateOptions
::new();
162 replace_file(&path
, raw
.as_bytes(), options
)?
;
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
)?
{
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());
186 impl TapeDriver
for VirtualTapeHandle
{
188 fn sync(&mut self) -> Result
<(), Error
> {
189 Ok(()) // do nothing for now
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()))?
;
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)."),
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()))?
;
206 match status
.current_tape
{
207 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
209 let index
= self.load_tape_index(name
)
210 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
212 if *pos
>= index
.files
{
213 return Ok(None
); // EOM
216 let path
= self.tape_file_path(name
, *pos
);
217 let file
= std
::fs
::OpenOptions
::new()
222 self.store_status(&status
)
223 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
225 let reader
= Box
::new(file
);
226 let reader
= Box
::new(EmulateTapeReader
::new(reader
));
228 match BlockedReader
::open(reader
)?
{
229 Some(reader
) => Ok(Some(Box
::new(reader
))),
233 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
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()))?
;
241 match status
.current_tape
{
242 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
244 let mut index
= self.load_tape_index(name
)
245 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
247 for i
in *pos
..index
.files
{
248 let path
= self.tape_file_path(name
, i
);
249 let _
= std
::fs
::remove_file(path
);
252 let mut used_space
= 0;
254 let path
= self.tape_file_path(name
, i
);
255 used_space
+= path
.metadata()?
.len() as usize;
258 index
.files
= *pos
+ 1;
260 self.store_tape_index(name
, &index
)
261 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
263 let path
= self.tape_file_path(name
, *pos
);
264 let file
= std
::fs
::OpenOptions
::new()
272 self.store_status(&status
)
273 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
275 let mut free_space
= 0;
276 if used_space
< self.max_size
{
277 free_space
= self.max_size
- used_space
;
280 let writer
= Box
::new(file
);
281 let writer
= Box
::new(EmulateTapeWriter
::new(writer
, free_space
));
282 let writer
= Box
::new(BlockedWriter
::new(writer
));
286 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
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 }
) => {
295 let index
= self.load_tape_index(name
)
296 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
299 self.store_status(&status
)
300 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
304 None
=> bail
!("drive is empty (no tape loaded)."),
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
) => {
313 self.store_status(&status
)?
;
316 None
=> bail
!("drive is empty (no tape loaded)."),
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
)?
;
328 None
=> bail
!("drive is empty (no tape loaded)."),
332 fn write_media_set_label(
334 media_set_label
: &MediaSetLabel
,
335 key_config
: Option
<&KeyConfig
>,
336 ) -> Result
<(), Error
> {
338 self.set_encryption(None
)?
;
340 if key_config
.is_some() {
341 bail
!("encryption is not implemented - internal error");
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)?
;
349 self.store_status(&status
)?
;
352 bail
!("media is empty (no label).");
355 bail
!("write_media_set_label: truncate failed - got wrong pos '{}'", pos
);
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);
362 let mut writer
= self.write_file()?
;
363 writer
.write_header(&header
, raw
.as_bytes())?
;
364 writer
.finish(false)?
;
369 None
=> bail
!("drive is empty (no tape loaded)."),
373 fn eject_media(&mut self) -> Result
<(), Error
> {
374 let status
= VirtualDriveStatus
{
377 self.store_status(&status
)
381 impl MediaChange
for VirtualTapeHandle
{
383 fn drive_number(&self) -> u64 {
387 fn drive_name(&self) -> &str {
391 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
393 let drive_status
= self.load_status()?
;
395 let mut drives
= Vec
::new();
397 if let Some(current_tape
) = &drive_status
.current_tape
{
398 drives
.push(DriveStatus
{
400 status
: ElementStatus
::VolumeTag(current_tape
.name
.clone()),
401 drive_serial_number
: None
,
408 // This implementation is lame, because we do not have fixed
409 // slot-assignment here.
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;
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())
421 slots
.push(StorageElementStatus
{
422 import_export
: false,
424 element_address
: (i
+ 1) as u16,
428 Ok(MtxStatus { drives, slots, transports: Vec::new() }
)
431 fn transfer_media(&mut self, _from
: u64, _to
: u64) -> Result
<MtxStatus
, Error
> {
432 bail
!("media tranfer is not implemented!");
435 fn export_media(&mut self, _label_text
: &str) -> Result
<Option
<u64>, Error
> {
436 bail
!("media export is not implemented!");
439 fn load_media_from_slot(&mut self, slot
: u64) -> Result
<MtxStatus
, Error
> {
441 bail
!("invalid slot ID {}", slot
);
444 let label_texts
= self.online_media_label_texts()?
;
446 if slot
> label_texts
.len() as u64 {
447 bail
!("slot {} is empty", slot
);
450 self.load_media(&label_texts
[slot
as usize - 1])
453 /// Try to load media
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();
462 eprintln
!("unable to find tape {} - creating file {:?}", label
, path
);
463 let index
= TapeIndex { files: 0 }
;
464 self.store_tape_index(label
, &index
)?
;
467 let status
= VirtualDriveStatus
{
468 current_tape
: Some(VirtualTapeStatus
{
469 name
: label
.to_string(),
473 self.store_status(&status
)?
;
478 fn unload_media(&mut self, _target_slot
: Option
<u64>) -> Result
<MtxStatus
, Error
> {
479 // Note: we currently simply ignore target_slot
484 fn clean_drive(&mut self) -> Result
<MtxStatus
, Error
> {
490 impl MediaChange
for VirtualTapeDrive
{
492 fn drive_number(&self) -> u64 {
496 fn drive_name(&self) -> &str {
500 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
501 let mut handle
= self.open()?
;
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
)
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
)
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
)
520 fn load_media(&mut self, label_text
: &str) -> Result
<MtxStatus
, Error
> {
521 let mut handle
= self.open()?
;
522 handle
.load_media(label_text
)
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
)
530 fn online_media_label_texts(&mut self) -> Result
<Vec
<String
>, Error
> {
531 let handle
= self.open()?
;
532 handle
.online_media_label_texts()
535 fn clean_drive(&mut self) -> Result
<MtxStatus
, Error
> {
536 let mut handle
= self.open()?
;