3 use std
::convert
::TryFrom
;
5 use anyhow
::{bail, format_err, Error}
;
15 section_config
::SectionConfigData
,
30 tools
::compute_file_csum
,
40 cached_user_info
::CachedUserInfo
,
42 PRIV_DATASTORE_BACKUP
,
43 PRIV_DATASTORE_MODIFY
,
72 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
,
73 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
,
74 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_1
,
75 PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
,
76 PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
,
77 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
,
78 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
,
82 SnapshotArchiveHeader
,
86 request_and_load_media
,
88 set_tape_device_state
,
93 pub const ROUTER
: Router
= Router
::new()
94 .post(&API_METHOD_RESTORE
);
100 schema
: DATASTORE_SCHEMA
,
103 schema
: DRIVE_NAME_SCHEMA
,
106 description
: "Media set UUID.",
123 // Note: parameters are no uri parameter, so we need to test inside function body
124 description
: "The user needs Tape.Read privilege on /tape/pool/{pool} \
125 and /tape/drive/{drive}, Datastore.Backup privilege on /datastore/{store}.",
126 permission
: &Permission
::Anybody
,
129 /// Restore data from media-set
134 notify_user
: Option
<Userid
>,
135 owner
: Option
<Authid
>,
136 rpcenv
: &mut dyn RpcEnvironment
,
137 ) -> Result
<Value
, Error
> {
139 let auth_id
: Authid
= rpcenv
.get_auth_id().unwrap().parse()?
;
140 let user_info
= CachedUserInfo
::new()?
;
142 let privs
= user_info
.lookup_privs(&auth_id
, &["datastore", &store
]);
143 if (privs
& PRIV_DATASTORE_BACKUP
) == 0 {
144 bail
!("no permissions on /datastore/{}", store
);
147 if let Some(ref owner
) = owner
{
148 let correct_owner
= owner
== &auth_id
150 && !auth_id
.is_token()
151 && owner
.user() == auth_id
.user());
153 // same permission as changing ownership after syncing
154 if !correct_owner
&& privs
& PRIV_DATASTORE_MODIFY
== 0 {
155 bail
!("no permission to restore as '{}'", owner
);
159 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "drive", &drive
]);
160 if (privs
& PRIV_TAPE_READ
) == 0 {
161 bail
!("no permissions on /tape/drive/{}", drive
);
164 let media_set_uuid
= media_set
.parse()?
;
166 let status_path
= Path
::new(TAPE_STATUS_DIR
);
168 let _lock
= lock_media_set(status_path
, &media_set_uuid
, None
)?
;
170 let inventory
= Inventory
::load(status_path
)?
;
172 let pool
= inventory
.lookup_media_set_pool(&media_set_uuid
)?
;
174 let privs
= user_info
.lookup_privs(&auth_id
, &["tape", "pool", &pool
]);
175 if (privs
& PRIV_TAPE_READ
) == 0 {
176 bail
!("no permissions on /tape/pool/{}", pool
);
179 let datastore
= DataStore
::lookup_datastore(&store
)?
;
181 let (drive_config
, _digest
) = config
::drive
::config()?
;
183 // early check/lock before starting worker
184 let drive_lock
= lock_tape_device(&drive_config
, &drive
)?
;
186 let to_stdout
= rpcenv
.env_type() == RpcEnvironmentType
::CLI
;
188 let upid_str
= WorkerTask
::new_thread(
194 let _drive_lock
= drive_lock
; // keep lock guard
196 set_tape_device_state(&drive
, &worker
.upid().to_string())?
;
198 let members
= inventory
.compute_media_set_members(&media_set_uuid
)?
;
200 let media_list
= members
.media_list();
202 let mut media_id_list
= Vec
::new();
204 let mut encryption_key_fingerprint
= None
;
206 for (seq_nr
, media_uuid
) in media_list
.iter().enumerate() {
209 bail
!("media set {} is incomplete (missing member {}).", media_set_uuid
, seq_nr
);
211 Some(media_uuid
) => {
212 let media_id
= inventory
.lookup_media(media_uuid
).unwrap();
213 if let Some(ref set
) = media_id
.media_set_label
{ // always true here
214 if encryption_key_fingerprint
.is_none() && set
.encryption_key_fingerprint
.is_some() {
215 encryption_key_fingerprint
= set
.encryption_key_fingerprint
.clone();
218 media_id_list
.push(media_id
);
223 task_log
!(worker
, "Restore mediaset '{}'", media_set
);
224 if let Some(fingerprint
) = encryption_key_fingerprint
{
225 task_log
!(worker
, "Encryption key fingerprint: {}", fingerprint
);
227 task_log
!(worker
, "Pool: {}", pool
);
228 task_log
!(worker
, "Datastore: {}", store
);
229 task_log
!(worker
, "Drive: {}", drive
);
232 "Required media list: {}",
234 .map(|media_id
| media_id
.label
.label_text
.as_str())
235 .collect
::<Vec
<&str>>()
239 for media_id
in media_id_list
.iter() {
240 request_and_restore_media(
252 task_log
!(worker
, "Restore mediaset '{}' done", media_set
);
254 if let Err(err
) = set_tape_device_state(&drive
, "") {
257 "could not unset drive state for {}: {}",
270 /// Request and restore complete media without using existing catalog (create catalog instead)
271 pub fn request_and_restore_media(
274 drive_config
: &SectionConfigData
,
276 datastore
: &DataStore
,
278 notify_user
: &Option
<Userid
>,
279 owner
: &Option
<Authid
>,
280 ) -> Result
<(), Error
> {
282 let media_set_uuid
= match media_id
.media_set_label
{
283 None
=> bail
!("restore_media: no media set - internal error"),
284 Some(ref set
) => &set
.uuid
,
287 let email
= notify_user
289 .and_then(|userid
| lookup_user_email(userid
))
290 .or_else(|| lookup_user_email(&authid
.clone().into()));
292 let (mut drive
, info
) = request_and_load_media(worker
, &drive_config
, &drive_name
, &media_id
.label
, &email
)?
;
294 match info
.media_set_label
{
296 bail
!("missing media set label on media {} ({})",
297 media_id
.label
.label_text
, media_id
.label
.uuid
);
300 if &set
.uuid
!= media_set_uuid
{
301 bail
!("wrong media set label on media {} ({} != {})",
302 media_id
.label
.label_text
, media_id
.label
.uuid
,
305 let encrypt_fingerprint
= set
.encryption_key_fingerprint
.clone()
306 .map(|fp
| (fp
, set
.uuid
.clone()));
308 drive
.set_encryption(encrypt_fingerprint
)?
;
312 let restore_owner
= owner
.as_ref().unwrap_or(authid
);
314 restore_media(worker
, &mut drive
, &info
, Some((datastore
, restore_owner
)), false)
317 /// Restore complete media content and catalog
319 /// Only create the catalog if target is None.
320 pub fn restore_media(
322 drive
: &mut Box
<dyn TapeDriver
>,
324 target
: Option
<(&DataStore
, &Authid
)>,
326 ) -> Result
<(), Error
> {
328 let status_path
= Path
::new(TAPE_STATUS_DIR
);
329 let mut catalog
= MediaCatalog
::create_temporary_database(status_path
, media_id
, false)?
;
332 let current_file_number
= drive
.current_file_number()?
;
333 let reader
= match drive
.read_next_file()?
{
335 task_log
!(worker
, "detected EOT after {} files", current_file_number
);
338 Some(reader
) => reader
,
341 restore_archive(worker
, reader
, current_file_number
, target
, &mut catalog
, verbose
)?
;
344 MediaCatalog
::finish_temporary_database(status_path
, &media_id
.label
.uuid
, true)?
;
349 fn restore_archive
<'a
>(
351 mut reader
: Box
<dyn 'a
+ TapeRead
>,
352 current_file_number
: u64,
353 target
: Option
<(&DataStore
, &Authid
)>,
354 catalog
: &mut MediaCatalog
,
356 ) -> Result
<(), Error
> {
358 let header
: MediaContentHeader
= unsafe { reader.read_le_value()? }
;
359 if header
.magic
!= PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0
{
360 bail
!("missing MediaContentHeader");
363 //println!("Found MediaContentHeader: {:?}", header);
365 match header
.content_magic
{
366 PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0
| PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0
=> {
367 bail
!("unexpected content magic (label)");
369 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0
=> {
370 bail
!("unexpected snapshot archive version (v1.0)");
372 PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_1
=> {
373 let header_data
= reader
.read_exact_allocated(header
.size
as usize)?
;
375 let archive_header
: SnapshotArchiveHeader
= serde_json
::from_slice(&header_data
)
376 .map_err(|err
| format_err
!("unable to parse snapshot archive header - {}", err
))?
;
378 let datastore_name
= archive_header
.store
;
379 let snapshot
= archive_header
.snapshot
;
381 task_log
!(worker
, "File {}: snapshot archive {}:{}", current_file_number
, datastore_name
, snapshot
);
383 let backup_dir
: BackupDir
= snapshot
.parse()?
;
385 if let Some((datastore
, authid
)) = target
.as_ref() {
387 let (owner
, _group_lock
) = datastore
.create_locked_backup_group(backup_dir
.group(), authid
)?
;
388 if *authid
!= &owner
{ // only the owner is allowed to create additional snapshots
389 bail
!("restore '{}' failed - owner check failed ({} != {})", snapshot
, authid
, owner
);
392 let (rel_path
, is_new
, _snap_lock
) = datastore
.create_locked_backup_dir(&backup_dir
)?
;
393 let mut path
= datastore
.base_path();
397 task_log
!(worker
, "restore snapshot {}", backup_dir
);
399 match restore_snapshot_archive(worker
, reader
, &path
) {
401 std
::fs
::remove_dir_all(&path
)?
;
402 bail
!("restore snapshot {} failed - {}", backup_dir
, err
);
405 std
::fs
::remove_dir_all(&path
)?
;
406 task_log
!(worker
, "skip incomplete snapshot {}", backup_dir
);
409 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, &datastore_name
, &snapshot
)?
;
410 catalog
.commit_if_large()?
;
417 reader
.skip_to_end()?
; // read all data
418 if let Ok(false) = reader
.is_incomplete() {
419 catalog
.register_snapshot(Uuid
::from(header
.uuid
), current_file_number
, &datastore_name
, &snapshot
)?
;
420 catalog
.commit_if_large()?
;
423 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0
=> {
424 bail
!("unexpected chunk archive version (v1.0)");
426 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
=> {
427 let header_data
= reader
.read_exact_allocated(header
.size
as usize)?
;
429 let archive_header
: ChunkArchiveHeader
= serde_json
::from_slice(&header_data
)
430 .map_err(|err
| format_err
!("unable to parse chunk archive header - {}", err
))?
;
432 let source_datastore
= archive_header
.store
;
434 task_log
!(worker
, "File {}: chunk archive for datastore '{}'", current_file_number
, source_datastore
);
435 let datastore
= target
.as_ref().map(|t
| t
.0);
437 if let Some(chunks
) = restore_chunk_archive(worker
, reader
, datastore
, verbose
)?
{
438 catalog
.start_chunk_archive(Uuid
::from(header
.uuid
), current_file_number
, &source_datastore
)?
;
439 for digest
in chunks
.iter() {
440 catalog
.register_chunk(&digest
)?
;
442 task_log
!(worker
, "register {} chunks", chunks
.len());
443 catalog
.end_chunk_archive()?
;
444 catalog
.commit_if_large()?
;
447 _
=> bail
!("unknown content magic {:?}", header
.content_magic
),
455 fn restore_chunk_archive
<'a
>(
457 reader
: Box
<dyn 'a
+ TapeRead
>,
458 datastore
: Option
<&DataStore
>,
460 ) -> Result
<Option
<Vec
<[u8;32]>>, Error
> {
462 let mut chunks
= Vec
::new();
464 let mut decoder
= ChunkArchiveDecoder
::new(reader
);
466 let result
: Result
<_
, Error
> = proxmox
::try_block
!({
467 while let Some((digest
, blob
)) = decoder
.next_chunk()?
{
469 worker
.check_abort()?
;
471 if let Some(datastore
) = datastore
{
472 let chunk_exists
= datastore
.cond_touch_chunk(&digest
, false)?
;
476 if blob
.crypt_mode()?
== CryptMode
::None
{
477 blob
.decode(None
, Some(&digest
))?
; // verify digest
480 task_log
!(worker
, "Insert chunk: {}", proxmox
::tools
::digest_to_hex(&digest
));
482 datastore
.insert_chunk(&blob
, &digest
)?
;
484 task_log
!(worker
, "Found existing chunk: {}", proxmox
::tools
::digest_to_hex(&digest
));
487 task_log
!(worker
, "Found chunk: {}", proxmox
::tools
::digest_to_hex(&digest
));
495 Ok(()) => Ok(Some(chunks
)),
497 let reader
= decoder
.reader();
499 // check if this stream is marked incomplete
500 if let Ok(true) = reader
.is_incomplete() {
501 return Ok(Some(chunks
));
504 // check if this is an aborted stream without end marker
505 if let Ok(false) = reader
.has_end_marker() {
506 worker
.log("missing stream end marker".to_string());
510 // else the archive is corrupt
516 fn restore_snapshot_archive
<'a
>(
518 reader
: Box
<dyn 'a
+ TapeRead
>,
519 snapshot_path
: &Path
,
520 ) -> Result
<bool
, Error
> {
522 let mut decoder
= pxar
::decoder
::sync
::Decoder
::from_std(reader
)?
;
523 match try_restore_snapshot_archive(worker
, &mut decoder
, snapshot_path
) {
526 let reader
= decoder
.input();
528 // check if this stream is marked incomplete
529 if let Ok(true) = reader
.is_incomplete() {
533 // check if this is an aborted stream without end marker
534 if let Ok(false) = reader
.has_end_marker() {
538 // else the archive is corrupt
544 fn try_restore_snapshot_archive
<R
: pxar
::decoder
::SeqRead
>(
546 decoder
: &mut pxar
::decoder
::sync
::Decoder
<R
>,
547 snapshot_path
: &Path
,
548 ) -> Result
<(), Error
> {
550 let _root
= match decoder
.next() {
551 None
=> bail
!("missing root entry"),
555 pxar
::EntryKind
::Directory
=> { /* Ok */ }
556 _
=> bail
!("wrong root entry type"),
562 let root_path
= Path
::new("/");
563 let manifest_file_name
= OsStr
::new(MANIFEST_BLOB_NAME
);
565 let mut manifest
= None
;
568 worker
.check_abort()?
;
570 let entry
= match decoder
.next() {
572 Some(entry
) => entry?
,
574 let entry_path
= entry
.path();
577 pxar
::EntryKind
::File { .. }
=> { /* Ok */ }
578 _
=> bail
!("wrong entry type for {:?}", entry_path
),
580 match entry_path
.parent() {
581 None
=> bail
!("wrong parent for {:?}", entry_path
),
584 bail
!("wrong parent for {:?}", entry_path
);
589 let filename
= entry
.file_name();
590 let mut contents
= match decoder
.contents() {
591 None
=> bail
!("missing file content"),
592 Some(contents
) => contents
,
595 let mut archive_path
= snapshot_path
.to_owned();
596 archive_path
.push(&filename
);
598 let mut tmp_path
= archive_path
.clone();
599 tmp_path
.set_extension("tmp");
601 if filename
== manifest_file_name
{
603 let blob
= DataBlob
::load_from_reader(&mut contents
)?
;
604 let options
= CreateOptions
::new();
605 replace_file(&tmp_path
, blob
.raw_data(), options
)?
;
607 manifest
= Some(BackupManifest
::try_from(blob
)?
);
609 let mut tmpfile
= std
::fs
::OpenOptions
::new()
614 .map_err(|err
| format_err
!("restore {:?} failed - {}", tmp_path
, err
))?
;
616 std
::io
::copy(&mut contents
, &mut tmpfile
)?
;
618 if let Err(err
) = std
::fs
::rename(&tmp_path
, &archive_path
) {
619 bail
!("Atomic rename file {:?} failed - {}", archive_path
, err
);
624 let manifest
= match manifest
{
625 None
=> bail
!("missing manifest"),
626 Some(manifest
) => manifest
,
629 for item
in manifest
.files() {
630 let mut archive_path
= snapshot_path
.to_owned();
631 archive_path
.push(&item
.filename
);
633 match archive_type(&item
.filename
)?
{
634 ArchiveType
::DynamicIndex
=> {
635 let index
= DynamicIndexReader
::open(&archive_path
)?
;
636 let (csum
, size
) = index
.compute_csum();
637 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
639 ArchiveType
::FixedIndex
=> {
640 let index
= FixedIndexReader
::open(&archive_path
)?
;
641 let (csum
, size
) = index
.compute_csum();
642 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
644 ArchiveType
::Blob
=> {
645 let mut tmpfile
= std
::fs
::File
::open(&archive_path
)?
;
646 let (csum
, size
) = compute_file_csum(&mut tmpfile
)?
;
647 manifest
.verify_file(&item
.filename
, &csum
, size
)?
;
653 let mut manifest_path
= snapshot_path
.to_owned();
654 manifest_path
.push(MANIFEST_BLOB_NAME
);
655 let mut tmp_manifest_path
= manifest_path
.clone();
656 tmp_manifest_path
.set_extension("tmp");
658 if let Err(err
) = std
::fs
::rename(&tmp_manifest_path
, &manifest_path
) {
659 bail
!("Atomic rename manifest {:?} failed - {}", manifest_path
, err
);