3 use anyhow
::{bail, Error}
;
4 use endian_trait
::Endian
;
6 use proxmox_io
::ReadExt
;
7 use proxmox_uuid
::Uuid
;
9 use pbs_datastore
::DataBlob
;
11 PROXMOX_TAPE_BLOCK_SIZE
,
12 TapeWrite
, MediaContentHeader
,
15 use crate::tape
::file_formats
::{
16 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
,
17 PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
,
19 ChunkArchiveEntryHeader
,
22 /// Writes chunk archives to tape.
24 /// A chunk archive consists of a `MediaContentHeader` followed by a
25 /// list of chunks entries. Each chunk entry consists of a
26 /// `ChunkArchiveEntryHeader` followed by the chunk data (`DataBlob`).
28 /// `| MediaContentHeader | ( ChunkArchiveEntryHeader | DataBlob )* |`
29 pub struct ChunkArchiveWriter
<'a
> {
30 writer
: Option
<Box
<dyn TapeWrite
+ 'a
>>,
31 bytes_written
: usize, // does not include bytes from current writer
35 impl <'a
> ChunkArchiveWriter
<'a
> {
37 pub const MAGIC
: [u8; 8] = PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
;
39 /// Creates a new instance
41 mut writer
: Box
<dyn TapeWrite
+ 'a
>,
44 ) -> Result
<(Self,Uuid
), Error
> {
46 let archive_header
= ChunkArchiveHeader { store: store.to_string() }
;
47 let header_data
= serde_json
::to_string_pretty(&archive_header
)?
.as_bytes().to_vec();
49 let header
= MediaContentHeader
::new(Self::MAGIC
, header_data
.len() as u32);
50 writer
.write_header(&header
, &header_data
)?
;
58 Ok((me
, header
.uuid
.into()))
61 /// Returns the number of bytes written so far.
62 pub fn bytes_written(&self) -> usize {
64 Some(ref writer
) => writer
.bytes_written(),
65 None
=> self.bytes_written
, // finalize sets this
69 fn write_all(&mut self, data
: &[u8]) -> Result
<bool
, std
::io
::Error
> {
71 Some(ref mut writer
) => writer
.write_all(data
),
72 None
=> proxmox
::io_bail
!(
73 "detected write after archive finished - internal error"),
77 /// Write chunk into archive.
79 /// This may return false when `LEOM` is detected (when close_on_leom is set).
80 /// In that case the archive only contains parts of the last chunk.
81 pub fn try_write_chunk(
85 ) -> Result
<bool
, std
::io
::Error
> {
87 if self.writer
.is_none() {
91 let head
= ChunkArchiveEntryHeader
{
92 magic
: PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
,
94 size
: blob
.raw_size(),
97 let head
= head
.to_le();
98 let data
= unsafe { std
::slice
::from_raw_parts(
99 &head
as *const ChunkArchiveEntryHeader
as *const u8,
100 std
::mem
::size_of
::<ChunkArchiveEntryHeader
>())
103 self.write_all(data
)?
;
106 let blob_data
= blob
.raw_data();
108 if start
>= blob_data
.len() {
112 let end
= start
+ PROXMOX_TAPE_BLOCK_SIZE
;
113 let mut chunk_is_complete
= false;
114 let leom
= if end
> blob_data
.len() {
115 chunk_is_complete
= true;
116 self.write_all(&blob_data
[start
..])?
118 self.write_all(&blob_data
[start
..end
])?
121 if self.close_on_leom
{
122 let mut writer
= self.writer
.take().unwrap();
123 writer
.finish(false)?
;
124 self.bytes_written
= writer
.bytes_written();
125 return Ok(chunk_is_complete
);
134 /// This must be called at the end to add padding and `EOF`
136 /// Returns true on `LEOM` or when we hit max archive size
137 pub fn finish(&mut self) -> Result
<bool
, std
::io
::Error
> {
138 match self.writer
.take() {
139 Some(mut writer
) => {
140 self.bytes_written
= writer
.bytes_written();
148 /// Read chunk archives.
149 pub struct ChunkArchiveDecoder
<R
> {
153 impl <R
: Read
> ChunkArchiveDecoder
<R
> {
155 /// Creates a new instance
156 pub fn new(reader
: R
) -> Self {
160 /// Allow access to the underlying reader
161 pub fn reader(&self) -> &R
{
165 /// Returns the next chunk (if any).
166 pub fn next_chunk(&mut self) -> Result
<Option
<([u8;32], DataBlob
)>, Error
> {
168 let mut header
= ChunkArchiveEntryHeader
{
174 std
::slice
::from_raw_parts_mut(
175 (&mut header
as *mut ChunkArchiveEntryHeader
) as *mut u8,
176 std
::mem
::size_of
::<ChunkArchiveEntryHeader
>())
179 match self.reader
.read_exact_or_eof(data
) {
182 // last chunk is allowed to be incomplete - simply report EOD
185 Err(err
) => return Err(err
.into()),
188 if header
.magic
!= PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
{
189 bail
!("wrong magic number");
192 let raw_data
= match self.reader
.read_exact_allocated(header
.size
as usize) {
194 Err(err
) if err
.kind() == std
::io
::ErrorKind
::UnexpectedEof
=> {
195 // last chunk is allowed to be incomplete - simply report EOD
198 Err(err
) => return Err(err
.into()),
201 let blob
= DataBlob
::from_raw(raw_data
)?
;
204 Ok(Some((header
.digest
, blob
)))