api: tape: rust format
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
938a1f137c
commit
085ae87380
@ -1,5 +1,5 @@
|
||||
use std::path::Path;
|
||||
use std::sync::{Mutex, Arc};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use serde_json::Value;
|
||||
@ -10,43 +10,29 @@ use proxmox_schema::api;
|
||||
use proxmox_sys::{task_log, task_warn, WorkerTaskContext};
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, Userid, TapeBackupJobConfig, TapeBackupJobSetup, TapeBackupJobStatus, MediaPoolConfig,
|
||||
UPID_SCHEMA, JOB_ID_SCHEMA, PRIV_DATASTORE_READ, PRIV_TAPE_AUDIT, PRIV_TAPE_WRITE,
|
||||
GroupFilter,
|
||||
Authid, GroupFilter, MediaPoolConfig, TapeBackupJobConfig, TapeBackupJobSetup,
|
||||
TapeBackupJobStatus, Userid, JOB_ID_SCHEMA, PRIV_DATASTORE_READ, PRIV_TAPE_AUDIT,
|
||||
PRIV_TAPE_WRITE, UPID_SCHEMA,
|
||||
};
|
||||
|
||||
use pbs_datastore::{DataStore, StoreProgress, SnapshotReader};
|
||||
use pbs_datastore::backup_info::{BackupDir, BackupInfo, BackupGroup};
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_datastore::backup_info::{BackupDir, BackupGroup, BackupInfo};
|
||||
use pbs_datastore::{DataStore, SnapshotReader, StoreProgress};
|
||||
use proxmox_rest_server::WorkerTask;
|
||||
|
||||
use crate::{
|
||||
server::{
|
||||
lookup_user_email,
|
||||
TapeBackupJobSummary,
|
||||
jobstate::{
|
||||
Job,
|
||||
JobState,
|
||||
compute_schedule_status,
|
||||
},
|
||||
jobstate::{compute_schedule_status, Job, JobState},
|
||||
lookup_user_email, TapeBackupJobSummary,
|
||||
},
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
PoolWriter,
|
||||
MediaPool,
|
||||
drive::{
|
||||
media_changer,
|
||||
lock_tape_device,
|
||||
TapeLockError,
|
||||
set_tape_device_state,
|
||||
},
|
||||
changer::update_changer_online_status,
|
||||
drive::{lock_tape_device, media_changer, set_tape_device_state, TapeLockError},
|
||||
Inventory, MediaPool, PoolWriter, TAPE_STATUS_DIR,
|
||||
},
|
||||
};
|
||||
|
||||
const TAPE_BACKUP_JOB_ROUTER: Router = Router::new()
|
||||
.post(&API_METHOD_RUN_TAPE_BACKUP_JOB);
|
||||
const TAPE_BACKUP_JOB_ROUTER: Router = Router::new().post(&API_METHOD_RUN_TAPE_BACKUP_JOB);
|
||||
|
||||
pub const ROUTER: Router = Router::new()
|
||||
.get(&API_METHOD_LIST_TAPE_BACKUP_JOBS)
|
||||
@ -59,7 +45,6 @@ fn check_backup_permission(
|
||||
pool: &str,
|
||||
drive: &str,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let user_info = CachedUserInfo::new()?;
|
||||
|
||||
let privs = user_info.lookup_privs(auth_id, &["datastore", store]);
|
||||
@ -144,7 +129,11 @@ pub fn list_tape_backup_jobs(
|
||||
}
|
||||
}
|
||||
|
||||
list.push(TapeBackupJobStatus { config: job, status, next_media_label });
|
||||
list.push(TapeBackupJobStatus {
|
||||
config: job,
|
||||
status,
|
||||
next_media_label,
|
||||
});
|
||||
}
|
||||
|
||||
rpcenv["digest"] = hex::encode(&digest).into();
|
||||
@ -159,12 +148,13 @@ pub fn do_tape_backup_job(
|
||||
schedule: Option<String>,
|
||||
to_stdout: bool,
|
||||
) -> Result<String, Error> {
|
||||
|
||||
let job_id = format!("{}:{}:{}:{}",
|
||||
setup.store,
|
||||
setup.pool,
|
||||
setup.drive,
|
||||
job.jobname());
|
||||
let job_id = format!(
|
||||
"{}:{}:{}:{}",
|
||||
setup.store,
|
||||
setup.pool,
|
||||
setup.drive,
|
||||
job.jobname()
|
||||
);
|
||||
|
||||
let worker_type = job.jobtype().to_string();
|
||||
|
||||
@ -182,7 +172,10 @@ pub fn do_tape_backup_job(
|
||||
Some(lock_tape_device(&drive_config, &setup.drive)?)
|
||||
};
|
||||
|
||||
let notify_user = setup.notify_user.as_ref().unwrap_or_else(|| Userid::root_userid());
|
||||
let notify_user = setup
|
||||
.notify_user
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| Userid::root_userid());
|
||||
let email = lookup_user_email(notify_user);
|
||||
|
||||
let upid_str = WorkerTask::new_thread(
|
||||
@ -213,12 +206,11 @@ pub fn do_tape_backup_job(
|
||||
}
|
||||
set_tape_device_state(&setup.drive, &worker.upid().to_string())?;
|
||||
|
||||
task_log!(worker,"Starting tape backup job '{}'", job_id);
|
||||
task_log!(worker, "Starting tape backup job '{}'", job_id);
|
||||
if let Some(event_str) = schedule {
|
||||
task_log!(worker,"task triggered by schedule '{}'", event_str);
|
||||
task_log!(worker, "task triggered by schedule '{}'", event_str);
|
||||
}
|
||||
|
||||
|
||||
backup_worker(
|
||||
&worker,
|
||||
datastore,
|
||||
@ -253,15 +245,11 @@ pub fn do_tape_backup_job(
|
||||
}
|
||||
|
||||
if let Err(err) = set_tape_device_state(&setup.drive, "") {
|
||||
eprintln!(
|
||||
"could not unset drive state for {}: {}",
|
||||
setup.drive,
|
||||
err
|
||||
);
|
||||
eprintln!("could not unset drive state for {}: {}", setup.drive, err);
|
||||
}
|
||||
|
||||
job_result
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(upid_str)
|
||||
@ -283,10 +271,7 @@ pub fn do_tape_backup_job(
|
||||
},
|
||||
)]
|
||||
/// Runs a tape backup job manually.
|
||||
pub fn run_tape_backup_job(
|
||||
id: String,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<String, Error> {
|
||||
pub fn run_tape_backup_job(id: String, rpcenv: &mut dyn RpcEnvironment) -> Result<String, Error> {
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
let (config, _digest) = pbs_config::tape_job::config()?;
|
||||
@ -339,15 +324,9 @@ pub fn backup(
|
||||
force_media_set: bool,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
|
||||
let auth_id: Authid = rpcenv.get_auth_id().unwrap().parse()?;
|
||||
|
||||
check_backup_permission(
|
||||
&auth_id,
|
||||
&setup.store,
|
||||
&setup.pool,
|
||||
&setup.drive,
|
||||
)?;
|
||||
check_backup_permission(&auth_id, &setup.store, &setup.pool, &setup.drive)?;
|
||||
|
||||
let datastore = DataStore::lookup_datastore(&setup.store)?;
|
||||
|
||||
@ -363,7 +342,10 @@ pub fn backup(
|
||||
|
||||
let job_id = format!("{}:{}:{}", setup.store, setup.pool, setup.drive);
|
||||
|
||||
let notify_user = setup.notify_user.as_ref().unwrap_or_else(|| Userid::root_userid());
|
||||
let notify_user = setup
|
||||
.notify_user
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| Userid::root_userid());
|
||||
let email = lookup_user_email(notify_user);
|
||||
|
||||
let upid_str = WorkerTask::new_thread(
|
||||
@ -401,7 +383,7 @@ pub fn backup(
|
||||
// ignore errors
|
||||
let _ = set_tape_device_state(&setup.drive, "");
|
||||
job_result
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(upid_str.into())
|
||||
@ -416,7 +398,6 @@ fn backup_worker(
|
||||
summary: &mut TapeBackupJobSummary,
|
||||
force_media_set: bool,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
@ -425,13 +406,7 @@ fn backup_worker(
|
||||
|
||||
let pool = MediaPool::with_config(status_path, pool_config, changer_name, false)?;
|
||||
|
||||
let mut pool_writer = PoolWriter::new(
|
||||
pool,
|
||||
&setup.drive,
|
||||
worker,
|
||||
email,
|
||||
force_media_set
|
||||
)?;
|
||||
let mut pool_writer = PoolWriter::new(pool, &setup.drive, worker, email, force_media_set)?;
|
||||
|
||||
let mut group_list = BackupInfo::list_backup_groups(&datastore.base_path())?;
|
||||
|
||||
@ -443,9 +418,17 @@ fn backup_worker(
|
||||
};
|
||||
|
||||
let group_count_full = group_list.len();
|
||||
let list: Vec<BackupGroup> = group_list.into_iter().filter(|group| filter_fn(group, group_filters)).collect();
|
||||
let list: Vec<BackupGroup> = group_list
|
||||
.into_iter()
|
||||
.filter(|group| filter_fn(group, group_filters))
|
||||
.collect();
|
||||
let group_count = list.len();
|
||||
task_log!(worker, "found {} groups (out of {} total)", group_count, group_count_full);
|
||||
task_log!(
|
||||
worker,
|
||||
"found {} groups (out of {} total)",
|
||||
group_count,
|
||||
group_count_full
|
||||
);
|
||||
(list, group_count)
|
||||
} else {
|
||||
let group_count = group_list.len();
|
||||
@ -458,7 +441,10 @@ fn backup_worker(
|
||||
let latest_only = setup.latest_only.unwrap_or(false);
|
||||
|
||||
if latest_only {
|
||||
task_log!(worker, "latest-only: true (only considering latest snapshots)");
|
||||
task_log!(
|
||||
worker,
|
||||
"latest-only: true (only considering latest snapshots)"
|
||||
);
|
||||
}
|
||||
|
||||
let datastore_name = datastore.name();
|
||||
@ -504,11 +490,7 @@ fn backup_worker(
|
||||
summary.snapshot_list.push(snapshot_name);
|
||||
}
|
||||
progress.done_snapshots = 1;
|
||||
task_log!(
|
||||
worker,
|
||||
"percentage done: {}",
|
||||
progress
|
||||
);
|
||||
task_log!(worker, "percentage done: {}", progress);
|
||||
}
|
||||
} else {
|
||||
progress.group_snapshots = snapshot_list.len() as u64;
|
||||
@ -527,11 +509,7 @@ fn backup_worker(
|
||||
summary.snapshot_list.push(snapshot_name);
|
||||
}
|
||||
progress.done_snapshots = snapshot_number as u64 + 1;
|
||||
task_log!(
|
||||
worker,
|
||||
"percentage done: {}",
|
||||
progress
|
||||
);
|
||||
task_log!(worker, "percentage done: {}", progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -544,7 +522,10 @@ fn backup_worker(
|
||||
let uuid = pool_writer.load_writable_media(worker)?;
|
||||
let done = pool_writer.append_catalog_archive(worker)?;
|
||||
if !done {
|
||||
task_log!(worker, "catalog does not fit on tape, writing to next volume");
|
||||
task_log!(
|
||||
worker,
|
||||
"catalog does not fit on tape, writing to next volume"
|
||||
);
|
||||
pool_writer.set_media_status_full(&uuid)?;
|
||||
pool_writer.load_writable_media(worker)?;
|
||||
let done = pool_writer.append_catalog_archive(worker)?;
|
||||
@ -571,22 +552,15 @@ fn backup_worker(
|
||||
|
||||
// Try to update the the media online status
|
||||
fn update_media_online_status(drive: &str) -> Result<Option<String>, Error> {
|
||||
|
||||
let (config, _digest) = pbs_config::drive::config()?;
|
||||
|
||||
if let Ok(Some((mut changer, changer_name))) = media_changer(&config, drive) {
|
||||
|
||||
let label_text_list = changer.online_media_label_texts()?;
|
||||
let label_text_list = changer.online_media_label_texts()?;
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
|
||||
update_changer_online_status(
|
||||
&config,
|
||||
&mut inventory,
|
||||
&changer_name,
|
||||
&label_text_list,
|
||||
)?;
|
||||
update_changer_online_status(&config, &mut inventory, &changer_name, &label_text_list)?;
|
||||
|
||||
Ok(Some(changer_name))
|
||||
} else {
|
||||
@ -600,7 +574,6 @@ pub fn backup_snapshot(
|
||||
datastore: Arc<DataStore>,
|
||||
snapshot: BackupDir,
|
||||
) -> Result<bool, Error> {
|
||||
|
||||
task_log!(worker, "backup snapshot {}", snapshot);
|
||||
|
||||
let snapshot_reader = match SnapshotReader::new(datastore.clone(), snapshot.clone()) {
|
||||
@ -614,10 +587,8 @@ pub fn backup_snapshot(
|
||||
|
||||
let snapshot_reader = Arc::new(Mutex::new(snapshot_reader));
|
||||
|
||||
let (reader_thread, chunk_iter) = pool_writer.spawn_chunk_reader_thread(
|
||||
datastore.clone(),
|
||||
snapshot_reader.clone(),
|
||||
)?;
|
||||
let (reader_thread, chunk_iter) =
|
||||
pool_writer.spawn_chunk_reader_thread(datastore.clone(), snapshot_reader.clone())?;
|
||||
|
||||
let mut chunk_iter = chunk_iter.peekable();
|
||||
|
||||
@ -627,7 +598,7 @@ pub fn backup_snapshot(
|
||||
// test is we have remaining chunks
|
||||
match chunk_iter.peek() {
|
||||
None => break,
|
||||
Some(Ok(_)) => { /* Ok */ },
|
||||
Some(Ok(_)) => { /* Ok */ }
|
||||
Some(Err(err)) => bail!("{}", err),
|
||||
}
|
||||
|
||||
@ -635,7 +606,8 @@ pub fn backup_snapshot(
|
||||
|
||||
worker.check_abort()?;
|
||||
|
||||
let (leom, _bytes) = pool_writer.append_chunk_archive(worker, &mut chunk_iter, datastore.name())?;
|
||||
let (leom, _bytes) =
|
||||
pool_writer.append_chunk_archive(worker, &mut chunk_iter, datastore.name())?;
|
||||
|
||||
if leom {
|
||||
pool_writer.set_media_status_full(&uuid)?;
|
||||
|
@ -4,8 +4,8 @@ use std::path::Path;
|
||||
use anyhow::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox_schema::api;
|
||||
use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
|
||||
use proxmox_schema::api;
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, ChangerListEntry, LtoTapeDrive, MtxEntryKind, MtxStatusEntry, ScsiTapeChanger,
|
||||
@ -13,24 +13,16 @@ use pbs_api_types::{
|
||||
};
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_tape::{
|
||||
linux_list_drives::{linux_tape_changer_list, lookup_device_identification},
|
||||
ElementStatus,
|
||||
linux_list_drives::{lookup_device_identification, linux_tape_changer_list},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
changer::{
|
||||
OnlineStatusMap,
|
||||
ScsiMediaChange,
|
||||
mtx_status_to_online_set,
|
||||
},
|
||||
drive::get_tape_device_state,
|
||||
},
|
||||
use crate::tape::{
|
||||
changer::{mtx_status_to_online_set, OnlineStatusMap, ScsiMediaChange},
|
||||
drive::get_tape_device_state,
|
||||
Inventory, TAPE_STATUS_DIR,
|
||||
};
|
||||
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
@ -56,18 +48,12 @@ use crate::{
|
||||
},
|
||||
)]
|
||||
/// Get tape changer status
|
||||
pub async fn get_status(
|
||||
name: String,
|
||||
cache: bool,
|
||||
) -> Result<Vec<MtxStatusEntry>, Error> {
|
||||
|
||||
pub async fn get_status(name: String, cache: bool) -> Result<Vec<MtxStatusEntry>, Error> {
|
||||
let (config, _digest) = pbs_config::drive::config()?;
|
||||
|
||||
let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?;
|
||||
|
||||
let status = tokio::task::spawn_blocking(move || {
|
||||
changer_config.status(cache)
|
||||
}).await??;
|
||||
let status = tokio::task::spawn_blocking(move || changer_config.status(cache)).await??;
|
||||
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
@ -155,12 +141,7 @@ pub async fn get_status(
|
||||
},
|
||||
)]
|
||||
/// Transfers media from one slot to another
|
||||
pub async fn transfer(
|
||||
name: String,
|
||||
from: u64,
|
||||
to: u64,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
pub async fn transfer(name: String, from: u64, to: u64) -> Result<(), Error> {
|
||||
let (config, _digest) = pbs_config::drive::config()?;
|
||||
|
||||
let mut changer_config: ScsiTapeChanger = config.lookup("changer", &name)?;
|
||||
@ -168,7 +149,8 @@ pub async fn transfer(
|
||||
tokio::task::spawn_blocking(move || {
|
||||
changer_config.transfer(from, to)?;
|
||||
Ok(())
|
||||
}).await?
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
#[api(
|
||||
@ -210,23 +192,18 @@ pub fn list_changers(
|
||||
}
|
||||
|
||||
let info = lookup_device_identification(&linux_changers, &changer.path);
|
||||
let entry = ChangerListEntry { config: changer, info };
|
||||
let entry = ChangerListEntry {
|
||||
config: changer,
|
||||
info,
|
||||
};
|
||||
list.push(entry);
|
||||
}
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
const SUBDIRS: SubdirMap = &[
|
||||
(
|
||||
"status",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_GET_STATUS)
|
||||
),
|
||||
(
|
||||
"transfer",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_TRANSFER)
|
||||
),
|
||||
("status", &Router::new().get(&API_METHOD_GET_STATUS)),
|
||||
("transfer", &Router::new().post(&API_METHOD_TRANSFER)),
|
||||
];
|
||||
|
||||
const ITEM_ROUTER: Router = Router::new()
|
||||
|
@ -1,67 +1,50 @@
|
||||
use std::collections::HashMap;
|
||||
use std::panic::UnwindSafe;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox_sys::sortable;
|
||||
use proxmox_router::{
|
||||
list_subdirs_api_method, Permission, Router, RpcEnvironment, RpcEnvironmentType, SubdirMap,
|
||||
};
|
||||
use proxmox_schema::api;
|
||||
use proxmox_section_config::SectionConfigData;
|
||||
use proxmox_uuid::Uuid;
|
||||
use proxmox_sys::sortable;
|
||||
use proxmox_sys::{task_log, task_warn};
|
||||
use proxmox_uuid::Uuid;
|
||||
|
||||
use pbs_api_types::{
|
||||
UPID_SCHEMA, CHANGER_NAME_SCHEMA, DRIVE_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA,
|
||||
Authid, DriveListEntry, LtoTapeDrive, MediaIdFlat, LabelUuidMap, MamAttribute,
|
||||
LtoDriveAndMediaStatus, Lp17VolumeStatistics,
|
||||
Authid, DriveListEntry, LabelUuidMap, Lp17VolumeStatistics, LtoDriveAndMediaStatus,
|
||||
LtoTapeDrive, MamAttribute, MediaIdFlat, CHANGER_NAME_SCHEMA, DRIVE_NAME_SCHEMA,
|
||||
MEDIA_LABEL_SCHEMA, MEDIA_POOL_NAME_SCHEMA, UPID_SCHEMA,
|
||||
};
|
||||
|
||||
|
||||
use pbs_api_types::{PRIV_TAPE_AUDIT, PRIV_TAPE_READ, PRIV_TAPE_WRITE};
|
||||
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_config::key_config::KeyConfig;
|
||||
use pbs_config::tape_encryption_keys::insert_key;
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_tape::{
|
||||
BlockReadError,
|
||||
linux_list_drives::{lookup_device_identification, lto_tape_device_list, open_lto_tape_device},
|
||||
sg_tape::tape_alert_flags_critical,
|
||||
linux_list_drives::{lto_tape_device_list, lookup_device_identification, open_lto_tape_device},
|
||||
BlockReadError,
|
||||
};
|
||||
use proxmox_rest_server::WorkerTask;
|
||||
|
||||
use crate::{
|
||||
api2::tape::restore::{
|
||||
fast_catalog_restore,
|
||||
restore_media,
|
||||
},
|
||||
api2::tape::restore::{fast_catalog_restore, restore_media},
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
MediaCatalog,
|
||||
MediaId,
|
||||
lock_media_set,
|
||||
lock_media_pool,
|
||||
lock_unassigned_media_pool,
|
||||
file_formats::{
|
||||
MediaLabel,
|
||||
MediaSetLabel,
|
||||
},
|
||||
drive::{
|
||||
TapeDriver,
|
||||
LtoTapeHandle,
|
||||
open_lto_tape_drive,
|
||||
media_changer,
|
||||
required_media_changer,
|
||||
open_drive,
|
||||
lock_tape_device,
|
||||
set_tape_device_state,
|
||||
get_tape_device_state,
|
||||
},
|
||||
changer::update_changer_online_status,
|
||||
drive::{
|
||||
get_tape_device_state, lock_tape_device, media_changer, open_drive,
|
||||
open_lto_tape_drive, required_media_changer, set_tape_device_state, LtoTapeHandle,
|
||||
TapeDriver,
|
||||
},
|
||||
file_formats::{MediaLabel, MediaSetLabel},
|
||||
lock_media_pool, lock_media_set, lock_unassigned_media_pool, Inventory, MediaCatalog,
|
||||
MediaId, TAPE_STATUS_DIR,
|
||||
},
|
||||
};
|
||||
|
||||
@ -151,7 +134,12 @@ pub fn load_media(
|
||||
"load-media",
|
||||
Some(job_id),
|
||||
move |worker, config| {
|
||||
task_log!(worker, "loading media '{}' into drive '{}'", label_text, drive);
|
||||
task_log!(
|
||||
worker,
|
||||
"loading media '{}' into drive '{}'",
|
||||
label_text,
|
||||
drive
|
||||
);
|
||||
let (mut changer, _) = required_media_changer(&config, &drive)?;
|
||||
changer.load_media(&label_text)?;
|
||||
Ok(())
|
||||
@ -228,7 +216,7 @@ pub async fn export_media(drive: String, label_text: String) -> Result<u64, Erro
|
||||
changer_name
|
||||
),
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -354,13 +342,17 @@ pub fn format_media(
|
||||
task_log!(
|
||||
worker,
|
||||
"found media '{}' with uuid '{}'",
|
||||
media_id.label.label_text, media_id.label.uuid,
|
||||
media_id.label.label_text,
|
||||
media_id.label.uuid,
|
||||
);
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::new(status_path);
|
||||
|
||||
if let Some(MediaSetLabel { ref pool, ref uuid, ..}) = media_id.media_set_label {
|
||||
if let Some(MediaSetLabel {
|
||||
ref pool, ref uuid, ..
|
||||
}) = media_id.media_set_label
|
||||
{
|
||||
let _pool_lock = lock_media_pool(status_path, pool)?;
|
||||
let _media_set_lock = lock_media_set(status_path, uuid, None)?;
|
||||
MediaCatalog::destroy(status_path, &media_id.label.uuid)?;
|
||||
@ -398,10 +390,7 @@ pub fn format_media(
|
||||
},
|
||||
)]
|
||||
/// Rewind tape
|
||||
pub fn rewind(
|
||||
drive: String,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
pub fn rewind(drive: String, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
|
||||
let upid_str = run_drive_worker(
|
||||
rpcenv,
|
||||
drive.clone(),
|
||||
@ -433,10 +422,7 @@ pub fn rewind(
|
||||
},
|
||||
)]
|
||||
/// Eject/Unload drive media
|
||||
pub fn eject_media(
|
||||
drive: String,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
pub fn eject_media(drive: String, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
|
||||
let upid_str = run_drive_worker(
|
||||
rpcenv,
|
||||
drive.clone(),
|
||||
@ -509,8 +495,8 @@ pub fn label_media(
|
||||
|
||||
match drive.read_next_file() {
|
||||
Ok(_reader) => bail!("media is not empty (format it first)"),
|
||||
Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
|
||||
Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
|
||||
Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ }
|
||||
Err(BlockReadError::EndOfStream) => { /* tape is empty */ }
|
||||
Err(err) => {
|
||||
bail!("media read error - {}", err);
|
||||
}
|
||||
@ -536,19 +522,26 @@ fn write_media_label(
|
||||
label: MediaLabel,
|
||||
pool: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
drive.label_tape(&label)?;
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let media_id = if let Some(ref pool) = pool {
|
||||
// assign media to pool by writing special media set label
|
||||
task_log!(worker, "Label media '{}' for pool '{}'", label.label_text, pool);
|
||||
task_log!(
|
||||
worker,
|
||||
"Label media '{}' for pool '{}'",
|
||||
label.label_text,
|
||||
pool
|
||||
);
|
||||
let set = MediaSetLabel::with_data(pool, [0u8; 16].into(), 0, label.ctime, None);
|
||||
|
||||
drive.write_media_set_label(&set, None)?;
|
||||
|
||||
let media_id = MediaId { label, media_set_label: Some(set) };
|
||||
let media_id = MediaId {
|
||||
label,
|
||||
media_set_label: Some(set),
|
||||
};
|
||||
|
||||
// Create the media catalog
|
||||
MediaCatalog::overwrite(status_path, &media_id, false)?;
|
||||
@ -558,9 +551,16 @@ fn write_media_label(
|
||||
|
||||
media_id
|
||||
} else {
|
||||
task_log!(worker, "Label media '{}' (no pool assignment)", label.label_text);
|
||||
task_log!(
|
||||
worker,
|
||||
"Label media '{}' (no pool assignment)",
|
||||
label.label_text
|
||||
);
|
||||
|
||||
let media_id = MediaId { label, media_set_label: None };
|
||||
let media_id = MediaId {
|
||||
label,
|
||||
media_set_label: None,
|
||||
};
|
||||
|
||||
// Create the media catalog
|
||||
MediaCatalog::overwrite(status_path, &media_id, false)?;
|
||||
@ -593,7 +593,7 @@ fn write_media_label(
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok((None, _)) => bail!("verify label failed (got empty media)"),
|
||||
Err(err) => bail!("verify label failed - {}", err),
|
||||
};
|
||||
@ -668,7 +668,7 @@ pub async fn restore_key(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[api(
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
drive: {
|
||||
@ -688,75 +688,63 @@ pub async fn restore_key(
|
||||
},
|
||||
)]
|
||||
/// Read media label (optionally inventorize media)
|
||||
pub async fn read_label(
|
||||
drive: String,
|
||||
inventorize: Option<bool>,
|
||||
) -> Result<MediaIdFlat, Error> {
|
||||
run_drive_blocking_task(
|
||||
drive.clone(),
|
||||
"reading label".to_string(),
|
||||
move |config| {
|
||||
let mut drive = open_drive(&config, &drive)?;
|
||||
pub async fn read_label(drive: String, inventorize: Option<bool>) -> Result<MediaIdFlat, Error> {
|
||||
run_drive_blocking_task(drive.clone(), "reading label".to_string(), move |config| {
|
||||
let mut drive = open_drive(&config, &drive)?;
|
||||
|
||||
let (media_id, _key_config) = drive.read_label()?;
|
||||
let (media_id, _key_config) = drive.read_label()?;
|
||||
let media_id = media_id.ok_or(format_err!("Media is empty (no label)."))?;
|
||||
|
||||
let media_id = match media_id {
|
||||
Some(media_id) => {
|
||||
let mut flat = MediaIdFlat {
|
||||
uuid: media_id.label.uuid.clone(),
|
||||
label_text: media_id.label.label_text.clone(),
|
||||
ctime: media_id.label.ctime,
|
||||
media_set_ctime: None,
|
||||
media_set_uuid: None,
|
||||
encryption_key_fingerprint: None,
|
||||
pool: None,
|
||||
seq_nr: None,
|
||||
};
|
||||
if let Some(ref set) = media_id.media_set_label {
|
||||
flat.pool = Some(set.pool.clone());
|
||||
flat.seq_nr = Some(set.seq_nr);
|
||||
flat.media_set_uuid = Some(set.uuid.clone());
|
||||
flat.media_set_ctime = Some(set.ctime);
|
||||
flat.encryption_key_fingerprint = set
|
||||
.encryption_key_fingerprint
|
||||
.as_ref()
|
||||
.map(|fp| fp.signature());
|
||||
let label = if let Some(ref set) = media_id.media_set_label {
|
||||
let key = &set.encryption_key_fingerprint;
|
||||
|
||||
let encrypt_fingerprint = set.encryption_key_fingerprint.clone()
|
||||
.map(|fp| (fp, set.uuid.clone()));
|
||||
if let Err(err) = drive.set_encryption(key.clone().map(|fp| (fp, set.uuid.clone()))) {
|
||||
eprintln!("unable to load encryption key: {}", err); // best-effort only
|
||||
}
|
||||
MediaIdFlat {
|
||||
ctime: media_id.label.ctime,
|
||||
encryption_key_fingerprint: key.as_ref().map(|fp| fp.signature()),
|
||||
label_text: media_id.label.label_text.clone(),
|
||||
media_set_ctime: Some(set.ctime),
|
||||
media_set_uuid: Some(set.uuid.clone()),
|
||||
pool: Some(set.pool.clone()),
|
||||
seq_nr: Some(set.seq_nr),
|
||||
uuid: media_id.label.uuid.clone(),
|
||||
}
|
||||
} else {
|
||||
MediaIdFlat {
|
||||
ctime: media_id.label.ctime,
|
||||
encryption_key_fingerprint: None,
|
||||
label_text: media_id.label.label_text.clone(),
|
||||
media_set_ctime: None,
|
||||
media_set_uuid: None,
|
||||
pool: None,
|
||||
seq_nr: None,
|
||||
uuid: media_id.label.uuid.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = drive.set_encryption(encrypt_fingerprint) {
|
||||
// try, but ignore errors. just log to stderr
|
||||
eprintln!("unable to load encryption key: {}", err);
|
||||
}
|
||||
}
|
||||
if let Some(true) = inventorize {
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::new(state_path);
|
||||
|
||||
if let Some(true) = inventorize {
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::new(state_path);
|
||||
|
||||
if let Some(MediaSetLabel { ref pool, ref uuid, ..}) = media_id.media_set_label {
|
||||
let _pool_lock = lock_media_pool(state_path, pool)?;
|
||||
let _lock = lock_media_set(state_path, uuid, None)?;
|
||||
MediaCatalog::destroy_unrelated_catalog(state_path, &media_id)?;
|
||||
inventory.store(media_id, false)?;
|
||||
} else {
|
||||
let _lock = lock_unassigned_media_pool(state_path)?;
|
||||
MediaCatalog::destroy(state_path, &media_id.label.uuid)?;
|
||||
inventory.store(media_id, false)?;
|
||||
};
|
||||
}
|
||||
|
||||
flat
|
||||
}
|
||||
None => {
|
||||
bail!("Media is empty (no label).");
|
||||
}
|
||||
if let Some(MediaSetLabel {
|
||||
ref pool, ref uuid, ..
|
||||
}) = media_id.media_set_label
|
||||
{
|
||||
let _pool_lock = lock_media_pool(state_path, pool)?;
|
||||
let _lock = lock_media_set(state_path, uuid, None)?;
|
||||
MediaCatalog::destroy_unrelated_catalog(state_path, &media_id)?;
|
||||
inventory.store(media_id, false)?;
|
||||
} else {
|
||||
let _lock = lock_unassigned_media_pool(state_path)?;
|
||||
MediaCatalog::destroy(state_path, &media_id.label.uuid)?;
|
||||
inventory.store(media_id, false)?;
|
||||
};
|
||||
|
||||
Ok(media_id)
|
||||
}
|
||||
)
|
||||
|
||||
Ok(label)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@ -776,10 +764,7 @@ pub async fn read_label(
|
||||
},
|
||||
)]
|
||||
/// Clean drive
|
||||
pub fn clean_drive(
|
||||
drive: String,
|
||||
rpcenv: &mut dyn RpcEnvironment,
|
||||
) -> Result<Value, Error> {
|
||||
pub fn clean_drive(drive: String, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> {
|
||||
let upid_str = run_drive_worker(
|
||||
rpcenv,
|
||||
drive.clone(),
|
||||
@ -792,27 +777,27 @@ pub fn clean_drive(
|
||||
|
||||
changer.clean_drive()?;
|
||||
|
||||
if let Ok(drive_config) = config.lookup::<LtoTapeDrive>("lto", &drive) {
|
||||
// Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
|
||||
let mut handle = LtoTapeHandle::new(open_lto_tape_device(&drive_config.path)?)?;
|
||||
if let Ok(drive_config) = config.lookup::<LtoTapeDrive>("lto", &drive) {
|
||||
// Note: clean_drive unloads the cleaning media, so we cannot use drive_config.open
|
||||
let mut handle = LtoTapeHandle::new(open_lto_tape_device(&drive_config.path)?)?;
|
||||
|
||||
// test for critical tape alert flags
|
||||
if let Ok(alert_flags) = handle.tape_alert_flags() {
|
||||
if !alert_flags.is_empty() {
|
||||
task_log!(worker, "TapeAlertFlags: {:?}", alert_flags);
|
||||
if tape_alert_flags_critical(alert_flags) {
|
||||
bail!("found critical tape alert flags: {:?}", alert_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
// test for critical tape alert flags
|
||||
if let Ok(alert_flags) = handle.tape_alert_flags() {
|
||||
if !alert_flags.is_empty() {
|
||||
task_log!(worker, "TapeAlertFlags: {:?}", alert_flags);
|
||||
if tape_alert_flags_critical(alert_flags) {
|
||||
bail!("found critical tape alert flags: {:?}", alert_flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test wearout (max. 50 mounts)
|
||||
if let Ok(volume_stats) = handle.volume_statistics() {
|
||||
task_log!(worker, "Volume mounts: {}", volume_stats.volume_mounts);
|
||||
let wearout = volume_stats.volume_mounts * 2; // (*100.0/50.0);
|
||||
task_log!(worker, "Cleaning tape wearout: {}%", wearout);
|
||||
}
|
||||
}
|
||||
// test wearout (max. 50 mounts)
|
||||
if let Ok(volume_stats) = handle.volume_statistics() {
|
||||
task_log!(worker, "Volume mounts: {}", volume_stats.volume_mounts);
|
||||
let wearout = volume_stats.volume_mounts * 2; // (*100.0/50.0);
|
||||
task_log!(worker, "Cleaning tape wearout: {}%", wearout);
|
||||
}
|
||||
}
|
||||
|
||||
task_log!(worker, "Drive cleaned successfully");
|
||||
|
||||
@ -849,48 +834,43 @@ pub fn clean_drive(
|
||||
/// This method queries the changer to get a list of media labels.
|
||||
///
|
||||
/// Note: This updates the media online status.
|
||||
pub async fn inventory(
|
||||
drive: String,
|
||||
) -> Result<Vec<LabelUuidMap>, Error> {
|
||||
run_drive_blocking_task(
|
||||
drive.clone(),
|
||||
"inventorize".to_string(),
|
||||
move |config| {
|
||||
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
||||
pub async fn inventory(drive: String) -> Result<Vec<LabelUuidMap>, Error> {
|
||||
run_drive_blocking_task(drive.clone(), "inventorize".to_string(), move |config| {
|
||||
let (mut changer, changer_name) = required_media_changer(&config, &drive)?;
|
||||
|
||||
let label_text_list = changer.online_media_label_texts()?;
|
||||
let label_text_list = changer.online_media_label_texts()?;
|
||||
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
let state_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
|
||||
update_changer_online_status(
|
||||
&config,
|
||||
&mut inventory,
|
||||
&changer_name,
|
||||
&label_text_list,
|
||||
)?;
|
||||
update_changer_online_status(&config, &mut inventory, &changer_name, &label_text_list)?;
|
||||
|
||||
let mut list = Vec::new();
|
||||
let mut list = Vec::new();
|
||||
|
||||
for label_text in label_text_list.iter() {
|
||||
if label_text.starts_with("CLN") {
|
||||
// skip cleaning unit
|
||||
continue;
|
||||
}
|
||||
|
||||
let label_text = label_text.to_string();
|
||||
|
||||
if let Some(media_id) = inventory.find_media_by_label_text(&label_text) {
|
||||
list.push(LabelUuidMap { label_text, uuid: Some(media_id.label.uuid.clone()) });
|
||||
} else {
|
||||
list.push(LabelUuidMap { label_text, uuid: None });
|
||||
}
|
||||
for label_text in label_text_list.iter() {
|
||||
if label_text.starts_with("CLN") {
|
||||
// skip cleaning unit
|
||||
continue;
|
||||
}
|
||||
|
||||
Ok(list)
|
||||
let label_text = label_text.to_string();
|
||||
|
||||
if let Some(media_id) = inventory.find_media_by_label_text(&label_text) {
|
||||
list.push(LabelUuidMap {
|
||||
label_text,
|
||||
uuid: Some(media_id.label.uuid.clone()),
|
||||
});
|
||||
} else {
|
||||
list.push(LabelUuidMap {
|
||||
label_text,
|
||||
uuid: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Ok(list)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@ -955,7 +935,9 @@ pub fn update_inventory(
|
||||
|
||||
let label_text = label_text.to_string();
|
||||
|
||||
if !read_all_labels.unwrap_or(false) && inventory.find_media_by_label_text(&label_text).is_some() {
|
||||
if !read_all_labels.unwrap_or(false)
|
||||
&& inventory.find_media_by_label_text(&label_text).is_some()
|
||||
{
|
||||
task_log!(worker, "media '{}' already inventoried", label_text);
|
||||
continue;
|
||||
}
|
||||
@ -968,19 +950,37 @@ pub fn update_inventory(
|
||||
let mut drive = open_drive(&config, &drive)?;
|
||||
match drive.read_label() {
|
||||
Err(err) => {
|
||||
task_warn!(worker, "unable to read label form media '{}' - {}", label_text, err);
|
||||
task_warn!(
|
||||
worker,
|
||||
"unable to read label form media '{}' - {}",
|
||||
label_text,
|
||||
err
|
||||
);
|
||||
}
|
||||
Ok((None, _)) => {
|
||||
task_log!(worker, "media '{}' is empty", label_text);
|
||||
}
|
||||
Ok((Some(media_id), _key_config)) => {
|
||||
if label_text != media_id.label.label_text {
|
||||
task_warn!(worker, "label text mismatch ({} != {})", label_text, media_id.label.label_text);
|
||||
task_warn!(
|
||||
worker,
|
||||
"label text mismatch ({} != {})",
|
||||
label_text,
|
||||
media_id.label.label_text
|
||||
);
|
||||
continue;
|
||||
}
|
||||
task_log!(worker, "inventorize media '{}' with uuid '{}'", label_text, media_id.label.uuid);
|
||||
task_log!(
|
||||
worker,
|
||||
"inventorize media '{}' with uuid '{}'",
|
||||
label_text,
|
||||
media_id.label.uuid
|
||||
);
|
||||
|
||||
if let Some(MediaSetLabel { ref pool, ref uuid, ..}) = media_id.media_set_label {
|
||||
if let Some(MediaSetLabel {
|
||||
ref pool, ref uuid, ..
|
||||
}) = media_id.media_set_label
|
||||
{
|
||||
let _pool_lock = lock_media_pool(state_path, pool)?;
|
||||
let _lock = lock_media_set(state_path, uuid, None)?;
|
||||
MediaCatalog::destroy_unrelated_catalog(state_path, &media_id)?;
|
||||
@ -1001,7 +1001,6 @@ pub fn update_inventory(
|
||||
Ok(upid_str.into())
|
||||
}
|
||||
|
||||
|
||||
#[api(
|
||||
input: {
|
||||
properties: {
|
||||
@ -1063,18 +1062,29 @@ fn barcode_label_media_worker(
|
||||
|
||||
let mut inventory = Inventory::load(state_path)?;
|
||||
|
||||
update_changer_online_status(drive_config, &mut inventory, &changer_name, &label_text_list)?;
|
||||
update_changer_online_status(
|
||||
drive_config,
|
||||
&mut inventory,
|
||||
&changer_name,
|
||||
&label_text_list,
|
||||
)?;
|
||||
|
||||
if label_text_list.is_empty() {
|
||||
bail!("changer device does not list any media labels");
|
||||
}
|
||||
|
||||
for label_text in label_text_list {
|
||||
if label_text.starts_with("CLN") { continue; }
|
||||
if label_text.starts_with("CLN") {
|
||||
continue;
|
||||
}
|
||||
|
||||
inventory.reload()?;
|
||||
if inventory.find_media_by_label_text(&label_text).is_some() {
|
||||
task_log!(worker, "media '{}' already inventoried (already labeled)", label_text);
|
||||
task_log!(
|
||||
worker,
|
||||
"media '{}' already inventoried (already labeled)",
|
||||
label_text
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1090,13 +1100,21 @@ fn barcode_label_media_worker(
|
||||
|
||||
match drive.read_next_file() {
|
||||
Ok(_reader) => {
|
||||
task_log!(worker, "media '{}' is not empty (format it first)", label_text);
|
||||
task_log!(
|
||||
worker,
|
||||
"media '{}' is not empty (format it first)",
|
||||
label_text
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ },
|
||||
Err(BlockReadError::EndOfStream) => { /* tape is empty */ },
|
||||
Err(BlockReadError::EndOfFile) => { /* EOF mark at BOT, assume tape is empty */ }
|
||||
Err(BlockReadError::EndOfStream) => { /* tape is empty */ }
|
||||
Err(_err) => {
|
||||
task_warn!(worker, "media '{}' read error (maybe not empty - format it first)", label_text);
|
||||
task_warn!(
|
||||
worker,
|
||||
"media '{}' read error (maybe not empty - format it first)",
|
||||
label_text
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -1143,7 +1161,7 @@ pub async fn cartridge_memory(drive: String) -> Result<Vec<MamAttribute>, Error>
|
||||
let mut handle = open_lto_tape_drive(&drive_config)?;
|
||||
|
||||
handle.cartridge_memory()
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -1173,7 +1191,7 @@ pub async fn volume_statistics(drive: String) -> Result<Lp17VolumeStatistics, Er
|
||||
let mut handle = open_lto_tape_drive(&drive_config)?;
|
||||
|
||||
handle.volume_statistics()
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -1207,7 +1225,7 @@ pub async fn status(drive: String) -> Result<LtoDriveAndMediaStatus, Error> {
|
||||
let mut handle = LtoTapeHandle::new(file)?;
|
||||
|
||||
handle.get_drive_and_media_status()
|
||||
}
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
@ -1279,7 +1297,7 @@ pub fn catalog_media(
|
||||
);
|
||||
}
|
||||
media_id
|
||||
},
|
||||
}
|
||||
(None, _) => bail!("media is empty (no media label found)"),
|
||||
};
|
||||
|
||||
@ -1296,14 +1314,17 @@ pub fn catalog_media(
|
||||
return Ok(());
|
||||
}
|
||||
Some(ref set) => {
|
||||
if set.uuid.as_ref() == [0u8;16] { // media is empty
|
||||
if set.uuid.as_ref() == [0u8; 16] {
|
||||
// media is empty
|
||||
task_log!(worker, "media is empty");
|
||||
let _lock = lock_unassigned_media_pool(status_path)?;
|
||||
MediaCatalog::destroy(status_path, &media_id.label.uuid)?;
|
||||
inventory.store(media_id.clone(), false)?;
|
||||
return Ok(());
|
||||
}
|
||||
let encrypt_fingerprint = set.encryption_key_fingerprint.clone()
|
||||
let encrypt_fingerprint = set
|
||||
.encryption_key_fingerprint
|
||||
.clone()
|
||||
.map(|fp| (fp, set.uuid.clone()));
|
||||
|
||||
drive.set_encryption(encrypt_fingerprint)?;
|
||||
@ -1327,7 +1348,7 @@ pub fn catalog_media(
|
||||
let media_set = inventory.compute_media_set_members(media_set_uuid)?;
|
||||
|
||||
if fast_catalog_restore(&worker, &mut drive, &media_set, &media_id.label.uuid)? {
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
task_log!(worker, "no catalog found");
|
||||
@ -1339,7 +1360,14 @@ pub fn catalog_media(
|
||||
drive.read_label()?; // skip over labels - we already read them above
|
||||
|
||||
let mut checked_chunks = HashMap::new();
|
||||
restore_media(worker, &mut drive, &media_id, None, &mut checked_chunks, verbose)?;
|
||||
restore_media(
|
||||
worker,
|
||||
&mut drive,
|
||||
&media_id,
|
||||
None,
|
||||
&mut checked_chunks,
|
||||
verbose,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
},
|
||||
@ -1398,7 +1426,11 @@ pub fn list_drives(
|
||||
|
||||
let info = lookup_device_identification(<o_drives, &drive.path);
|
||||
let state = get_tape_device_state(&config, &drive.name)?;
|
||||
let entry = DriveListEntry { config: drive, info, state };
|
||||
let entry = DriveListEntry {
|
||||
config: drive,
|
||||
info,
|
||||
state,
|
||||
};
|
||||
list.push(entry);
|
||||
}
|
||||
|
||||
@ -1409,90 +1441,38 @@ pub fn list_drives(
|
||||
pub const SUBDIRS: SubdirMap = &sorted!([
|
||||
(
|
||||
"barcode-label-media",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_BARCODE_LABEL_MEDIA)
|
||||
),
|
||||
(
|
||||
"catalog",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_CATALOG_MEDIA)
|
||||
),
|
||||
(
|
||||
"clean",
|
||||
&Router::new()
|
||||
.put(&API_METHOD_CLEAN_DRIVE)
|
||||
),
|
||||
(
|
||||
"eject-media",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_EJECT_MEDIA)
|
||||
&Router::new().post(&API_METHOD_BARCODE_LABEL_MEDIA)
|
||||
),
|
||||
("catalog", &Router::new().post(&API_METHOD_CATALOG_MEDIA)),
|
||||
("clean", &Router::new().put(&API_METHOD_CLEAN_DRIVE)),
|
||||
("eject-media", &Router::new().post(&API_METHOD_EJECT_MEDIA)),
|
||||
(
|
||||
"format-media",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_FORMAT_MEDIA)
|
||||
),
|
||||
(
|
||||
"export-media",
|
||||
&Router::new()
|
||||
.put(&API_METHOD_EXPORT_MEDIA)
|
||||
&Router::new().post(&API_METHOD_FORMAT_MEDIA)
|
||||
),
|
||||
("export-media", &Router::new().put(&API_METHOD_EXPORT_MEDIA)),
|
||||
(
|
||||
"inventory",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_INVENTORY)
|
||||
.put(&API_METHOD_UPDATE_INVENTORY)
|
||||
),
|
||||
(
|
||||
"label-media",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_LABEL_MEDIA)
|
||||
),
|
||||
(
|
||||
"load-media",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_LOAD_MEDIA)
|
||||
),
|
||||
(
|
||||
"load-slot",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_LOAD_SLOT)
|
||||
),
|
||||
("label-media", &Router::new().post(&API_METHOD_LABEL_MEDIA)),
|
||||
("load-media", &Router::new().post(&API_METHOD_LOAD_MEDIA)),
|
||||
("load-slot", &Router::new().post(&API_METHOD_LOAD_SLOT)),
|
||||
(
|
||||
"cartridge-memory",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_CARTRIDGE_MEMORY)
|
||||
&Router::new().get(&API_METHOD_CARTRIDGE_MEMORY)
|
||||
),
|
||||
(
|
||||
"volume-statistics",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_VOLUME_STATISTICS)
|
||||
),
|
||||
(
|
||||
"read-label",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_READ_LABEL)
|
||||
),
|
||||
(
|
||||
"restore-key",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_RESTORE_KEY)
|
||||
),
|
||||
(
|
||||
"rewind",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_REWIND)
|
||||
),
|
||||
(
|
||||
"status",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_STATUS)
|
||||
),
|
||||
(
|
||||
"unload",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_UNLOAD)
|
||||
&Router::new().get(&API_METHOD_VOLUME_STATISTICS)
|
||||
),
|
||||
("read-label", &Router::new().get(&API_METHOD_READ_LABEL)),
|
||||
("restore-key", &Router::new().post(&API_METHOD_RESTORE_KEY)),
|
||||
("rewind", &Router::new().post(&API_METHOD_REWIND)),
|
||||
("status", &Router::new().get(&API_METHOD_STATUS)),
|
||||
("unload", &Router::new().post(&API_METHOD_UNLOAD)),
|
||||
]);
|
||||
|
||||
const ITEM_ROUTER: Router = Router::new()
|
||||
|
@ -1,30 +1,23 @@
|
||||
use std::path::Path;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
|
||||
use proxmox_router::{list_subdirs_api_method, Router, SubdirMap, RpcEnvironment, Permission};
|
||||
use proxmox_router::{list_subdirs_api_method, Permission, Router, RpcEnvironment, SubdirMap};
|
||||
use proxmox_schema::api;
|
||||
use proxmox_uuid::Uuid;
|
||||
|
||||
use pbs_datastore::backup_info::BackupDir;
|
||||
use pbs_api_types::{
|
||||
MEDIA_POOL_NAME_SCHEMA, MEDIA_LABEL_SCHEMA, MEDIA_UUID_SCHEMA, CHANGER_NAME_SCHEMA,
|
||||
VAULT_NAME_SCHEMA, Authid, MediaPoolConfig, MediaListEntry, MediaSetListEntry,
|
||||
MediaStatus, MediaContentEntry, MediaContentListFilter,
|
||||
PRIV_TAPE_AUDIT,
|
||||
Authid, MediaContentEntry, MediaContentListFilter, MediaListEntry, MediaPoolConfig,
|
||||
MediaSetListEntry, MediaStatus, CHANGER_NAME_SCHEMA, MEDIA_LABEL_SCHEMA,
|
||||
MEDIA_POOL_NAME_SCHEMA, MEDIA_UUID_SCHEMA, PRIV_TAPE_AUDIT, VAULT_NAME_SCHEMA,
|
||||
};
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_datastore::backup_info::BackupDir;
|
||||
|
||||
use crate::{
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
Inventory,
|
||||
MediaPool,
|
||||
MediaCatalog,
|
||||
media_catalog_snapshot_list,
|
||||
changer::update_online_status,
|
||||
},
|
||||
use crate::tape::{
|
||||
changer::update_online_status, media_catalog_snapshot_list, Inventory, MediaCatalog, MediaPool,
|
||||
TAPE_STATUS_DIR,
|
||||
};
|
||||
|
||||
#[api(
|
||||
@ -61,7 +54,7 @@ pub async fn list_media_sets(
|
||||
};
|
||||
|
||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", pool_name]);
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -150,7 +143,8 @@ pub async fn list_media(
|
||||
}
|
||||
// test what catalog files we have
|
||||
MediaCatalog::media_with_catalogs(status_path)
|
||||
}).await??;
|
||||
})
|
||||
.await??;
|
||||
|
||||
let mut list = Vec::new();
|
||||
|
||||
@ -166,7 +160,7 @@ pub async fn list_media(
|
||||
}
|
||||
|
||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", pool_name]);
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -185,17 +179,14 @@ pub async fn list_media(
|
||||
for media in pool.list_media() {
|
||||
let expired = pool.media_is_expired(&media, current_time);
|
||||
|
||||
let media_set_uuid = media.media_set_label()
|
||||
.map(|set| set.uuid.clone());
|
||||
let media_set_uuid = media.media_set_label().map(|set| set.uuid.clone());
|
||||
|
||||
let seq_nr = media.media_set_label()
|
||||
.map(|set| set.seq_nr);
|
||||
let seq_nr = media.media_set_label().map(|set| set.seq_nr);
|
||||
|
||||
let media_set_name = media.media_set_label()
|
||||
.map(|set| {
|
||||
pool.generate_media_set_name(&set.uuid, config.template.clone())
|
||||
.unwrap_or_else(|_| set.uuid.to_string())
|
||||
});
|
||||
let media_set_name = media.media_set_label().map(|set| {
|
||||
pool.generate_media_set_name(&set.uuid, config.template.clone())
|
||||
.unwrap_or_else(|_| set.uuid.to_string())
|
||||
});
|
||||
|
||||
let catalog_ok = if media.media_set_label().is_none() {
|
||||
// Media is empty, we need no catalog
|
||||
@ -224,11 +215,9 @@ pub async fn list_media(
|
||||
let inventory = Inventory::load(status_path)?;
|
||||
|
||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool"]);
|
||||
if (privs & PRIV_TAPE_AUDIT) != 0 {
|
||||
if (privs & PRIV_TAPE_AUDIT) != 0 {
|
||||
if pool.is_none() {
|
||||
|
||||
for media_id in inventory.list_unassigned_media() {
|
||||
|
||||
let (mut status, location) = inventory.status_and_location(&media_id.label.uuid);
|
||||
|
||||
if status == MediaStatus::Unknown {
|
||||
@ -267,7 +256,7 @@ pub async fn list_media(
|
||||
}
|
||||
|
||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", &media_set_label.pool]);
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -289,10 +278,8 @@ pub async fn list_media(
|
||||
media_set_name: Some(media_set_name),
|
||||
seq_nr: Some(media_set_label.seq_nr),
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
@ -310,15 +297,12 @@ pub async fn list_media(
|
||||
},
|
||||
)]
|
||||
/// Change Tape location to vault (if given), or offline.
|
||||
pub fn move_tape(
|
||||
label_text: String,
|
||||
vault_name: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
pub fn move_tape(label_text: String, vault_name: Option<String>) -> Result<(), Error> {
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
|
||||
let uuid = inventory.find_media_by_label_text(&label_text)
|
||||
let uuid = inventory
|
||||
.find_media_by_label_text(&label_text)
|
||||
.ok_or_else(|| format_err!("no such media '{}'", label_text))?
|
||||
.label
|
||||
.uuid
|
||||
@ -348,21 +332,24 @@ pub fn move_tape(
|
||||
},
|
||||
)]
|
||||
/// Destroy media (completely remove from database)
|
||||
pub fn destroy_media(label_text: String, force: Option<bool>,) -> Result<(), Error> {
|
||||
|
||||
pub fn destroy_media(label_text: String, force: Option<bool>) -> Result<(), Error> {
|
||||
let force = force.unwrap_or(false);
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
|
||||
let media_id = inventory.find_media_by_label_text(&label_text)
|
||||
let media_id = inventory
|
||||
.find_media_by_label_text(&label_text)
|
||||
.ok_or_else(|| format_err!("no such media '{}'", label_text))?;
|
||||
|
||||
if !force {
|
||||
if let Some(ref set) = media_id.media_set_label {
|
||||
let is_empty = set.uuid.as_ref() == [0u8;16];
|
||||
let is_empty = set.uuid.as_ref() == [0u8; 16];
|
||||
if !is_empty {
|
||||
bail!("media '{}' contains data (please use 'force' flag to remove.", label_text);
|
||||
bail!(
|
||||
"media '{}' contains data (please use 'force' flag to remove.",
|
||||
label_text
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,24 +401,32 @@ pub fn list_content(
|
||||
let set = media_id.media_set_label.as_ref().unwrap();
|
||||
|
||||
if let Some(ref label_text) = filter.label_text {
|
||||
if &media_id.label.label_text != label_text { continue; }
|
||||
if &media_id.label.label_text != label_text {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref pool) = filter.pool {
|
||||
if &set.pool != pool { continue; }
|
||||
if &set.pool != pool {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let privs = user_info.lookup_privs(&auth_id, &["tape", "pool", &set.pool]);
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
if (privs & PRIV_TAPE_AUDIT) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ref media_uuid) = filter.media {
|
||||
if &media_id.label.uuid != media_uuid { continue; }
|
||||
if &media_id.label.uuid != media_uuid {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref media_set_uuid) = filter.media_set {
|
||||
if &set.uuid != media_set_uuid { continue; }
|
||||
if &set.uuid != media_set_uuid {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let template = match config.lookup::<MediaPoolConfig>("pool", &set.pool) {
|
||||
@ -447,10 +442,14 @@ pub fn list_content(
|
||||
let backup_dir: BackupDir = snapshot.parse()?;
|
||||
|
||||
if let Some(ref backup_type) = filter.backup_type {
|
||||
if backup_dir.group().backup_type() != backup_type { continue; }
|
||||
if backup_dir.group().backup_type() != backup_type {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(ref backup_id) = filter.backup_id {
|
||||
if backup_dir.group().backup_id() != backup_id { continue; }
|
||||
if backup_dir.group().backup_id() != backup_id {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list.push(MediaContentEntry {
|
||||
@ -482,7 +481,6 @@ pub fn list_content(
|
||||
)]
|
||||
/// Get current media status
|
||||
pub fn get_media_status(uuid: Uuid) -> Result<MediaStatus, Error> {
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let inventory = Inventory::load(status_path)?;
|
||||
|
||||
@ -509,7 +507,6 @@ pub fn get_media_status(uuid: Uuid) -> Result<MediaStatus, Error> {
|
||||
/// It is not allowed to set status to 'writable' or 'unknown' (those
|
||||
/// are internally managed states).
|
||||
pub fn update_media_status(uuid: Uuid, status: Option<MediaStatus>) -> Result<(), Error> {
|
||||
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut inventory = Inventory::load(status_path)?;
|
||||
|
||||
@ -524,14 +521,12 @@ pub fn update_media_status(uuid: Uuid, status: Option<MediaStatus>) -> Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const MEDIA_SUBDIRS: SubdirMap = &[
|
||||
(
|
||||
"status",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_GET_MEDIA_STATUS)
|
||||
.post(&API_METHOD_UPDATE_MEDIA_STATUS)
|
||||
),
|
||||
];
|
||||
const MEDIA_SUBDIRS: SubdirMap = &[(
|
||||
"status",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_GET_MEDIA_STATUS)
|
||||
.post(&API_METHOD_UPDATE_MEDIA_STATUS),
|
||||
)];
|
||||
|
||||
pub const MEDIA_ROUTER: Router = Router::new()
|
||||
.get(&list_subdirs_api_method!(MEDIA_SUBDIRS))
|
||||
@ -542,30 +537,16 @@ pub const MEDIA_LIST_ROUTER: Router = Router::new()
|
||||
.match_all("uuid", &MEDIA_ROUTER);
|
||||
|
||||
const SUBDIRS: SubdirMap = &[
|
||||
(
|
||||
"content",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_LIST_CONTENT)
|
||||
),
|
||||
(
|
||||
"destroy",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_DESTROY_MEDIA)
|
||||
),
|
||||
( "list", &MEDIA_LIST_ROUTER ),
|
||||
("content", &Router::new().get(&API_METHOD_LIST_CONTENT)),
|
||||
("destroy", &Router::new().get(&API_METHOD_DESTROY_MEDIA)),
|
||||
("list", &MEDIA_LIST_ROUTER),
|
||||
(
|
||||
"media-sets",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_LIST_MEDIA_SETS)
|
||||
),
|
||||
(
|
||||
"move",
|
||||
&Router::new()
|
||||
.post(&API_METHOD_MOVE_TAPE)
|
||||
&Router::new().get(&API_METHOD_LIST_MEDIA_SETS),
|
||||
),
|
||||
("move", &Router::new().post(&API_METHOD_MOVE_TAPE)),
|
||||
];
|
||||
|
||||
|
||||
pub const ROUTER: Router = Router::new()
|
||||
.get(&list_subdirs_api_method!(SUBDIRS))
|
||||
.subdirs(SUBDIRS);
|
||||
|
@ -3,16 +3,16 @@
|
||||
use anyhow::Error;
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox_schema::api;
|
||||
use proxmox_router::{list_subdirs_api_method, Router, SubdirMap};
|
||||
use proxmox_schema::api;
|
||||
|
||||
use pbs_api_types::TapeDeviceInfo;
|
||||
use pbs_tape::linux_list_drives::{lto_tape_device_list, linux_tape_changer_list};
|
||||
use pbs_tape::linux_list_drives::{linux_tape_changer_list, lto_tape_device_list};
|
||||
|
||||
pub mod drive;
|
||||
pub mod changer;
|
||||
pub mod media;
|
||||
pub mod backup;
|
||||
pub mod changer;
|
||||
pub mod drive;
|
||||
pub mod media;
|
||||
pub mod restore;
|
||||
|
||||
#[api(
|
||||
@ -29,7 +29,6 @@ pub mod restore;
|
||||
)]
|
||||
/// Scan tape drives
|
||||
pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
|
||||
|
||||
let list = lto_tape_device_list();
|
||||
|
||||
Ok(list)
|
||||
@ -49,7 +48,6 @@ pub fn scan_drives(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
|
||||
)]
|
||||
/// Scan for SCSI tape changers
|
||||
pub fn scan_changers(_param: Value) -> Result<Vec<TapeDeviceInfo>, Error> {
|
||||
|
||||
let list = linux_tape_changer_list();
|
||||
|
||||
Ok(list)
|
||||
@ -63,14 +61,9 @@ const SUBDIRS: SubdirMap = &[
|
||||
("restore", &restore::ROUTER),
|
||||
(
|
||||
"scan-changers",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_SCAN_CHANGERS),
|
||||
),
|
||||
(
|
||||
"scan-drives",
|
||||
&Router::new()
|
||||
.get(&API_METHOD_SCAN_DRIVES),
|
||||
&Router::new().get(&API_METHOD_SCAN_CHANGERS),
|
||||
),
|
||||
("scan-drives", &Router::new().get(&API_METHOD_SCAN_DRIVES)),
|
||||
];
|
||||
|
||||
pub const ROUTER: Router = Router::new()
|
||||
|
@ -1,71 +1,53 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::ffi::OsStr;
|
||||
use std::collections::{HashMap, HashSet, BTreeMap};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::{Seek, SeekFrom};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use serde_json::Value;
|
||||
|
||||
use proxmox_sys::fs::{replace_file, CreateOptions};
|
||||
use proxmox_io::ReadExt;
|
||||
use proxmox_router::{Permission, Router, RpcEnvironment, RpcEnvironmentType};
|
||||
use proxmox_schema::api;
|
||||
use proxmox_section_config::SectionConfigData;
|
||||
use proxmox_uuid::Uuid;
|
||||
use proxmox_sys::fs::{replace_file, CreateOptions};
|
||||
use proxmox_sys::{task_log, task_warn, WorkerTaskContext};
|
||||
use proxmox_uuid::Uuid;
|
||||
|
||||
use pbs_api_types::{
|
||||
Authid, Userid, CryptMode,
|
||||
DATASTORE_MAP_ARRAY_SCHEMA, DATASTORE_MAP_LIST_SCHEMA, DRIVE_NAME_SCHEMA,
|
||||
UPID_SCHEMA, TAPE_RESTORE_SNAPSHOT_SCHEMA,
|
||||
PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_TAPE_READ,
|
||||
Authid, CryptMode, Userid, DATASTORE_MAP_ARRAY_SCHEMA, DATASTORE_MAP_LIST_SCHEMA,
|
||||
DRIVE_NAME_SCHEMA, PRIV_DATASTORE_BACKUP, PRIV_DATASTORE_MODIFY, PRIV_TAPE_READ,
|
||||
TAPE_RESTORE_SNAPSHOT_SCHEMA, UPID_SCHEMA,
|
||||
};
|
||||
use pbs_datastore::{DataStore, DataBlob};
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_datastore::backup_info::BackupDir;
|
||||
use pbs_datastore::dynamic_index::DynamicIndexReader;
|
||||
use pbs_datastore::fixed_index::FixedIndexReader;
|
||||
use pbs_datastore::index::IndexFile;
|
||||
use pbs_datastore::manifest::{archive_type, ArchiveType, BackupManifest, MANIFEST_BLOB_NAME};
|
||||
use pbs_config::CachedUserInfo;
|
||||
use pbs_datastore::{DataBlob, DataStore};
|
||||
use pbs_tape::{
|
||||
TapeRead, BlockReadError, MediaContentHeader,
|
||||
PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
|
||||
BlockReadError, MediaContentHeader, TapeRead, PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
|
||||
};
|
||||
use proxmox_rest_server::WorkerTask;
|
||||
|
||||
use crate::{
|
||||
tools::parallel_handler::ParallelHandler,
|
||||
server::lookup_user_email,
|
||||
tape::{
|
||||
TAPE_STATUS_DIR,
|
||||
MediaId,
|
||||
MediaSet,
|
||||
MediaCatalog,
|
||||
MediaSetCatalog,
|
||||
Inventory,
|
||||
lock_media_set,
|
||||
drive::{lock_tape_device, request_and_load_media, set_tape_device_state, TapeDriver},
|
||||
file_formats::{
|
||||
PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0,
|
||||
CatalogArchiveHeader, ChunkArchiveDecoder, ChunkArchiveHeader, SnapshotArchiveHeader,
|
||||
PROXMOX_BACKUP_CATALOG_ARCHIVE_MAGIC_1_0, PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1, PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_1,
|
||||
PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0,
|
||||
PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1,
|
||||
PROXMOX_BACKUP_CATALOG_ARCHIVE_MAGIC_1_0,
|
||||
ChunkArchiveHeader,
|
||||
ChunkArchiveDecoder,
|
||||
SnapshotArchiveHeader,
|
||||
CatalogArchiveHeader,
|
||||
},
|
||||
drive::{
|
||||
TapeDriver,
|
||||
request_and_load_media,
|
||||
lock_tape_device,
|
||||
set_tape_device_state,
|
||||
},
|
||||
lock_media_set, Inventory, MediaCatalog, MediaId, MediaSet, MediaSetCatalog,
|
||||
TAPE_STATUS_DIR,
|
||||
},
|
||||
tools::parallel_handler::ParallelHandler,
|
||||
};
|
||||
|
||||
const RESTORE_TMP_DIR: &str = "/var/tmp/proxmox-backup";
|
||||
@ -306,16 +288,11 @@ pub fn restore(
|
||||
}
|
||||
|
||||
if let Err(err) = set_tape_device_state(&drive, "") {
|
||||
task_log!(
|
||||
worker,
|
||||
"could not unset drive state for {}: {}",
|
||||
drive,
|
||||
err
|
||||
);
|
||||
task_log!(worker, "could not unset drive state for {}: {}", drive, err);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(upid_str.into())
|
||||
@ -342,12 +319,19 @@ fn restore_full_worker(
|
||||
for (seq_nr, media_uuid) in media_list.iter().enumerate() {
|
||||
match media_uuid {
|
||||
None => {
|
||||
bail!("media set {} is incomplete (missing member {}).", media_set_uuid, seq_nr);
|
||||
bail!(
|
||||
"media set {} is incomplete (missing member {}).",
|
||||
media_set_uuid,
|
||||
seq_nr
|
||||
);
|
||||
}
|
||||
Some(media_uuid) => {
|
||||
let media_id = inventory.lookup_media(media_uuid).unwrap();
|
||||
if let Some(ref set) = media_id.media_set_label { // always true here
|
||||
if encryption_key_fingerprint.is_none() && set.encryption_key_fingerprint.is_some() {
|
||||
if let Some(ref set) = media_id.media_set_label {
|
||||
// always true here
|
||||
if encryption_key_fingerprint.is_none()
|
||||
&& set.encryption_key_fingerprint.is_some()
|
||||
{
|
||||
encryption_key_fingerprint = set.encryption_key_fingerprint.clone();
|
||||
}
|
||||
}
|
||||
@ -364,21 +348,22 @@ fn restore_full_worker(
|
||||
worker,
|
||||
"Datastore(s): {}",
|
||||
store_map
|
||||
.used_datastores()
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
.used_datastores()
|
||||
.into_iter()
|
||||
.map(String::from)
|
||||
.collect::<Vec<String>>()
|
||||
.join(", "),
|
||||
);
|
||||
|
||||
task_log!(worker, "Drive: {}", drive_name);
|
||||
task_log!(
|
||||
worker,
|
||||
"Required media list: {}",
|
||||
media_id_list.iter()
|
||||
.map(|media_id| media_id.label.label_text.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(";")
|
||||
media_id_list
|
||||
.iter()
|
||||
.map(|media_id| media_id.label.label_text.as_str())
|
||||
.collect::<Vec<&str>>()
|
||||
.join(";")
|
||||
);
|
||||
|
||||
let mut datastore_locks = Vec::new();
|
||||
@ -529,11 +514,13 @@ fn restore_list_worker(
|
||||
&info,
|
||||
&media_set_uuid,
|
||||
&mut datastore_chunk_map,
|
||||
).map_err(|err| format_err!("could not restore snapshots to tmpdir: {}", err))?;
|
||||
)
|
||||
.map_err(|err| format_err!("could not restore snapshots to tmpdir: {}", err))?;
|
||||
}
|
||||
|
||||
// sorted media_uuid => (sorted file_num => (set of digests)))
|
||||
let mut media_file_chunk_map: BTreeMap<Uuid, BTreeMap<u64, HashSet<[u8; 32]>>> = BTreeMap::new();
|
||||
let mut media_file_chunk_map: BTreeMap<Uuid, BTreeMap<u64, HashSet<[u8; 32]>>> =
|
||||
BTreeMap::new();
|
||||
|
||||
for (source_datastore, chunks) in datastore_chunk_map.into_iter() {
|
||||
let datastore = store_map.get_datastore(&source_datastore).ok_or_else(|| {
|
||||
@ -546,7 +533,9 @@ fn restore_list_worker(
|
||||
// we only want to restore chunks that we do not have yet
|
||||
if !datastore.cond_touch_chunk(&digest, false)? {
|
||||
if let Some((uuid, nr)) = catalog.lookup_chunk(&source_datastore, &digest) {
|
||||
let file = media_file_chunk_map.entry(uuid.clone()).or_insert_with(BTreeMap::new);
|
||||
let file = media_file_chunk_map
|
||||
.entry(uuid.clone())
|
||||
.or_insert_with(BTreeMap::new);
|
||||
let chunks = file.entry(nr).or_insert_with(HashSet::new);
|
||||
chunks.insert(digest);
|
||||
}
|
||||
@ -590,9 +579,9 @@ fn restore_list_worker(
|
||||
.ok_or_else(|| format_err!("invalid snapshot:{}", store_snapshot))?;
|
||||
let backup_dir: BackupDir = snapshot.parse()?;
|
||||
|
||||
let datastore = store_map
|
||||
.get_datastore(source_datastore)
|
||||
.ok_or_else(|| format_err!("unexpected source datastore: {}", source_datastore))?;
|
||||
let datastore = store_map.get_datastore(source_datastore).ok_or_else(|| {
|
||||
format_err!("unexpected source datastore: {}", source_datastore)
|
||||
})?;
|
||||
|
||||
let mut tmp_path = base_path.clone();
|
||||
tmp_path.push(&source_datastore);
|
||||
@ -608,13 +597,17 @@ fn restore_list_worker(
|
||||
}
|
||||
task_log!(worker, "Restore snapshot '{}' done", snapshot);
|
||||
Ok(())
|
||||
}).map_err(|err: Error| format_err!("could not copy {}: {}", store_snapshot, err))?;
|
||||
})
|
||||
.map_err(|err: Error| format_err!("could not copy {}: {}", store_snapshot, err))?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if res.is_err() {
|
||||
task_warn!(worker, "Error during restore, partially restored snapshots will NOT be cleaned up");
|
||||
task_warn!(
|
||||
worker,
|
||||
"Error during restore, partially restored snapshots will NOT be cleaned up"
|
||||
);
|
||||
}
|
||||
|
||||
match std::fs::remove_dir_all(&base_path) {
|
||||
@ -693,7 +686,12 @@ fn restore_snapshots_to_tmpdir(
|
||||
for file_num in file_list {
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
if current_file_number != *file_num {
|
||||
task_log!(worker, "was at file {}, moving to {}", current_file_number, file_num);
|
||||
task_log!(
|
||||
worker,
|
||||
"was at file {}, moving to {}",
|
||||
current_file_number,
|
||||
file_num
|
||||
);
|
||||
drive.move_to_file(*file_num)?;
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
task_log!(worker, "now at file {}", current_file_number);
|
||||
@ -735,7 +733,8 @@ fn restore_snapshots_to_tmpdir(
|
||||
let chunks = chunks_list
|
||||
.entry(source_datastore)
|
||||
.or_insert_with(HashSet::new);
|
||||
let manifest = try_restore_snapshot_archive(worker.clone(), &mut decoder, &tmp_path)?;
|
||||
let manifest =
|
||||
try_restore_snapshot_archive(worker.clone(), &mut decoder, &tmp_path)?;
|
||||
for item in manifest.files() {
|
||||
let mut archive_path = tmp_path.to_owned();
|
||||
archive_path.push(&item.filename);
|
||||
@ -744,9 +743,7 @@ fn restore_snapshots_to_tmpdir(
|
||||
ArchiveType::DynamicIndex => {
|
||||
Box::new(DynamicIndexReader::open(&archive_path)?)
|
||||
}
|
||||
ArchiveType::FixedIndex => {
|
||||
Box::new(FixedIndexReader::open(&archive_path)?)
|
||||
}
|
||||
ArchiveType::FixedIndex => Box::new(FixedIndexReader::open(&archive_path)?),
|
||||
ArchiveType::Blob => continue,
|
||||
};
|
||||
for i in 0..index.index_count() {
|
||||
@ -772,7 +769,12 @@ fn restore_file_chunk_map(
|
||||
for (nr, chunk_map) in file_chunk_map.iter_mut() {
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
if current_file_number != *nr {
|
||||
task_log!(worker, "was at file {}, moving to {}", current_file_number, nr);
|
||||
task_log!(
|
||||
worker,
|
||||
"was at file {}, moving to {}",
|
||||
current_file_number,
|
||||
nr
|
||||
);
|
||||
drive.move_to_file(*nr)?;
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
task_log!(worker, "now at file {}", current_file_number);
|
||||
@ -803,7 +805,12 @@ fn restore_file_chunk_map(
|
||||
format_err!("unexpected chunk archive for store: {}", source_datastore)
|
||||
})?;
|
||||
|
||||
let count = restore_partial_chunk_archive(worker.clone(), reader, datastore.clone(), chunk_map)?;
|
||||
let count = restore_partial_chunk_archive(
|
||||
worker.clone(),
|
||||
reader,
|
||||
datastore.clone(),
|
||||
chunk_map,
|
||||
)?;
|
||||
task_log!(worker, "restored {} chunks", count);
|
||||
}
|
||||
_ => bail!("unexpected content magic {:?}", header.content_magic),
|
||||
@ -882,7 +889,6 @@ fn restore_partial_chunk_archive<'a>(
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
|
||||
/// Request and restore complete media without using existing catalog (create catalog instead)
|
||||
pub fn request_and_restore_media(
|
||||
worker: Arc<WorkerTask>,
|
||||
@ -890,7 +896,7 @@ pub fn request_and_restore_media(
|
||||
drive_config: &SectionConfigData,
|
||||
drive_name: &str,
|
||||
store_map: &DataStoreMap,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8;32]>>,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||
restore_owner: &Authid,
|
||||
email: &Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
@ -899,20 +905,29 @@ pub fn request_and_restore_media(
|
||||
Some(ref set) => &set.uuid,
|
||||
};
|
||||
|
||||
let (mut drive, info) = request_and_load_media(&worker, drive_config, drive_name, &media_id.label, email)?;
|
||||
let (mut drive, info) =
|
||||
request_and_load_media(&worker, drive_config, drive_name, &media_id.label, email)?;
|
||||
|
||||
match info.media_set_label {
|
||||
None => {
|
||||
bail!("missing media set label on media {} ({})",
|
||||
media_id.label.label_text, media_id.label.uuid);
|
||||
bail!(
|
||||
"missing media set label on media {} ({})",
|
||||
media_id.label.label_text,
|
||||
media_id.label.uuid
|
||||
);
|
||||
}
|
||||
Some(ref set) => {
|
||||
if &set.uuid != media_set_uuid {
|
||||
bail!("wrong media set label on media {} ({} != {})",
|
||||
media_id.label.label_text, media_id.label.uuid,
|
||||
media_set_uuid);
|
||||
bail!(
|
||||
"wrong media set label on media {} ({} != {})",
|
||||
media_id.label.label_text,
|
||||
media_id.label.uuid,
|
||||
media_set_uuid
|
||||
);
|
||||
}
|
||||
let encrypt_fingerprint = set.encryption_key_fingerprint.clone()
|
||||
let encrypt_fingerprint = set
|
||||
.encryption_key_fingerprint
|
||||
.clone()
|
||||
.map(|fp| (fp, set.uuid.clone()));
|
||||
|
||||
drive.set_encryption(encrypt_fingerprint)?;
|
||||
@ -937,10 +952,9 @@ pub fn restore_media(
|
||||
drive: &mut Box<dyn TapeDriver>,
|
||||
media_id: &MediaId,
|
||||
target: Option<(&DataStoreMap, &Authid)>,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8;32]>>,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||
verbose: bool,
|
||||
) -> Result<(), Error> {
|
||||
|
||||
) -> Result<(), Error> {
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
let mut catalog = MediaCatalog::create_temporary_database(status_path, media_id, false)?;
|
||||
|
||||
@ -948,7 +962,11 @@ pub fn restore_media(
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
let reader = match drive.read_next_file() {
|
||||
Err(BlockReadError::EndOfFile) => {
|
||||
task_log!(worker, "skip unexpected filemark at pos {}", current_file_number);
|
||||
task_log!(
|
||||
worker,
|
||||
"skip unexpected filemark at pos {}",
|
||||
current_file_number
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(BlockReadError::EndOfStream) => {
|
||||
@ -961,7 +979,15 @@ pub fn restore_media(
|
||||
Ok(reader) => reader,
|
||||
};
|
||||
|
||||
restore_archive(worker.clone(), reader, current_file_number, target, &mut catalog, checked_chunks_map, verbose)?;
|
||||
restore_archive(
|
||||
worker.clone(),
|
||||
reader,
|
||||
current_file_number,
|
||||
target,
|
||||
&mut catalog,
|
||||
checked_chunks_map,
|
||||
verbose,
|
||||
)?;
|
||||
}
|
||||
|
||||
catalog.commit()?;
|
||||
@ -977,7 +1003,7 @@ fn restore_archive<'a>(
|
||||
current_file_number: u64,
|
||||
target: Option<(&DataStoreMap, &Authid)>,
|
||||
catalog: &mut MediaCatalog,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8;32]>>,
|
||||
checked_chunks_map: &mut HashMap<String, HashSet<[u8; 32]>>,
|
||||
verbose: bool,
|
||||
) -> Result<(), Error> {
|
||||
let header: MediaContentHeader = unsafe { reader.read_le_value()? };
|
||||
@ -1003,7 +1029,13 @@ fn restore_archive<'a>(
|
||||
let datastore_name = archive_header.store;
|
||||
let snapshot = archive_header.snapshot;
|
||||
|
||||
task_log!(worker, "File {}: snapshot archive {}:{}", current_file_number, datastore_name, snapshot);
|
||||
task_log!(
|
||||
worker,
|
||||
"File {}: snapshot archive {}:{}",
|
||||
current_file_number,
|
||||
datastore_name,
|
||||
snapshot
|
||||
);
|
||||
|
||||
let backup_dir: BackupDir = snapshot.parse()?;
|
||||
|
||||
@ -1057,7 +1089,12 @@ fn restore_archive<'a>(
|
||||
|
||||
reader.skip_data()?; // read all data
|
||||
if let Ok(false) = reader.is_incomplete() {
|
||||
catalog.register_snapshot(Uuid::from(header.uuid), current_file_number, &datastore_name, &snapshot)?;
|
||||
catalog.register_snapshot(
|
||||
Uuid::from(header.uuid),
|
||||
current_file_number,
|
||||
&datastore_name,
|
||||
&snapshot,
|
||||
)?;
|
||||
catalog.commit_if_large()?;
|
||||
}
|
||||
}
|
||||
@ -1072,18 +1109,35 @@ fn restore_archive<'a>(
|
||||
|
||||
let source_datastore = archive_header.store;
|
||||
|
||||
task_log!(worker, "File {}: chunk archive for datastore '{}'", current_file_number, source_datastore);
|
||||
task_log!(
|
||||
worker,
|
||||
"File {}: chunk archive for datastore '{}'",
|
||||
current_file_number,
|
||||
source_datastore
|
||||
);
|
||||
let datastore = target
|
||||
.as_ref()
|
||||
.and_then(|t| t.0.get_datastore(&source_datastore));
|
||||
|
||||
if datastore.is_some() || target.is_none() {
|
||||
let checked_chunks = checked_chunks_map
|
||||
.entry(datastore.as_ref().map(|d| d.name()).unwrap_or("_unused_").to_string())
|
||||
.entry(
|
||||
datastore
|
||||
.as_ref()
|
||||
.map(|d| d.name())
|
||||
.unwrap_or("_unused_")
|
||||
.to_string(),
|
||||
)
|
||||
.or_insert(HashSet::new());
|
||||
|
||||
let chunks = if let Some(datastore) = datastore {
|
||||
restore_chunk_archive(worker.clone(), reader, datastore, checked_chunks, verbose)?
|
||||
restore_chunk_archive(
|
||||
worker.clone(),
|
||||
reader,
|
||||
datastore,
|
||||
checked_chunks,
|
||||
verbose,
|
||||
)?
|
||||
} else {
|
||||
scan_chunk_archive(worker.clone(), reader, verbose)?
|
||||
};
|
||||
@ -1111,11 +1165,16 @@ fn restore_archive<'a>(
|
||||
let archive_header: CatalogArchiveHeader = serde_json::from_slice(&header_data)
|
||||
.map_err(|err| format_err!("unable to parse catalog archive header - {}", err))?;
|
||||
|
||||
task_log!(worker, "File {}: skip catalog '{}'", current_file_number, archive_header.uuid);
|
||||
task_log!(
|
||||
worker,
|
||||
"File {}: skip catalog '{}'",
|
||||
current_file_number,
|
||||
archive_header.uuid
|
||||
);
|
||||
|
||||
reader.skip_data()?; // read all data
|
||||
}
|
||||
_ => bail!("unknown content magic {:?}", header.content_magic),
|
||||
_ => bail!("unknown content magic {:?}", header.content_magic),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -1126,8 +1185,7 @@ fn scan_chunk_archive<'a>(
|
||||
worker: Arc<WorkerTask>,
|
||||
reader: Box<dyn 'a + TapeRead>,
|
||||
verbose: bool,
|
||||
) -> Result<Option<Vec<[u8;32]>>, Error> {
|
||||
|
||||
) -> Result<Option<Vec<[u8; 32]>>, Error> {
|
||||
let mut chunks = Vec::new();
|
||||
|
||||
let mut decoder = ChunkArchiveDecoder::new(reader);
|
||||
@ -1171,10 +1229,9 @@ fn restore_chunk_archive<'a>(
|
||||
worker: Arc<WorkerTask>,
|
||||
reader: Box<dyn 'a + TapeRead>,
|
||||
datastore: Arc<DataStore>,
|
||||
checked_chunks: &mut HashSet<[u8;32]>,
|
||||
checked_chunks: &mut HashSet<[u8; 32]>,
|
||||
verbose: bool,
|
||||
) -> Result<Option<Vec<[u8;32]>>, Error> {
|
||||
|
||||
) -> Result<Option<Vec<[u8; 32]>>, Error> {
|
||||
let mut chunks = Vec::new();
|
||||
|
||||
let mut decoder = ChunkArchiveDecoder::new(reader);
|
||||
@ -1211,7 +1268,6 @@ fn restore_chunk_archive<'a>(
|
||||
|
||||
let verify_and_write_channel = writer_pool.channel();
|
||||
|
||||
|
||||
loop {
|
||||
let (digest, blob) = match decoder.next_chunk() {
|
||||
Ok(Some((digest, blob))) => (digest, blob),
|
||||
@ -1267,7 +1323,6 @@ fn restore_snapshot_archive<'a>(
|
||||
reader: Box<dyn 'a + TapeRead>,
|
||||
snapshot_path: &Path,
|
||||
) -> Result<bool, Error> {
|
||||
|
||||
let mut decoder = pxar::decoder::sync::Decoder::from_std(reader)?;
|
||||
match try_restore_snapshot_archive(worker, &mut decoder, snapshot_path) {
|
||||
Ok(_) => Ok(true),
|
||||
@ -1295,7 +1350,6 @@ fn try_restore_snapshot_archive<R: pxar::decoder::SeqRead>(
|
||||
decoder: &mut pxar::decoder::sync::Decoder<R>,
|
||||
snapshot_path: &Path,
|
||||
) -> Result<BackupManifest, Error> {
|
||||
|
||||
let _root = match decoder.next() {
|
||||
None => bail!("missing root entry"),
|
||||
Some(root) => {
|
||||
@ -1348,12 +1402,12 @@ fn try_restore_snapshot_archive<R: pxar::decoder::SeqRead>(
|
||||
tmp_path.set_extension("tmp");
|
||||
|
||||
if filename == manifest_file_name {
|
||||
|
||||
let blob = DataBlob::load_from_reader(&mut contents)?;
|
||||
let mut old_manifest = BackupManifest::try_from(blob)?;
|
||||
|
||||
// Remove verify_state to indicate that this snapshot is not verified
|
||||
old_manifest.unprotected
|
||||
old_manifest
|
||||
.unprotected
|
||||
.as_object_mut()
|
||||
.map(|m| m.remove("verify_state"));
|
||||
|
||||
@ -1394,7 +1448,11 @@ fn try_restore_snapshot_archive<R: pxar::decoder::SeqRead>(
|
||||
tmp_manifest_path.set_extension("tmp");
|
||||
|
||||
if let Err(err) = std::fs::rename(&tmp_manifest_path, &manifest_path) {
|
||||
bail!("Atomic rename manifest {:?} failed - {}", manifest_path, err);
|
||||
bail!(
|
||||
"Atomic rename manifest {:?} failed - {}",
|
||||
manifest_path,
|
||||
err
|
||||
);
|
||||
}
|
||||
|
||||
Ok(manifest)
|
||||
@ -1406,8 +1464,7 @@ pub fn fast_catalog_restore(
|
||||
drive: &mut Box<dyn TapeDriver>,
|
||||
media_set: &MediaSet,
|
||||
uuid: &Uuid, // current media Uuid
|
||||
) -> Result<bool, Error> {
|
||||
|
||||
) -> Result<bool, Error> {
|
||||
let status_path = Path::new(TAPE_STATUS_DIR);
|
||||
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
@ -1422,10 +1479,15 @@ pub fn fast_catalog_restore(
|
||||
loop {
|
||||
let current_file_number = drive.current_file_number()?;
|
||||
|
||||
{ // limit reader scope
|
||||
{
|
||||
// limit reader scope
|
||||
let mut reader = match drive.read_next_file() {
|
||||
Err(BlockReadError::EndOfFile) => {
|
||||
task_log!(worker, "skip unexpected filemark at pos {}", current_file_number);
|
||||
task_log!(
|
||||
worker,
|
||||
"skip unexpected filemark at pos {}",
|
||||
current_file_number
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Err(BlockReadError::EndOfStream) => {
|
||||
@ -1449,10 +1511,16 @@ pub fn fast_catalog_restore(
|
||||
let header_data = reader.read_exact_allocated(header.size as usize)?;
|
||||
|
||||
let archive_header: CatalogArchiveHeader = serde_json::from_slice(&header_data)
|
||||
.map_err(|err| format_err!("unable to parse catalog archive header - {}", err))?;
|
||||
.map_err(|err| {
|
||||
format_err!("unable to parse catalog archive header - {}", err)
|
||||
})?;
|
||||
|
||||
if &archive_header.media_set_uuid != media_set.uuid() {
|
||||
task_log!(worker, "skipping unrelated catalog at pos {}", current_file_number);
|
||||
task_log!(
|
||||
worker,
|
||||
"skipping unrelated catalog at pos {}",
|
||||
current_file_number
|
||||
);
|
||||
reader.skip_data()?; // read all data
|
||||
continue;
|
||||
}
|
||||
@ -1462,16 +1530,18 @@ pub fn fast_catalog_restore(
|
||||
let wanted = media_set
|
||||
.media_list()
|
||||
.iter()
|
||||
.find(|e| {
|
||||
match e {
|
||||
None => false,
|
||||
Some(uuid) => uuid == catalog_uuid,
|
||||
}
|
||||
.find(|e| match e {
|
||||
None => false,
|
||||
Some(uuid) => uuid == catalog_uuid,
|
||||
})
|
||||
.is_some();
|
||||
|
||||
if !wanted {
|
||||
task_log!(worker, "skip catalog because media '{}' not inventarized", catalog_uuid);
|
||||
task_log!(
|
||||
worker,
|
||||
"skip catalog because media '{}' not inventarized",
|
||||
catalog_uuid
|
||||
);
|
||||
reader.skip_data()?; // read all data
|
||||
continue;
|
||||
}
|
||||
@ -1481,13 +1551,18 @@ pub fn fast_catalog_restore(
|
||||
} else {
|
||||
// only restore if catalog does not exist
|
||||
if MediaCatalog::exists(status_path, catalog_uuid) {
|
||||
task_log!(worker, "catalog for media '{}' already exists", catalog_uuid);
|
||||
task_log!(
|
||||
worker,
|
||||
"catalog for media '{}' already exists",
|
||||
catalog_uuid
|
||||
);
|
||||
reader.skip_data()?; // read all data
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = MediaCatalog::create_temporary_database_file(status_path, catalog_uuid)?;
|
||||
let mut file =
|
||||
MediaCatalog::create_temporary_database_file(status_path, catalog_uuid)?;
|
||||
|
||||
std::io::copy(&mut reader, &mut file)?;
|
||||
|
||||
@ -1496,11 +1571,19 @@ pub fn fast_catalog_restore(
|
||||
match MediaCatalog::parse_catalog_header(&mut file)? {
|
||||
(true, Some(media_uuid), Some(media_set_uuid)) => {
|
||||
if &media_uuid != catalog_uuid {
|
||||
task_log!(worker, "catalog uuid missmatch at pos {}", current_file_number);
|
||||
task_log!(
|
||||
worker,
|
||||
"catalog uuid missmatch at pos {}",
|
||||
current_file_number
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if media_set_uuid != archive_header.media_set_uuid {
|
||||
task_log!(worker, "catalog media_set missmatch at pos {}", current_file_number);
|
||||
task_log!(
|
||||
worker,
|
||||
"catalog media_set missmatch at pos {}",
|
||||
current_file_number
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1510,7 +1593,11 @@ pub fn fast_catalog_restore(
|
||||
task_log!(worker, "successfully restored catalog");
|
||||
found_catalog = true
|
||||
} else {
|
||||
task_log!(worker, "successfully restored related catalog {}", media_uuid);
|
||||
task_log!(
|
||||
worker,
|
||||
"successfully restored related catalog {}",
|
||||
media_uuid
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
Loading…
Reference in New Issue
Block a user