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
;
10 use pbs_tape
::{MediaContentHeader, TapeWrite, PROXMOX_TAPE_BLOCK_SIZE}
;
12 use crate::tape
::file_formats
::{
13 ChunkArchiveEntryHeader
, ChunkArchiveHeader
, PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
,
14 PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
,
17 /// Writes chunk archives to tape.
19 /// A chunk archive consists of a `MediaContentHeader` followed by a
20 /// list of chunks entries. Each chunk entry consists of a
21 /// `ChunkArchiveEntryHeader` followed by the chunk data (`DataBlob`).
23 /// `| MediaContentHeader | ( ChunkArchiveEntryHeader | DataBlob )* |`
24 pub struct ChunkArchiveWriter
<'a
> {
25 writer
: Option
<Box
<dyn TapeWrite
+ 'a
>>,
26 bytes_written
: usize, // does not include bytes from current writer
30 impl<'a
> ChunkArchiveWriter
<'a
> {
31 pub const MAGIC
: [u8; 8] = PROXMOX_BACKUP_CHUNK_ARCHIVE_MAGIC_1_1
;
33 /// Creates a new instance
35 mut writer
: Box
<dyn TapeWrite
+ 'a
>,
38 ) -> Result
<(Self, Uuid
), Error
> {
39 let archive_header
= ChunkArchiveHeader
{
40 store
: store
.to_string(),
42 let header_data
= serde_json
::to_string_pretty(&archive_header
)?
46 let header
= MediaContentHeader
::new(Self::MAGIC
, header_data
.len() as u32);
47 writer
.write_header(&header
, &header_data
)?
;
55 Ok((me
, header
.uuid
.into()))
58 /// Returns the number of bytes written so far.
59 pub fn bytes_written(&self) -> usize {
61 Some(ref writer
) => writer
.bytes_written(),
62 None
=> self.bytes_written
, // finalize sets this
66 fn write_all(&mut self, data
: &[u8]) -> Result
<bool
, std
::io
::Error
> {
68 Some(ref mut writer
) => writer
.write_all(data
),
70 proxmox_lang
::io_bail
!("detected write after archive finished - internal error")
75 /// Write chunk into archive.
77 /// This may return false when `LEOM` is detected (when close_on_leom is set).
78 /// In that case the archive only contains parts of the last chunk.
79 pub fn try_write_chunk(
83 ) -> Result
<bool
, std
::io
::Error
> {
84 if self.writer
.is_none() {
88 let head
= ChunkArchiveEntryHeader
{
89 magic
: PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
,
91 size
: blob
.raw_size(),
94 let head
= head
.to_le();
96 std
::slice
::from_raw_parts(
97 &head
as *const ChunkArchiveEntryHeader
as *const u8,
98 std
::mem
::size_of
::<ChunkArchiveEntryHeader
>(),
102 self.write_all(data
)?
;
105 let blob_data
= blob
.raw_data();
107 if start
>= blob_data
.len() {
111 let end
= start
+ PROXMOX_TAPE_BLOCK_SIZE
;
112 let mut chunk_is_complete
= false;
113 let leom
= if end
> blob_data
.len() {
114 chunk_is_complete
= true;
115 self.write_all(&blob_data
[start
..])?
117 self.write_all(&blob_data
[start
..end
])?
119 if leom
&& self.close_on_leom
{
120 let mut writer
= self.writer
.take().unwrap();
121 writer
.finish(false)?
;
122 self.bytes_written
= writer
.bytes_written();
123 return Ok(chunk_is_complete
);
131 /// This must be called at the end to add padding and `EOF`
133 /// Returns true on `LEOM` or when we hit max archive size
134 pub fn finish(&mut self) -> Result
<bool
, std
::io
::Error
> {
135 match self.writer
.take() {
136 Some(mut writer
) => {
137 self.bytes_written
= writer
.bytes_written();
145 /// Read chunk archives.
146 pub struct ChunkArchiveDecoder
<R
> {
150 impl<R
: Read
> ChunkArchiveDecoder
<R
> {
151 /// Creates a new instance
152 pub fn new(reader
: R
) -> Self {
156 /// Allow access to the underlying reader
157 pub fn reader(&self) -> &R
{
161 /// Returns the next chunk (if any).
162 pub fn next_chunk(&mut self) -> Result
<Option
<([u8; 32], DataBlob
)>, Error
> {
163 let mut header
= ChunkArchiveEntryHeader
{
169 std
::slice
::from_raw_parts_mut(
170 (&mut header
as *mut ChunkArchiveEntryHeader
) as *mut u8,
171 std
::mem
::size_of
::<ChunkArchiveEntryHeader
>(),
175 match self.reader
.read_exact_or_eof(data
) {
178 // last chunk is allowed to be incomplete - simply report EOD
181 Err(err
) => return Err(err
.into()),
184 if header
.magic
!= PROXMOX_BACKUP_CHUNK_ARCHIVE_ENTRY_MAGIC_1_0
{
185 bail
!("wrong magic number");
188 let raw_data
= match self.reader
.read_exact_allocated(header
.size
as usize) {
190 Err(err
) if err
.kind() == std
::io
::ErrorKind
::UnexpectedEof
=> {
191 // last chunk is allowed to be incomplete - simply report EOD
194 Err(err
) => return Err(err
.into()),
197 let blob
= DataBlob
::from_raw(raw_data
)?
;
200 Ok(Some((header
.digest
, blob
)))