3 use std
::convert
::TryFrom
;
5 use anyhow
::{bail, format_err, Error}
;
14 section_config
::SectionConfigData
,
27 tools
::compute_file_csum
,
36 drive
::check_drive_exists
,
61 request_and_load_media
,
63 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
64 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
,
65 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
66 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
67 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
,
73 pub const ROUTER
: Router
= Router
::new()
74 .post(&API_METHOD_RESTORE
);
81 schema
: DATASTORE_SCHEMA
,
84 description
: "Media set UUID.",
93 /// Restore data from media-set
97 rpcenv
: &mut dyn RpcEnvironment
,
98 ) -> Result
<Value
, Error
> {
100 let datastore
= DataStore
::lookup_datastore(&store
)?
;
102 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
104 let status_path
= Path
::new(TAPE_STATUS_DIR
);
105 let inventory
= Inventory
::load(status_path
)?
;
107 let media_set_uuid
= media_set
.parse()?
;
109 let pool
= inventory
.lookup_media_set_pool(&media_set_uuid
)?
;
111 let (config
, _digest
) = config
::media_pool
::config()?
;
112 let pool_config
: MediaPoolConfig
= config
.lookup("pool", &pool
)?
;
114 let (drive_config
, _digest
) = config
::drive
::config()?
;
115 // early check before starting worker
116 check_drive_exists(&drive_config
, &pool_config
.drive
)?
;
118 let to_stdout
= if rpcenv
.env_type() == RpcEnvironmentType
::CLI { true }
else { false }
;
120 let upid_str
= WorkerTask
::new_thread(
127 let _lock
= MediaPool
::lock(status_path
, &pool
)?
;
129 let members
= inventory
.compute_media_set_members(&media_set_uuid
)?
;
131 let media_list
= members
.media_list();
133 let mut media_id_list
= Vec
::new();
135 for (seq_nr
, media_uuid
) in media_list
.iter().enumerate() {
138 bail
!("media set {} is incomplete (missing member {}).", media_set_uuid
, seq_nr
);
140 Some(media_uuid
) => {
141 media_id_list
.push(inventory
.lookup_media(media_uuid
).unwrap());
146 let drive
= &pool_config
.drive
;
148 worker
.log(format
!("Restore mediaset '{}'", media_set
));
149 worker
.log(format
!("Pool: {}", pool
));
150 worker
.log(format
!("Datastore: {}", store
));
151 worker
.log(format
!("Drive: {}", drive
));
153 "Required media list: {}",
155 .map(|media_id
| media_id
.label
.label_text
.as_str())
156 .collect
::<Vec
<&str>>()
160 for media_id
in media_id_list
.iter() {
161 request_and_restore_media(
171 worker
.log(format
!("Restore mediaset '{}' done", media_set
));
179 /// Request and restore complete media without using existing catalog (create catalog instead)
180 pub fn request_and_restore_media(
183 drive_config
: &SectionConfigData
,
185 datastore
: &DataStore
,
187 ) -> Result
<(), Error
> {
189 let media_set_uuid
= match media_id
.media_set_label
{
190 None
=> bail
!("restore_media: no media set - internal error"),
191 Some(ref set
) => &set
.uuid
,
194 let (mut drive
, info
) = request_and_load_media(worker
, &drive_config
, &drive_name
, &media_id
.label
)?
;
196 match info
.media_set_label
{
198 bail
!("missing media set label on media {} ({})",
199 media_id
.label
.label_text
, media_id
.label
.uuid
);
202 if &set
.uuid
!= media_set_uuid
{
203 bail
!("wrong media set label on media {} ({} != {})",
204 media_id
.label
.label_text
, media_id
.label
.uuid
,
210 restore_media(worker
, &mut drive
, &info
, Some((datastore
, authid
)), false)
213 /// Restore complete media content and catalog
215 /// Only create the catalog if target is None.
216 pub fn restore_media(
218 drive
: &mut Box
<dyn TapeDriver
>,
220 target
: Option
<(&DataStore
, &Authid
)>,
222 ) -> Result
<(), Error
> {
224 let status_path
= Path
::new(TAPE_STATUS_DIR
);
225 let mut catalog
= MediaCatalog
::create_temporary_database(status_path
, media_id
, false)?
;
228 let current_file_number
= drive
.current_file_number()?
;
229 let reader
= match drive
.read_next_file()?
{
231 worker
.log(format
!("detected EOT after {} files", current_file_number
));
234 Some(reader
) => reader
,
237 restore_archive(worker
, reader
, current_file_number
, target
, &mut catalog
, verbose
)?
;
240 MediaCatalog
::finish_temporary_database(status_path
, &media_id
.label
.uuid
, true)?
;
245 fn restore_archive
<'a
>(
247 mut reader
: Box
<dyn 'a
+ TapeRead
>,
248 current_file_number
: u64,
249 target
: Option
<(&DataStore
, &Authid
)>,
250 catalog
: &mut MediaCatalog
,
252 ) -> Result
<(), Error
> {
254 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
255 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
256 bail
!("missing MediaContentHeader");
259 //println!("Found MediaContentHeader: {:?}", header);
261 match header
.content_magic
{
262 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
| PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
=> {
263 bail
!("unexpected content magic (label)");
265 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
=> {
266 let snapshot
= reader
.read_exact_allocated(header
.size
as usize)?
;
267 let snapshot
= std
::str::from_utf8(&snapshot
)
268 .map_err(|_
| format_err
!("found snapshot archive with non-utf8 characters in name"))?
;
269 worker
.log(format
!("Found snapshot archive: {} {}", current_file_number
, snapshot
));
271 let backup_dir
: BackupDir
= snapshot
.parse()?
;
273 if let Some((datastore
, authid
)) = target
.as_ref() {
275 let (owner
, _group_lock
) = datastore
.create_locked_backup_group(backup_dir
.group(), authid
)?
;
276 if *authid
!= &owner
{ // only the owner is allowed to create additional snapshots
277 bail
!("restore '{}' failed - owner check failed ({} != {})", snapshot
, authid
, owner
);
280 let (rel_path
, is_new
, _snap_lock
) = datastore
.create_locked_backup_dir(&backup_dir
)?
;
281 let mut path
= datastore
.base_path();
285 worker
.log(format
!("restore snapshot {}", backup_dir
));
287 match restore_snapshot_archive(reader
, &path
) {
289 std
::fs
::remove_dir_all(&path
)?
;
290 bail
!("restore snapshot {} failed - {}", backup_dir
, err
);
293 std
::fs
::remove_dir_all(&path
)?
;
294 worker
.log(format
!("skip incomplete snapshot {}", backup_dir
));
297 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, snapshot
)?
;
298 catalog
.commit_if_large()?
;
305 reader
.skip_to_end()?
; // read all data
306 if let Ok(false) = reader
.is_incomplete() {
307 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, snapshot
)?
;
308 catalog
.commit_if_large()?
;
311 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
=> {
313 worker
.log(format
!("Found chunk archive: {}", current_file_number
));
314 let datastore
= target
.as_ref().map(|t
| t
.0);
316 if let Some(chunks
) = restore_chunk_archive(worker
, reader
, datastore
, verbose
)?
{
317 catalog
.start_chunk_archive(Uuid
::from(header
.uuid
), current_file_number
)?
;
318 for digest
in chunks
.iter() {
319 catalog
.register_chunk(&digest
)?
;
321 worker
.log(format
!("register {} chunks", chunks
.len()));
322 catalog
.end_chunk_archive()?
;
323 catalog
.commit_if_large()?
;
326 _
=> bail
!("unknown content magic {:?}", header
.content_magic
),
334 fn restore_chunk_archive
<'a
>(
336 reader
: Box
<dyn 'a
+ TapeRead
>,
337 datastore
: Option
<&DataStore
>,
339 ) -> Result
<Option
<Vec
<[u8;32]>>, Error
> {
341 let mut chunks
= Vec
::new();
343 let mut decoder
= ChunkArchiveDecoder
::new(reader
);
345 let result
: Result
<_
, Error
> = proxmox
::try_block
!({
347 match decoder
.next_chunk()?
{
348 Some((digest
, blob
)) => {
350 if let Some(datastore
) = datastore
{
351 let chunk_exists
= datastore
.cond_touch_chunk(&digest
, false)?
;
355 if blob
.crypt_mode()?
== CryptMode
::None
{
356 blob
.decode(None
, Some(&digest
))?
; // verify digest
359 worker
.log(format
!("Insert chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
361 datastore
.insert_chunk(&blob
, &digest
)?
;
364 worker
.log(format
!("Found existing chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
369 worker
.log(format
!("Found chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
381 Ok(()) => Ok(Some(chunks
)),
383 let reader
= decoder
.reader();
385 // check if this stream is marked incomplete
386 if let Ok(true) = reader
.is_incomplete() {
387 return Ok(Some(chunks
));
390 // check if this is an aborted stream without end marker
391 if let Ok(false) = reader
.has_end_marker() {
392 worker
.log(format
!("missing stream end marker"));
396 // else the archive is corrupt
402 fn restore_snapshot_archive
<'a
>(
403 reader
: Box
<dyn 'a
+ TapeRead
>,
404 snapshot_path
: &Path
,
405 ) -> Result
<bool
, Error
> {
407 let mut decoder
= pxar
::decoder
::sync
::Decoder
::from_std(reader
)?
;
408 match try_restore_snapshot_archive(&mut decoder
, snapshot_path
) {
409 Ok(()) => return Ok(true),
411 let reader
= decoder
.input();
413 // check if this stream is marked incomplete
414 if let Ok(true) = reader
.is_incomplete() {
418 // check if this is an aborted stream without end marker
419 if let Ok(false) = reader
.has_end_marker() {
423 // else the archive is corrupt
429 fn try_restore_snapshot_archive
<R
: pxar
::decoder
::SeqRead
>(
430 decoder
: &mut pxar
::decoder
::sync
::Decoder
<R
>,
431 snapshot_path
: &Path
,
432 ) -> Result
<(), Error
> {
434 let _root
= match decoder
.next() {
435 None
=> bail
!("missing root entry"),
439 pxar
::EntryKind
::Directory
=> { /* Ok */ }
440 _
=> bail
!("wrong root entry type"),
446 let root_path
= Path
::new("/");
447 let manifest_file_name
= OsStr
::new(MANIFEST_BLOB_NAME
);
449 let mut manifest
= None
;
452 let entry
= match decoder
.next() {
454 Some(entry
) => entry?
,
456 let entry_path
= entry
.path();
459 pxar
::EntryKind
::File { .. }
=> { /* Ok */ }
460 _
=> bail
!("wrong entry type for {:?}", entry_path
),
462 match entry_path
.parent() {
463 None
=> bail
!("wrong parent for {:?}", entry_path
),
466 bail
!("wrong parent for {:?}", entry_path
);
471 let filename
= entry
.file_name();
472 let mut contents
= match decoder
.contents() {
473 None
=> bail
!("missing file content"),
474 Some(contents
) => contents
,
477 let mut archive_path
= snapshot_path
.to_owned();
478 archive_path
.push(&filename
);
480 let mut tmp_path
= archive_path
.clone();
481 tmp_path
.set_extension("tmp");
483 if filename
== manifest_file_name
{
485 let blob
= DataBlob
::load_from_reader(&mut contents
)?
;
486 let options
= CreateOptions
::new();
487 replace_file(&tmp_path
, blob
.raw_data(), options
)?
;
489 manifest
= Some(BackupManifest
::try_from(blob
)?
);
491 let mut tmpfile
= std
::fs
::OpenOptions
::new()
496 .map_err(|err
| format_err
!("restore {:?} failed - {}", tmp_path
, err
))?
;
498 std
::io
::copy(&mut contents
, &mut tmpfile
)?
;
500 if let Err(err
) = std
::fs
::rename(&tmp_path
, &archive_path
) {
501 bail
!("Atomic rename file {:?} failed - {}", archive_path
, err
);
506 let manifest
= match manifest
{
507 None
=> bail
!("missing manifest"),
508 Some(manifest
) => manifest
,
511 for item
in manifest
.files() {
512 let mut archive_path
= snapshot_path
.to_owned();
513 archive_path
.push(&item
.filename
);
515 match archive_type(&item
.filename
)?
{
516 ArchiveType
::DynamicIndex
=> {
517 let index
= DynamicIndexReader
::open(&archive_path
)?
;
518 let (csum
, size
) = index
.compute_csum();
519 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
521 ArchiveType
::FixedIndex
=> {
522 let index
= FixedIndexReader
::open(&archive_path
)?
;
523 let (csum
, size
) = index
.compute_csum();
524 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
526 ArchiveType
::Blob
=> {
527 let mut tmpfile
= std
::fs
::File
::open(&archive_path
)?
;
528 let (csum
, size
) = compute_file_csum(&mut tmpfile
)?
;
529 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
535 let mut manifest_path
= snapshot_path
.to_owned();
536 manifest_path
.push(MANIFEST_BLOB_NAME
);
537 let mut tmp_manifest_path
= manifest_path
.clone();
538 tmp_manifest_path
.set_extension("tmp");
540 if let Err(err
) = std
::fs
::rename(&tmp_manifest_path
, &manifest_path
) {
541 bail
!("Atomic rename manifest {:?} failed - {}", manifest_path
, err
);