2021-02-04 06:58:34 +00:00
|
|
|
//! File format definitions and implementations for data written to
|
|
|
|
//! tapes
|
|
|
|
|
|
|
|
mod blocked_reader;
|
|
|
|
pub use blocked_reader::*;
|
|
|
|
|
|
|
|
mod blocked_writer;
|
|
|
|
pub use blocked_writer::*;
|
|
|
|
|
|
|
|
mod chunk_archive;
|
|
|
|
pub use chunk_archive::*;
|
|
|
|
|
|
|
|
mod snapshot_archive;
|
|
|
|
pub use snapshot_archive::*;
|
2021-01-21 12:19:07 +00:00
|
|
|
|
2021-03-19 05:58:02 +00:00
|
|
|
mod catalog_archive;
|
|
|
|
pub use catalog_archive::*;
|
|
|
|
|
2021-02-04 07:36:35 +00:00
|
|
|
mod multi_volume_writer;
|
|
|
|
pub use multi_volume_writer::*;
|
|
|
|
|
|
|
|
mod multi_volume_reader;
|
|
|
|
pub use multi_volume_reader::*;
|
|
|
|
|
2020-12-14 11:55:49 +00:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
2020-12-05 09:45:08 +00:00
|
|
|
use anyhow::{bail, Error};
|
|
|
|
use ::serde::{Deserialize, Serialize};
|
|
|
|
use endian_trait::Endian;
|
|
|
|
use bitflags::bitflags;
|
|
|
|
|
|
|
|
use proxmox::tools::Uuid;
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
use crate::backup::Fingerprint;
|
|
|
|
|
2020-12-05 09:45:08 +00:00
|
|
|
/// We use 256KB blocksize (always)
|
|
|
|
pub const PROXMOX_TAPE_BLOCK_SIZE: usize = 256*1024;
|
|
|
|
|
|
|
|
// openssl::sha::sha256(b"Proxmox Tape Block Header v1.0")[0..8]
|
|
|
|
pub const PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0: [u8; 8] = [220, 189, 175, 202, 235, 160, 165, 40];
|
|
|
|
|
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Content Header v1.0")[0..8];
|
|
|
|
pub const PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0: [u8; 8] = [99, 238, 20, 159, 205, 242, 155, 12];
|
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Tape Label v1.0")[0..8];
|
2020-12-14 16:37:16 +00:00
|
|
|
pub const PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0: [u8; 8] = [42, 5, 191, 60, 176, 48, 170, 57];
|
2020-12-05 09:45:08 +00:00
|
|
|
// openssl::sha::sha256(b"Proxmox Backup MediaSet Label v1.0")
|
|
|
|
pub const PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0: [u8; 8] = [8, 96, 99, 249, 47, 151, 83, 216];
|
|
|
|
|
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Chunk Archive v1.0")[0..8]
|
2021-03-16 11:52:49 +00:00
|
|
|
// only used in unreleased version - no longer supported
|
2020-12-05 09:45:08 +00:00
|
|
|
pub const PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0: [u8; 8] = [62, 173, 167, 95, 49, 76, 6, 110];
|
2021-03-16 11:52:49 +00:00
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Chunk Archive v1.1")[0..8]
|
|
|
|
pub const PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1: [u8; 8] = [109, 49, 99, 109, 215, 2, 131, 191];
|
|
|
|
|
2020-12-05 09:45:08 +00:00
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Chunk Archive Entry v1.0")[0..8]
|
|
|
|
pub const PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0: [u8; 8] = [72, 87, 109, 242, 222, 66, 143, 220];
|
|
|
|
|
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Snapshot Archive v1.0")[0..8];
|
2021-03-16 11:52:49 +00:00
|
|
|
// only used in unreleased version - no longer supported
|
2020-12-05 09:45:08 +00:00
|
|
|
pub const PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0: [u8; 8] = [9, 182, 2, 31, 125, 232, 114, 133];
|
2021-03-16 11:52:49 +00:00
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Snapshot Archive v1.1")[0..8];
|
|
|
|
pub const PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_1: [u8; 8] = [218, 22, 21, 208, 17, 226, 154, 98];
|
2020-12-05 09:45:08 +00:00
|
|
|
|
2021-03-17 12:35:23 +00:00
|
|
|
// openssl::sha::sha256(b"Proxmox Backup Catalog Archive v1.0")[0..8];
|
|
|
|
pub const PROXMOX_BACKUP_CATALOG_ARCHIVE_MAGIC_1_0: [u8; 8] = [183, 207, 199, 37, 158, 153, 30, 115];
|
|
|
|
|
2020-12-14 11:55:49 +00:00
|
|
|
lazy_static::lazy_static!{
|
2021-02-04 06:58:34 +00:00
|
|
|
// Map content magic numbers to human readable names.
|
|
|
|
static ref PROXMOX_TAPE_CONTENT_NAME: HashMap<&'static [u8;8], &'static str> = {
|
2020-12-14 11:55:49 +00:00
|
|
|
let mut map = HashMap::new();
|
2020-12-14 16:37:16 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_MEDIA_LABEL_MAGIC_1_0, "Proxmox Backup Tape Label v1.0");
|
2020-12-14 11:55:49 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_MEDIA_SET_LABEL_MAGIC_1_0, "Proxmox Backup MediaSet Label v1.0");
|
|
|
|
map.insert(&PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_0, "Proxmox Backup Chunk Archive v1.0");
|
2021-03-16 11:52:49 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1, "Proxmox Backup Chunk Archive v1.1");
|
2020-12-14 11:55:49 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_0, "Proxmox Backup Snapshot Archive v1.0");
|
2021-03-16 11:52:49 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_SNAPSHOT_ARCHIVE_MAGIC_1_1, "Proxmox Backup Snapshot Archive v1.1");
|
2021-03-17 12:35:23 +00:00
|
|
|
map.insert(&PROXMOX_BACKUP_CATALOG_ARCHIVE_MAGIC_1_0, "Proxmox Backup Catalog Archive v1.0");
|
2020-12-14 11:55:49 +00:00
|
|
|
map
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-02-04 06:58:34 +00:00
|
|
|
/// Map content magic numbers to human readable names.
|
|
|
|
pub fn proxmox_tape_magic_to_text(magic: &[u8; 8]) -> Option<String> {
|
|
|
|
PROXMOX_TAPE_CONTENT_NAME.get(magic).map(|s| String::from(*s))
|
|
|
|
}
|
|
|
|
|
2020-12-06 09:26:24 +00:00
|
|
|
/// Tape Block Header with data payload
|
|
|
|
///
|
2020-12-14 16:29:57 +00:00
|
|
|
/// All tape files are written as sequence of blocks.
|
|
|
|
///
|
2020-12-05 09:45:08 +00:00
|
|
|
/// Note: this struct is large, never put this on the stack!
|
|
|
|
/// so we use an unsized type to avoid that.
|
2020-12-06 09:26:24 +00:00
|
|
|
///
|
|
|
|
/// Tape data block are always read/written with a fixed size
|
2020-12-16 11:23:52 +00:00
|
|
|
/// (`PROXMOX_TAPE_BLOCK_SIZE`). But they may contain less data, so the
|
2020-12-06 09:26:24 +00:00
|
|
|
/// header has an additional size field. For streams of blocks, there
|
2020-12-16 11:23:52 +00:00
|
|
|
/// is a sequence number (`seq_nr`) which may be use for additional
|
2020-12-06 09:26:24 +00:00
|
|
|
/// error checking.
|
2020-12-05 09:45:08 +00:00
|
|
|
#[repr(C,packed)]
|
|
|
|
pub struct BlockHeader {
|
2020-12-16 11:23:52 +00:00
|
|
|
/// fixed value `PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0`
|
2020-12-05 09:45:08 +00:00
|
|
|
pub magic: [u8; 8],
|
|
|
|
pub flags: BlockHeaderFlags,
|
|
|
|
/// size as 3 bytes unsigned, little endian
|
|
|
|
pub size: [u8; 3],
|
|
|
|
/// block sequence number
|
|
|
|
pub seq_nr: u32,
|
|
|
|
pub payload: [u8],
|
|
|
|
}
|
|
|
|
|
|
|
|
bitflags! {
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Header flags (e.g. `END_OF_STREAM` or `INCOMPLETE`)
|
2020-12-05 09:45:08 +00:00
|
|
|
pub struct BlockHeaderFlags: u8 {
|
|
|
|
/// Marks the last block in a stream.
|
|
|
|
const END_OF_STREAM = 0b00000001;
|
|
|
|
/// Mark multivolume streams (when set in the last block)
|
|
|
|
const INCOMPLETE = 0b00000010;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Endian, Copy, Clone, Debug)]
|
|
|
|
#[repr(C,packed)]
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Media Content Header
|
|
|
|
///
|
|
|
|
/// All tape files start with this header. The header may contain some
|
2020-12-16 11:23:52 +00:00
|
|
|
/// informational data indicated by `size`.
|
2020-12-14 16:29:57 +00:00
|
|
|
///
|
2020-12-16 11:23:52 +00:00
|
|
|
/// `| MediaContentHeader | header data (size) | stream data |`
|
2020-12-14 16:29:57 +00:00
|
|
|
///
|
|
|
|
/// Note: The stream data following may be of any size.
|
2020-12-05 09:45:08 +00:00
|
|
|
pub struct MediaContentHeader {
|
2020-12-16 11:23:52 +00:00
|
|
|
/// fixed value `PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0`
|
2020-12-05 09:45:08 +00:00
|
|
|
pub magic: [u8; 8],
|
2020-12-14 16:29:57 +00:00
|
|
|
/// magic number for the content following
|
2020-12-05 09:45:08 +00:00
|
|
|
pub content_magic: [u8; 8],
|
2020-12-14 16:29:57 +00:00
|
|
|
/// unique ID to identify this data stream
|
2020-12-05 09:45:08 +00:00
|
|
|
pub uuid: [u8; 16],
|
2020-12-14 16:29:57 +00:00
|
|
|
/// stream creation time
|
2020-12-05 09:45:08 +00:00
|
|
|
pub ctime: i64,
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Size of header data
|
2020-12-05 09:45:08 +00:00
|
|
|
pub size: u32,
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Part number for multipart archives.
|
2020-12-05 09:45:08 +00:00
|
|
|
pub part_number: u8,
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Reserved for future use
|
2020-12-05 09:45:08 +00:00
|
|
|
pub reserved_0: u8,
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Reserved for future use
|
2020-12-05 09:45:08 +00:00
|
|
|
pub reserved_1: u8,
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Reserved for future use
|
2020-12-05 09:45:08 +00:00
|
|
|
pub reserved_2: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MediaContentHeader {
|
|
|
|
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Create a new instance with autogenerated Uuid
|
2020-12-05 09:45:08 +00:00
|
|
|
pub fn new(content_magic: [u8; 8], size: u32) -> Self {
|
|
|
|
let uuid = *proxmox::tools::uuid::Uuid::generate()
|
|
|
|
.into_inner();
|
|
|
|
Self {
|
|
|
|
magic: PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0,
|
|
|
|
content_magic,
|
|
|
|
uuid,
|
|
|
|
ctime: proxmox::tools::time::epoch_i64(),
|
|
|
|
size,
|
|
|
|
part_number: 0,
|
|
|
|
reserved_0: 0,
|
|
|
|
reserved_1: 0,
|
|
|
|
reserved_2: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Helper to check magic numbers and size constraints
|
2020-12-05 09:45:08 +00:00
|
|
|
pub fn check(&self, content_magic: [u8; 8], min_size: u32, max_size: u32) -> Result<(), Error> {
|
|
|
|
if self.magic != PROXMOX_BACKUP_CONTENT_HEADER_MAGIC_1_0 {
|
|
|
|
bail!("MediaContentHeader: wrong magic");
|
|
|
|
}
|
|
|
|
if self.content_magic != content_magic {
|
|
|
|
bail!("MediaContentHeader: wrong content magic");
|
|
|
|
}
|
|
|
|
if self.size < min_size || self.size > max_size {
|
|
|
|
bail!("MediaContentHeader: got unexpected size");
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2020-12-14 16:29:57 +00:00
|
|
|
/// Returns the content Uuid
|
2020-12-05 09:45:08 +00:00
|
|
|
pub fn content_uuid(&self) -> Uuid {
|
|
|
|
Uuid::from(self.uuid)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-16 11:52:49 +00:00
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
/// Header for chunk archives
|
|
|
|
pub struct ChunkArchiveHeader {
|
|
|
|
// Datastore name
|
|
|
|
pub store: String,
|
|
|
|
}
|
|
|
|
|
2020-12-14 16:29:57 +00:00
|
|
|
#[derive(Endian)]
|
|
|
|
#[repr(C,packed)]
|
2020-12-16 11:08:34 +00:00
|
|
|
/// Header for data blobs inside a chunk archive
|
2020-12-14 16:29:57 +00:00
|
|
|
pub struct ChunkArchiveEntryHeader {
|
2020-12-16 11:23:52 +00:00
|
|
|
/// fixed value `PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0`
|
2020-12-14 16:29:57 +00:00
|
|
|
pub magic: [u8; 8],
|
2020-12-16 11:08:34 +00:00
|
|
|
/// Chunk digest
|
2020-12-14 16:29:57 +00:00
|
|
|
pub digest: [u8; 32],
|
2020-12-16 11:08:34 +00:00
|
|
|
/// Chunk size
|
2020-12-14 16:29:57 +00:00
|
|
|
pub size: u64,
|
|
|
|
}
|
|
|
|
|
2021-03-16 11:52:49 +00:00
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
/// Header for snapshot archives
|
|
|
|
pub struct SnapshotArchiveHeader {
|
|
|
|
/// Snapshot name
|
|
|
|
pub snapshot: String,
|
|
|
|
/// Datastore name
|
|
|
|
pub store: String,
|
|
|
|
}
|
|
|
|
|
2021-03-18 07:43:55 +00:00
|
|
|
#[derive(Deserialize, Serialize)]
|
|
|
|
/// Header for Catalog archives
|
|
|
|
pub struct CatalogArchiveHeader {
|
|
|
|
/// The uuid of the media the catalog is for
|
|
|
|
pub uuid: Uuid,
|
|
|
|
/// The media set uuid the catalog is for
|
|
|
|
pub media_set_uuid: Uuid,
|
|
|
|
/// Media sequence number
|
|
|
|
pub seq_nr: u64,
|
|
|
|
}
|
|
|
|
|
2020-12-05 09:45:08 +00:00
|
|
|
#[derive(Serialize,Deserialize,Clone,Debug)]
|
2020-12-14 16:37:16 +00:00
|
|
|
/// Media Label
|
2020-12-14 16:29:57 +00:00
|
|
|
///
|
2020-12-14 16:37:16 +00:00
|
|
|
/// Media labels are used to uniquely identify a media. They are
|
2020-12-14 16:29:57 +00:00
|
|
|
/// stored as first file on the tape.
|
2020-12-14 16:37:16 +00:00
|
|
|
pub struct MediaLabel {
|
2020-12-05 09:45:08 +00:00
|
|
|
/// Unique ID
|
|
|
|
pub uuid: Uuid,
|
2021-01-13 12:26:59 +00:00
|
|
|
/// Media label text (or Barcode)
|
|
|
|
pub label_text: String,
|
2020-12-05 09:45:08 +00:00
|
|
|
/// Creation time stamp
|
|
|
|
pub ctime: i64,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize,Deserialize,Clone,Debug)]
|
2020-12-16 11:23:52 +00:00
|
|
|
/// `MediaSet` Label
|
2020-12-14 16:29:57 +00:00
|
|
|
///
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Used to uniquely identify a `MediaSet`. They are stored as second
|
2020-12-14 16:29:57 +00:00
|
|
|
/// file on the tape.
|
2020-12-05 09:45:08 +00:00
|
|
|
pub struct MediaSetLabel {
|
2020-12-16 11:23:52 +00:00
|
|
|
/// The associated `MediaPool`
|
2020-12-05 09:45:08 +00:00
|
|
|
pub pool: String,
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Uuid. We use the all-zero Uuid to reseve an empty media for a specific pool
|
2020-12-05 09:45:08 +00:00
|
|
|
pub uuid: Uuid,
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Media sequence number
|
2020-12-05 09:45:08 +00:00
|
|
|
pub seq_nr: u64,
|
|
|
|
/// Creation time stamp
|
|
|
|
pub ctime: i64,
|
2021-01-18 12:36:11 +00:00
|
|
|
/// Encryption key finkerprint (if encryped)
|
|
|
|
#[serde(skip_serializing_if="Option::is_none")]
|
|
|
|
pub encryption_key_fingerprint: Option<Fingerprint>,
|
2020-12-05 09:45:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl MediaSetLabel {
|
|
|
|
|
2021-01-18 12:36:11 +00:00
|
|
|
pub fn with_data(
|
|
|
|
pool: &str,
|
|
|
|
uuid: Uuid,
|
|
|
|
seq_nr: u64,
|
|
|
|
ctime: i64,
|
|
|
|
encryption_key_fingerprint: Option<Fingerprint>,
|
|
|
|
) -> Self {
|
2020-12-05 09:45:08 +00:00
|
|
|
Self {
|
|
|
|
pool: pool.to_string(),
|
|
|
|
uuid,
|
|
|
|
seq_nr,
|
|
|
|
ctime,
|
2021-01-18 12:36:11 +00:00
|
|
|
encryption_key_fingerprint,
|
2020-12-05 09:45:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-12-06 09:26:24 +00:00
|
|
|
|
|
|
|
impl BlockHeader {
|
|
|
|
|
|
|
|
pub const SIZE: usize = PROXMOX_TAPE_BLOCK_SIZE;
|
|
|
|
|
|
|
|
/// Allocates a new instance on the heap
|
|
|
|
pub fn new() -> Box<Self> {
|
|
|
|
use std::alloc::{alloc_zeroed, Layout};
|
|
|
|
|
|
|
|
let mut buffer = unsafe {
|
|
|
|
let ptr = alloc_zeroed(
|
|
|
|
Layout::from_size_align(Self::SIZE, std::mem::align_of::<u64>())
|
|
|
|
.unwrap(),
|
|
|
|
);
|
|
|
|
Box::from_raw(
|
|
|
|
std::slice::from_raw_parts_mut(ptr, Self::SIZE - 16)
|
|
|
|
as *mut [u8] as *mut Self
|
|
|
|
)
|
|
|
|
};
|
|
|
|
buffer.magic = PROXMOX_TAPE_BLOCK_HEADER_MAGIC_1_0;
|
|
|
|
buffer
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Set the `size` field
|
2020-12-06 09:26:24 +00:00
|
|
|
pub fn set_size(&mut self, size: usize) {
|
|
|
|
let size = size.to_le_bytes();
|
|
|
|
self.size.copy_from_slice(&size[..3]);
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Returns the `size` field
|
2020-12-06 09:26:24 +00:00
|
|
|
pub fn size(&self) -> usize {
|
|
|
|
(self.size[0] as usize) + ((self.size[1] as usize)<<8) + ((self.size[2] as usize)<<16)
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Set the `seq_nr` field
|
2020-12-06 09:26:24 +00:00
|
|
|
pub fn set_seq_nr(&mut self, seq_nr: u32) {
|
|
|
|
self.seq_nr = seq_nr.to_le();
|
|
|
|
}
|
|
|
|
|
2020-12-16 11:23:52 +00:00
|
|
|
/// Returns the `seq_nr` field
|
2020-12-06 09:26:24 +00:00
|
|
|
pub fn seq_nr(&self) -> u32 {
|
|
|
|
u32::from_le(self.seq_nr)
|
|
|
|
}
|
|
|
|
}
|