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 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
62 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
,
63 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
64 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
65 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
,
70 request_and_load_media
,
75 pub const ROUTER
: Router
= Router
::new()
76 .post(&API_METHOD_RESTORE
);
83 schema
: DATASTORE_SCHEMA
,
86 description
: "Media set UUID.",
95 /// Restore data from media-set
99 rpcenv
: &mut dyn RpcEnvironment
,
100 ) -> Result
<Value
, Error
> {
102 let datastore
= DataStore
::lookup_datastore(&store
)?
;
104 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
106 let status_path
= Path
::new(TAPE_STATUS_DIR
);
107 let inventory
= Inventory
::load(status_path
)?
;
109 let media_set_uuid
= media_set
.parse()?
;
111 let pool
= inventory
.lookup_media_set_pool(&media_set_uuid
)?
;
113 let (config
, _digest
) = config
::media_pool
::config()?
;
114 let pool_config
: MediaPoolConfig
= config
.lookup("pool", &pool
)?
;
116 let (drive_config
, _digest
) = config
::drive
::config()?
;
117 // early check before starting worker
118 check_drive_exists(&drive_config
, &pool_config
.drive
)?
;
120 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
122 let upid_str
= WorkerTask
::new_thread(
129 let _lock
= MediaPool
::lock(status_path
, &pool
)?
;
131 let members
= inventory
.compute_media_set_members(&media_set_uuid
)?
;
133 let media_list
= members
.media_list();
135 let mut media_id_list
= Vec
::new();
137 let mut encryption_key_fingerprint
= None
;
139 for (seq_nr
, media_uuid
) in media_list
.iter().enumerate() {
142 bail
!("media set {} is incomplete (missing member {}).", media_set_uuid
, seq_nr
);
144 Some(media_uuid
) => {
145 let media_id
= inventory
.lookup_media(media_uuid
).unwrap();
146 if let Some(ref set
) = media_id
.media_set_label
{ // always true here
147 if encryption_key_fingerprint
.is_none() && set
.encryption_key_fingerprint
.is_some() {
148 encryption_key_fingerprint
= set
.encryption_key_fingerprint
.clone();
151 media_id_list
.push(media_id
);
156 let drive
= &pool_config
.drive
;
158 worker
.log(format
!("Restore mediaset '{}'", media_set
));
159 if let Some(fingerprint
) = encryption_key_fingerprint
{
160 worker
.log(format
!("Encryption key fingerprint: {}", fingerprint
));
162 worker
.log(format
!("Pool: {}", pool
));
163 worker
.log(format
!("Datastore: {}", store
));
164 worker
.log(format
!("Drive: {}", drive
));
166 "Required media list: {}",
168 .map(|media_id
| media_id
.label
.label_text
.as_str())
169 .collect
::<Vec
<&str>>()
173 for media_id
in media_id_list
.iter() {
174 request_and_restore_media(
184 worker
.log(format
!("Restore mediaset '{}' done", media_set
));
192 /// Request and restore complete media without using existing catalog (create catalog instead)
193 pub fn request_and_restore_media(
196 drive_config
: &SectionConfigData
,
198 datastore
: &DataStore
,
200 ) -> Result
<(), Error
> {
202 let media_set_uuid
= match media_id
.media_set_label
{
203 None
=> bail
!("restore_media: no media set - internal error"),
204 Some(ref set
) => &set
.uuid
,
207 let (mut drive
, info
) = request_and_load_media(worker
, &drive_config
, &drive_name
, &media_id
.label
)?
;
209 match info
.media_set_label
{
211 bail
!("missing media set label on media {} ({})",
212 media_id
.label
.label_text
, media_id
.label
.uuid
);
215 if &set
.uuid
!= media_set_uuid
{
216 bail
!("wrong media set label on media {} ({} != {})",
217 media_id
.label
.label_text
, media_id
.label
.uuid
,
220 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
221 .map(|fp
| (fp
, set
.uuid
.clone()));
223 drive
.set_encryption(encrypt_fingerprint
)?
;
227 restore_media(worker
, &mut drive
, &info
, Some((datastore
, authid
)), false)
230 /// Restore complete media content and catalog
232 /// Only create the catalog if target is None.
233 pub fn restore_media(
235 drive
: &mut Box
<dyn TapeDriver
>,
237 target
: Option
<(&DataStore
, &Authid
)>,
239 ) -> Result
<(), Error
> {
241 let status_path
= Path
::new(TAPE_STATUS_DIR
);
242 let mut catalog
= MediaCatalog
::create_temporary_database(status_path
, media_id
, false)?
;
245 let current_file_number
= drive
.current_file_number()?
;
246 let reader
= match drive
.read_next_file()?
{
248 worker
.log(format
!("detected EOT after {} files", current_file_number
));
251 Some(reader
) => reader
,
254 restore_archive(worker
, reader
, current_file_number
, target
, &mut catalog
, verbose
)?
;
257 MediaCatalog
::finish_temporary_database(status_path
, &media_id
.label
.uuid
, true)?
;
262 fn restore_archive
<'a
>(
264 mut reader
: Box
<dyn 'a
+ TapeRead
>,
265 current_file_number
: u64,
266 target
: Option
<(&DataStore
, &Authid
)>,
267 catalog
: &mut MediaCatalog
,
269 ) -> Result
<(), Error
> {
271 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
272 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
273 bail
!("missing MediaContentHeader");
276 //println!("Found MediaContentHeader: {:?}", header);
278 match header
.content_magic
{
279 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
| PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
=> {
280 bail
!("unexpected content magic (label)");
282 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
=> {
283 let snapshot
= reader
.read_exact_allocated(header
.size
as usize)?
;
284 let snapshot
= std
::str::from_utf8(&snapshot
)
285 .map_err(|_
| format_err
!("found snapshot archive with non-utf8 characters in name"))?
;
286 worker
.log(format
!("Found snapshot archive: {} {}", current_file_number
, snapshot
));
288 let backup_dir
: BackupDir
= snapshot
.parse()?
;
290 if let Some((datastore
, authid
)) = target
.as_ref() {
292 let (owner
, _group_lock
) = datastore
.create_locked_backup_group(backup_dir
.group(), authid
)?
;
293 if *authid
!= &owner
{ // only the owner is allowed to create additional snapshots
294 bail
!("restore '{}' failed - owner check failed ({} != {})", snapshot
, authid
, owner
);
297 let (rel_path
, is_new
, _snap_lock
) = datastore
.create_locked_backup_dir(&backup_dir
)?
;
298 let mut path
= datastore
.base_path();
302 worker
.log(format
!("restore snapshot {}", backup_dir
));
304 match restore_snapshot_archive(reader
, &path
) {
306 std
::fs
::remove_dir_all(&path
)?
;
307 bail
!("restore snapshot {} failed - {}", backup_dir
, err
);
310 std
::fs
::remove_dir_all(&path
)?
;
311 worker
.log(format
!("skip incomplete snapshot {}", backup_dir
));
314 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, snapshot
)?
;
315 catalog
.commit_if_large()?
;
322 reader
.skip_to_end()?
; // read all data
323 if let Ok(false) = reader
.is_incomplete() {
324 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, snapshot
)?
;
325 catalog
.commit_if_large()?
;
328 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
=> {
330 worker
.log(format
!("Found chunk archive: {}", current_file_number
));
331 let datastore
= target
.as_ref().map(|t
| t
.0);
333 if let Some(chunks
) = restore_chunk_archive(worker
, reader
, datastore
, verbose
)?
{
334 catalog
.start_chunk_archive(Uuid
::from(header
.uuid
), current_file_number
)?
;
335 for digest
in chunks
.iter() {
336 catalog
.register_chunk(&digest
)?
;
338 worker
.log(format
!("register {} chunks", chunks
.len()));
339 catalog
.end_chunk_archive()?
;
340 catalog
.commit_if_large()?
;
343 _
=> bail
!("unknown content magic {:?}", header
.content_magic
),
351 fn restore_chunk_archive
<'a
>(
353 reader
: Box
<dyn 'a
+ TapeRead
>,
354 datastore
: Option
<&DataStore
>,
356 ) -> Result
<Option
<Vec
<[u8;32]>>, Error
> {
358 let mut chunks
= Vec
::new();
360 let mut decoder
= ChunkArchiveDecoder
::new(reader
);
362 let result
: Result
<_
, Error
> = proxmox
::try_block
!({
363 while let Some((digest
, blob
)) = decoder
.next_chunk()?
{
364 if let Some(datastore
) = datastore
{
365 let chunk_exists
= datastore
.cond_touch_chunk(&digest
, false)?
;
369 if blob
.crypt_mode()?
== CryptMode
::None
{
370 blob
.decode(None
, Some(&digest
))?
; // verify digest
373 worker
.log(format
!("Insert chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
375 datastore
.insert_chunk(&blob
, &digest
)?
;
377 worker
.log(format
!("Found existing chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
380 worker
.log(format
!("Found chunk: {}", proxmox
::tools
::digest_to_hex(&digest
)));
388 Ok(()) => Ok(Some(chunks
)),
390 let reader
= decoder
.reader();
392 // check if this stream is marked incomplete
393 if let Ok(true) = reader
.is_incomplete() {
394 return Ok(Some(chunks
));
397 // check if this is an aborted stream without end marker
398 if let Ok(false) = reader
.has_end_marker() {
399 worker
.log("missing stream end marker".to_string());
403 // else the archive is corrupt
409 fn restore_snapshot_archive
<'a
>(
410 reader
: Box
<dyn 'a
+ TapeRead
>,
411 snapshot_path
: &Path
,
412 ) -> Result
<bool
, Error
> {
414 let mut decoder
= pxar
::decoder
::sync
::Decoder
::from_std(reader
)?
;
415 match try_restore_snapshot_archive(&mut decoder
, snapshot_path
) {
418 let reader
= decoder
.input();
420 // check if this stream is marked incomplete
421 if let Ok(true) = reader
.is_incomplete() {
425 // check if this is an aborted stream without end marker
426 if let Ok(false) = reader
.has_end_marker() {
430 // else the archive is corrupt
436 fn try_restore_snapshot_archive
<R
: pxar
::decoder
::SeqRead
>(
437 decoder
: &mut pxar
::decoder
::sync
::Decoder
<R
>,
438 snapshot_path
: &Path
,
439 ) -> Result
<(), Error
> {
441 let _root
= match decoder
.next() {
442 None
=> bail
!("missing root entry"),
446 pxar
::EntryKind
::Directory
=> { /* Ok */ }
447 _
=> bail
!("wrong root entry type"),
453 let root_path
= Path
::new("/");
454 let manifest_file_name
= OsStr
::new(MANIFEST_BLOB_NAME
);
456 let mut manifest
= None
;
459 let entry
= match decoder
.next() {
461 Some(entry
) => entry?
,
463 let entry_path
= entry
.path();
466 pxar
::EntryKind
::File { .. }
=> { /* Ok */ }
467 _
=> bail
!("wrong entry type for {:?}", entry_path
),
469 match entry_path
.parent() {
470 None
=> bail
!("wrong parent for {:?}", entry_path
),
473 bail
!("wrong parent for {:?}", entry_path
);
478 let filename
= entry
.file_name();
479 let mut contents
= match decoder
.contents() {
480 None
=> bail
!("missing file content"),
481 Some(contents
) => contents
,
484 let mut archive_path
= snapshot_path
.to_owned();
485 archive_path
.push(&filename
);
487 let mut tmp_path
= archive_path
.clone();
488 tmp_path
.set_extension("tmp");
490 if filename
== manifest_file_name
{
492 let blob
= DataBlob
::load_from_reader(&mut contents
)?
;
493 let options
= CreateOptions
::new();
494 replace_file(&tmp_path
, blob
.raw_data(), options
)?
;
496 manifest
= Some(BackupManifest
::try_from(blob
)?
);
498 let mut tmpfile
= std
::fs
::OpenOptions
::new()
503 .map_err(|err
| format_err
!("restore {:?} failed - {}", tmp_path
, err
))?
;
505 std
::io
::copy(&mut contents
, &mut tmpfile
)?
;
507 if let Err(err
) = std
::fs
::rename(&tmp_path
, &archive_path
) {
508 bail
!("Atomic rename file {:?} failed - {}", archive_path
, err
);
513 let manifest
= match manifest
{
514 None
=> bail
!("missing manifest"),
515 Some(manifest
) => manifest
,
518 for item
in manifest
.files() {
519 let mut archive_path
= snapshot_path
.to_owned();
520 archive_path
.push(&item
.filename
);
522 match archive_type(&item
.filename
)?
{
523 ArchiveType
::DynamicIndex
=> {
524 let index
= DynamicIndexReader
::open(&archive_path
)?
;
525 let (csum
, size
) = index
.compute_csum();
526 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
528 ArchiveType
::FixedIndex
=> {
529 let index
= FixedIndexReader
::open(&archive_path
)?
;
530 let (csum
, size
) = index
.compute_csum();
531 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
533 ArchiveType
::Blob
=> {
534 let mut tmpfile
= std
::fs
::File
::open(&archive_path
)?
;
535 let (csum
, size
) = compute_file_csum(&mut tmpfile
)?
;
536 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
542 let mut manifest_path
= snapshot_path
.to_owned();
543 manifest_path
.push(MANIFEST_BLOB_NAME
);
544 let mut tmp_manifest_path
= manifest_path
.clone();
545 tmp_manifest_path
.set_extension("tmp");
547 if let Err(err
) = std
::fs
::rename(&tmp_manifest_path
, &manifest_path
) {
548 bail
!("Atomic rename manifest {:?} failed - {}", manifest_path
, err
);