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}
,
28 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
39 impl VirtualTapeDrive
{
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");
46 let timeout
= std
::time
::Duration
::new(10, 0);
47 let lock
= proxmox
::tools
::fs
::open_file_locked(&lock_path
, timeout
, true)?
;
49 Ok(VirtualTapeHandle
{
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
),
58 #[derive(Serialize,Deserialize)]
59 struct VirtualTapeStatus
{
64 #[derive(Serialize,Deserialize)]
65 struct VirtualDriveStatus
{
66 current_tape
: Option
<VirtualTapeStatus
>,
69 #[derive(Serialize,Deserialize)]
74 pub struct VirtualTapeHandle
{
76 path
: std
::path
::PathBuf
,
81 impl VirtualTapeHandle
{
83 fn status_file_path(&self) -> std
::path
::PathBuf
{
84 let mut path
= self.path
.clone();
85 path
.push("drive-status.json");
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
));
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
));
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
)?
;
105 return Ok(TapeIndex { files: 0 }
);
107 let data
: TapeIndex
= serde_json
::from_slice(&raw
)?
;
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
)?
)?
;
115 let options
= CreateOptions
::new();
116 replace_file(&path
, raw
.as_bytes(), options
)?
;
120 fn truncate_tape(&self, tape_name
: &str, pos
: usize) -> Result
<usize, Error
> {
121 let mut index
= self.load_tape_index(tape_name
)?
;
123 if index
.files
<= pos
{
124 return Ok(index
.files
)
127 for i
in pos
..index
.files
{
128 let path
= self.tape_file_path(tape_name
, i
);
129 let _
= std
::fs
::remove_file(path
);
134 self.store_tape_index(tape_name
, &index
)?
;
139 fn load_status(&self) -> Result
<VirtualDriveStatus
, Error
> {
140 let path
= self.status_file_path();
142 let default = serde_json
::to_value(VirtualDriveStatus
{
146 let data
= proxmox
::tools
::fs
::file_get_json(&path
, Some(default))?
;
147 let status
: VirtualDriveStatus
= serde_json
::from_value(data
)?
;
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
)?
)?
;
155 let options
= CreateOptions
::new();
156 replace_file(&path
, raw
.as_bytes(), options
)?
;
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
)?
{
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());
180 impl TapeDriver
for VirtualTapeHandle
{
182 fn sync(&mut self) -> Result
<(), Error
> {
183 Ok(()) // do nothing for now
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()))?
;
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)."),
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()))?
;
200 match status
.current_tape
{
201 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
203 let index
= self.load_tape_index(name
)
204 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
206 if *pos
>= index
.files
{
207 return Ok(None
); // EOM
210 let path
= self.tape_file_path(name
, *pos
);
211 let file
= std
::fs
::OpenOptions
::new()
216 self.store_status(&status
)
217 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
219 let reader
= Box
::new(file
);
220 let reader
= Box
::new(EmulateTapeReader
::new(reader
));
222 match BlockedReader
::open(reader
)?
{
223 Some(reader
) => Ok(Some(Box
::new(reader
))),
227 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
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()))?
;
235 match status
.current_tape
{
236 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
238 let mut index
= self.load_tape_index(name
)
239 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
241 for i
in *pos
..index
.files
{
242 let path
= self.tape_file_path(name
, i
);
243 let _
= std
::fs
::remove_file(path
);
246 let mut used_space
= 0;
248 let path
= self.tape_file_path(name
, i
);
249 used_space
+= path
.metadata()?
.len() as usize;
252 index
.files
= *pos
+ 1;
254 self.store_tape_index(name
, &index
)
255 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
257 let path
= self.tape_file_path(name
, *pos
);
258 let file
= std
::fs
::OpenOptions
::new()
266 self.store_status(&status
)
267 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
269 let mut free_space
= 0;
270 if used_space
< self.max_size
{
271 free_space
= self.max_size
- used_space
;
274 let writer
= Box
::new(file
);
275 let writer
= Box
::new(EmulateTapeWriter
::new(writer
, free_space
));
276 let writer
= Box
::new(BlockedWriter
::new(writer
));
280 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
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 }
) => {
289 let index
= self.load_tape_index(name
)
290 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
293 self.store_status(&status
)
294 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
298 None
=> bail
!("drive is empty (no tape loaded)."),
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
) => {
307 self.store_status(&status
)?
;
310 None
=> bail
!("drive is empty (no tape loaded)."),
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
)?
;
322 None
=> bail
!("drive is empty (no tape loaded)."),
326 fn write_media_set_label(&mut self, media_set_label
: &MediaSetLabel
) -> Result
<(), Error
> {
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)?
;
333 self.store_status(&status
)?
;
336 bail
!("media is empty (no label).");
339 bail
!("write_media_set_label: truncate failed - got wrong pos '{}'", pos
);
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);
346 let mut writer
= self.write_file()?
;
347 writer
.write_header(&header
, raw
.as_bytes())?
;
348 writer
.finish(false)?
;
353 None
=> bail
!("drive is empty (no tape loaded)."),
357 fn eject_media(&mut self) -> Result
<(), Error
> {
358 let status
= VirtualDriveStatus
{
361 self.store_status(&status
)
365 impl MediaChange
for VirtualTapeHandle
{
367 fn drive_number(&self) -> u64 {
371 fn drive_name(&self) -> &str {
375 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
377 let drive_status
= self.load_status()?
;
379 let mut drives
= Vec
::new();
381 if let Some(current_tape
) = &drive_status
.current_tape
{
382 drives
.push(DriveStatus
{
384 status
: ElementStatus
::VolumeTag(current_tape
.name
.clone()),
388 // This implementation is lame, because we do not have fixed
389 // slot-assignment here.
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;
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())));
399 slots
.push((false, ElementStatus
::Empty
));
403 Ok(MtxStatus { drives, slots }
)
406 fn transfer_media(&mut self, _from
: u64, _to
: u64) -> Result
<(), Error
> {
407 bail
!("media tranfer is not implemented!");
410 fn export_media(&mut self, _changer_id
: &str) -> Result
<Option
<u64>, Error
> {
411 bail
!("media export is not implemented!");
414 fn load_media_from_slot(&mut self, slot
: u64) -> Result
<(), Error
> {
416 bail
!("invalid slot ID {}", slot
);
419 let changer_ids
= self.online_media_changer_ids()?
;
421 if slot
> changer_ids
.len() as u64 {
422 bail
!("slot {} is empty", slot
);
425 self.load_media(&changer_ids
[slot
as usize - 1])
428 /// Try to load media
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();
437 eprintln
!("unable to find tape {} - creating file {:?}", label
, path
);
438 let index
= TapeIndex { files: 0 }
;
439 self.store_tape_index(label
, &index
)?
;
442 let status
= VirtualDriveStatus
{
443 current_tape
: Some(VirtualTapeStatus
{
444 name
: label
.to_string(),
448 self.store_status(&status
)
451 fn unload_media(&mut self, _target_slot
: Option
<u64>) -> Result
<(), Error
> {
452 // Note: we currently simply ignore target_slot
457 fn eject_on_unload(&self) -> bool
{
461 fn clean_drive(&mut self) -> Result
<(), Error
> {
466 impl MediaChange
for VirtualTapeDrive
{
468 fn drive_number(&self) -> u64 {
472 fn drive_name(&self) -> &str {
476 fn status(&mut self) -> Result
<MtxStatus
, Error
> {
477 let mut handle
= self.open()?
;
481 fn transfer_media(&mut self, from
: u64, to
: u64) -> Result
<(), Error
> {
482 let mut handle
= self.open()?
;
483 handle
.transfer_media(from
, to
)
486 fn export_media(&mut self, changer_id
: &str) -> Result
<Option
<u64>, Error
> {
487 let mut handle
= self.open()?
;
488 handle
.export_media(changer_id
)
491 fn load_media_from_slot(&mut self, slot
: u64) -> Result
<(), Error
> {
492 let mut handle
= self.open()?
;
493 handle
.load_media_from_slot(slot
)
496 fn load_media(&mut self, changer_id
: &str) -> Result
<(), Error
> {
497 let mut handle
= self.open()?
;
498 handle
.load_media(changer_id
)
501 fn unload_media(&mut self, target_slot
: Option
<u64>) -> Result
<(), Error
> {
502 let mut handle
= self.open()?
;
503 handle
.unload_media(target_slot
)?
;
507 fn eject_on_unload(&self) -> bool
{
511 fn online_media_changer_ids(&mut self) -> Result
<Vec
<String
>, Error
> {
512 let handle
= self.open()?
;
513 handle
.online_media_changer_ids()
516 fn clean_drive(&mut self) -> Result
<(), Error
> {