1 // Note: This is only for test an debug
6 use anyhow
::{bail, format_err, Error}
;
7 use serde
::{Serialize, Deserialize}
;
11 fs
::{replace_file, CreateOptions}
,
26 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
37 impl VirtualTapeDrive
{
39 /// This needs to lock the drive
40 pub fn open(&self) -> Result
<VirtualTapeHandle
, Error
> {
41 let mut lock_path
= std
::path
::PathBuf
::from(&self.path
);
42 lock_path
.push(".drive.lck");
44 let timeout
= std
::time
::Duration
::new(10, 0);
45 let lock
= proxmox
::tools
::fs
::open_file_locked(&lock_path
, timeout
, true)?
;
47 Ok(VirtualTapeHandle
{
49 max_size
: self.max_size
.unwrap_or(64*1024*1024),
50 path
: std
::path
::PathBuf
::from(&self.path
),
55 #[derive(Serialize,Deserialize)]
56 struct VirtualTapeStatus
{
61 #[derive(Serialize,Deserialize)]
62 struct VirtualDriveStatus
{
63 current_tape
: Option
<VirtualTapeStatus
>,
66 #[derive(Serialize,Deserialize)]
71 pub struct VirtualTapeHandle
{
72 path
: std
::path
::PathBuf
,
77 impl VirtualTapeHandle
{
79 pub fn insert_tape(&self, _tape_filename
: &str) {
83 pub fn eject_tape(&self) {
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
)?
;
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
)?
;
165 impl TapeDriver
for VirtualTapeHandle
{
167 fn sync(&mut self) -> Result
<(), Error
> {
168 Ok(()) // do nothing for now
171 fn current_file_number(&mut self) -> Result
<usize, Error
> {
172 let status
= self.load_status()
173 .map_err(|err
| format_err
!("current_file_number failed: {}", err
.to_string()))?
;
175 match status
.current_tape
{
176 Some(VirtualTapeStatus { pos, .. }
) => { Ok(pos)}
,
177 None
=> bail
!("current_file_number failed: drive is empty (no tape loaded)."),
181 fn read_next_file(&mut self) -> Result
<Option
<Box
<dyn TapeRead
>>, io
::Error
> {
182 let mut status
= self.load_status()
183 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
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 if *pos
>= index
.files
{
192 return Ok(None
); // EOM
195 let path
= self.tape_file_path(name
, *pos
);
196 let file
= std
::fs
::OpenOptions
::new()
201 self.store_status(&status
)
202 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
204 let reader
= Box
::new(file
);
205 let reader
= Box
::new(EmulateTapeReader
::new(reader
));
207 match BlockedReader
::open(reader
)?
{
208 Some(reader
) => Ok(Some(Box
::new(reader
))),
212 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
216 fn write_file(&mut self) -> Result
<Box
<dyn TapeWrite
>, io
::Error
> {
217 let mut status
= self.load_status()
218 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
220 match status
.current_tape
{
221 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
223 let mut index
= self.load_tape_index(name
)
224 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
226 for i
in *pos
..index
.files
{
227 let path
= self.tape_file_path(name
, i
);
228 let _
= std
::fs
::remove_file(path
);
231 let mut used_space
= 0;
233 let path
= self.tape_file_path(name
, i
);
234 used_space
+= path
.metadata()?
.len() as usize;
237 index
.files
= *pos
+ 1;
239 self.store_tape_index(name
, &index
)
240 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
242 let path
= self.tape_file_path(name
, *pos
);
243 let file
= std
::fs
::OpenOptions
::new()
251 self.store_status(&status
)
252 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
254 let mut free_space
= 0;
255 if used_space
< self.max_size
{
256 free_space
= self.max_size
- used_space
;
259 let writer
= Box
::new(file
);
260 let writer
= Box
::new(EmulateTapeWriter
::new(writer
, free_space
));
261 let writer
= Box
::new(BlockedWriter
::new(writer
));
265 None
=> proxmox
::io_bail
!("drive is empty (no tape loaded)."),
269 fn move_to_eom(&mut self) -> Result
<(), Error
> {
270 let mut status
= self.load_status()?
;
271 match status
.current_tape
{
272 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
274 let index
= self.load_tape_index(name
)
275 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
278 self.store_status(&status
)
279 .map_err(|err
| io
::Error
::new(io
::ErrorKind
::Other
, err
.to_string()))?
;
283 None
=> bail
!("drive is empty (no tape loaded)."),
287 fn rewind(&mut self) -> Result
<(), Error
> {
288 let mut status
= self.load_status()?
;
289 match status
.current_tape
{
290 Some(ref mut tape_status
) => {
292 self.store_status(&status
)?
;
295 None
=> bail
!("drive is empty (no tape loaded)."),
299 fn erase_media(&mut self, _fast
: bool
) -> Result
<(), Error
> {
300 let mut status
= self.load_status()?
;
301 match status
.current_tape
{
302 Some(VirtualTapeStatus { ref name, ref mut pos }
) => {
303 *pos
= self.truncate_tape(name
, 0)?
;
304 self.store_status(&status
)?
;
307 None
=> bail
!("drive is empty (no tape loaded)."),
311 fn write_media_set_label(&mut self, media_set_label
: &MediaSetLabel
) -> Result
<Uuid
, 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
, 1)?
;
318 self.store_status(&status
)?
;
321 bail
!("media is empty (no label).");
324 bail
!("write_media_set_label: truncate failed - got wrong pos '{}'", pos
);
327 let raw
= serde_json
::to_string_pretty(&serde_json
::to_value(media_set_label
)?
)?
;
328 let header
= MediaContentHeader
::new(PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
, raw
.len() as u32);
331 let mut writer
= self.write_file()?
;
332 writer
.write_header(&header
, raw
.as_bytes())?
;
333 writer
.finish(false)?
;
336 Ok(Uuid
::from(header
.uuid
))
338 None
=> bail
!("drive is empty (no tape loaded)."),
342 fn eject_media(&mut self) -> Result
<(), Error
> {
343 let status
= VirtualDriveStatus
{
346 self.store_status(&status
)
350 impl MediaChange
for VirtualTapeHandle
{
352 /// Try to load media
354 /// We automatically create an empty virtual tape here (if it does
355 /// not exist already)
356 fn load_media(&mut self, label
: &str) -> Result
<(), Error
> {
357 let name
= format
!("tape-{}.json", label
);
358 let mut path
= self.path
.clone();
361 eprintln
!("unable to find tape {} - creating file {:?}", label
, path
);
362 let index
= TapeIndex { files: 0 }
;
363 self.store_tape_index(label
, &index
)?
;
366 let status
= VirtualDriveStatus
{
367 current_tape
: Some(VirtualTapeStatus
{
368 name
: label
.to_string(),
372 self.store_status(&status
)
375 fn unload_media(&mut self) -> Result
<(), Error
> {
380 fn eject_on_unload(&self) -> bool
{
384 fn list_media_changer_ids(&self) -> Result
<Vec
<String
>, Error
> {
385 let mut list
= Vec
::new();
386 for entry
in std
::fs
::read_dir(&self.path
)?
{
388 let path
= entry
.path();
389 if path
.is_file() && path
.extension() == Some(std
::ffi
::OsStr
::new("json")) {
390 if let Some(name
) = path
.file_stem() {
391 if let Some(name
) = name
.to_str() {
392 if name
.starts_with("tape-") {
393 list
.push(name
[5..].to_string());
403 impl MediaChange
for VirtualTapeDrive
{
405 fn load_media(&mut self, changer_id
: &str) -> Result
<(), Error
> {
406 let mut handle
= self.open()?
;
407 handle
.load_media(changer_id
)
410 fn unload_media(&mut self) -> Result
<(), Error
> {
411 let mut handle
= self.open()?
;
412 handle
.eject_media()?
;
416 fn eject_on_unload(&self) -> bool
{
420 fn list_media_changer_ids(&self) -> Result
<Vec
<String
>, Error
> {
421 let handle
= self.open()?
;
422 handle
.list_media_changer_ids()