mirror of
https://git.proxmox.com/git/pxar
synced 2025-04-28 12:41:33 +00:00
format/encoder/decoder: new pxar entry type Prelude
Introduces a new pxar format entry type `Prelude` and the associated encoder and decoder methods. A prelude starts with header marker `PXAR_PRELUDE` followed by raw byte content, used to store additional metadata associated with the pxar archive, e.g. command line arguments passed on archive creation. The prelude's content has no fixed encoding format but is stored as an raw, arbitrary byte slice. A prelude entry is encoded right after a pxar format version entry, both being encoded in the metadata archive in case of an archive with dedicated payload output. The prelude is not backwards compatible to pxar format version 1. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
parent
0983094c87
commit
be5d68aa8a
@ -16,6 +16,7 @@ const CONSTANTS: &[(&str, &str, &str)] = &[
|
|||||||
"PXAR_ENTRY_V1",
|
"PXAR_ENTRY_V1",
|
||||||
"__PROXMOX_FORMAT_ENTRY__",
|
"__PROXMOX_FORMAT_ENTRY__",
|
||||||
),
|
),
|
||||||
|
("", "PXAR_PRELUDE", "__PROXMOX_FORMAT_PRELUDE__"),
|
||||||
("", "PXAR_FILENAME", "__PROXMOX_FORMAT_FILENAME__"),
|
("", "PXAR_FILENAME", "__PROXMOX_FORMAT_FILENAME__"),
|
||||||
("", "PXAR_SYMLINK", "__PROXMOX_FORMAT_SYMLINK__"),
|
("", "PXAR_SYMLINK", "__PROXMOX_FORMAT_SYMLINK__"),
|
||||||
("", "PXAR_DEVICE", "__PROXMOX_FORMAT_DEVICE__"),
|
("", "PXAR_DEVICE", "__PROXMOX_FORMAT_DEVICE__"),
|
||||||
|
@ -322,6 +322,12 @@ impl<T: Clone + ReadAt> AccessorImpl<T> {
|
|||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| io_format_err!("unexpected EOF while decoding directory entry"))??;
|
.ok_or_else(|| io_format_err!("unexpected EOF while decoding directory entry"))??;
|
||||||
|
|
||||||
|
if let EntryKind::Prelude(_) = entry.kind() {
|
||||||
|
entry = decoder.next().await.ok_or_else(|| {
|
||||||
|
io_format_err!("unexpected EOF while decoding directory entry")
|
||||||
|
})??;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FileEntryImpl {
|
Ok(FileEntryImpl {
|
||||||
@ -559,6 +565,12 @@ impl<T: Clone + ReadAt> DirectoryImpl<T> {
|
|||||||
.next()
|
.next()
|
||||||
.await
|
.await
|
||||||
.ok_or_else(|| io_format_err!("unexpected EOF while decoding directory entry"))??;
|
.ok_or_else(|| io_format_err!("unexpected EOF while decoding directory entry"))??;
|
||||||
|
|
||||||
|
if let EntryKind::Prelude(_) = entry.kind() {
|
||||||
|
entry = decoder.next().await.ok_or_else(|| {
|
||||||
|
io_format_err!("unexpected EOF while decoding directory entry")
|
||||||
|
})??;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((entry, decoder))
|
Ok((entry, decoder))
|
||||||
|
@ -176,6 +176,7 @@ pub(crate) struct DecoderImpl<T> {
|
|||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
enum State {
|
enum State {
|
||||||
Begin,
|
Begin,
|
||||||
|
Prelude,
|
||||||
Root,
|
Root,
|
||||||
Default,
|
Default,
|
||||||
InPayload {
|
InPayload {
|
||||||
@ -264,10 +265,25 @@ impl<I: SeqRead> DecoderImpl<I> {
|
|||||||
State::Eof => return Ok(None),
|
State::Eof => return Ok(None),
|
||||||
State::Begin => {
|
State::Begin => {
|
||||||
let entry = self.read_next_entry().await.map(Some);
|
let entry = self.read_next_entry().await.map(Some);
|
||||||
|
// If the first entry is of kind Version, next must be Prelude or Directory
|
||||||
if let Ok(Some(ref entry)) = entry {
|
if let Ok(Some(ref entry)) = entry {
|
||||||
if let EntryKind::Version(version) = entry.kind() {
|
if let EntryKind::Version(version) = entry.kind() {
|
||||||
self.version = version.clone();
|
self.version = version.clone();
|
||||||
self.state = State::Root;
|
self.state = State::Prelude;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
State::Prelude => {
|
||||||
|
let entry = self.read_next_entry().await.map(Some);
|
||||||
|
if let Ok(Some(ref entry)) = entry {
|
||||||
|
match entry.kind() {
|
||||||
|
EntryKind::Prelude(_) => self.state = State::Root,
|
||||||
|
EntryKind::Directory => self.state = State::InDirectory,
|
||||||
|
_ => io_bail!(
|
||||||
|
"expected directory or prelude entry, got entry kind {:?}",
|
||||||
|
entry.kind()
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
@ -433,6 +449,14 @@ impl<I: SeqRead> DecoderImpl<I> {
|
|||||||
self.current_header = header;
|
self.current_header = header;
|
||||||
self.entry.kind = EntryKind::Version(self.read_format_version().await?);
|
self.entry.kind = EntryKind::Version(self.read_format_version().await?);
|
||||||
|
|
||||||
|
Ok(Some(self.entry.take()))
|
||||||
|
} else if header.htype == format::PXAR_PRELUDE {
|
||||||
|
if previous_state != State::Prelude {
|
||||||
|
io_bail!("Got format version entry at unexpected position");
|
||||||
|
}
|
||||||
|
self.current_header = header;
|
||||||
|
self.entry.kind = EntryKind::Prelude(self.read_prelude().await?);
|
||||||
|
|
||||||
Ok(Some(self.entry.take()))
|
Ok(Some(self.entry.take()))
|
||||||
} else if header.htype == format::PXAR_ENTRY || header.htype == format::PXAR_ENTRY_V1 {
|
} else if header.htype == format::PXAR_ENTRY || header.htype == format::PXAR_ENTRY_V1 {
|
||||||
if header.htype == format::PXAR_ENTRY {
|
if header.htype == format::PXAR_ENTRY {
|
||||||
@ -797,6 +821,11 @@ impl<I: SeqRead> DecoderImpl<I> {
|
|||||||
let version: u64 = seq_read_entry(self.input.archive_mut()).await?;
|
let version: u64 = seq_read_entry(self.input.archive_mut()).await?;
|
||||||
FormatVersion::deserialize(version)
|
FormatVersion::deserialize(version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn read_prelude(&mut self) -> io::Result<format::Prelude> {
|
||||||
|
let data = self.read_entry_as_bytes().await?;
|
||||||
|
Ok(format::Prelude { data })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reader for file contents inside a pxar archive.
|
/// Reader for file contents inside a pxar archive.
|
||||||
|
@ -24,8 +24,14 @@ impl<'a, T: tokio::io::AsyncWrite + 'a> Encoder<'a, TokioWriter<T>> {
|
|||||||
pub async fn from_tokio(
|
pub async fn from_tokio(
|
||||||
output: PxarVariant<T, T>,
|
output: PxarVariant<T, T>,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
|
prelude: Option<&[u8]>,
|
||||||
) -> io::Result<Encoder<'a, TokioWriter<T>>> {
|
) -> io::Result<Encoder<'a, TokioWriter<T>>> {
|
||||||
Encoder::new(output.wrap(|output| TokioWriter::new(output)), metadata).await
|
Encoder::new(
|
||||||
|
output.wrap(|output| TokioWriter::new(output)),
|
||||||
|
metadata,
|
||||||
|
prelude,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +47,7 @@ impl<'a> Encoder<'a, TokioWriter<tokio::fs::File>> {
|
|||||||
tokio::fs::File::create(path.as_ref()).await?,
|
tokio::fs::File::create(path.as_ref()).await?,
|
||||||
)),
|
)),
|
||||||
metadata,
|
metadata,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@ -48,10 +55,14 @@ impl<'a> Encoder<'a, TokioWriter<tokio::fs::File>> {
|
|||||||
|
|
||||||
impl<'a, T: SeqWrite + 'a> Encoder<'a, T> {
|
impl<'a, T: SeqWrite + 'a> Encoder<'a, T> {
|
||||||
/// Create an asynchronous encoder for an output implementing our internal write interface.
|
/// Create an asynchronous encoder for an output implementing our internal write interface.
|
||||||
pub async fn new(output: PxarVariant<T, T>, metadata: &Metadata) -> io::Result<Encoder<'a, T>> {
|
pub async fn new(
|
||||||
|
output: PxarVariant<T, T>,
|
||||||
|
metadata: &Metadata,
|
||||||
|
prelude: Option<&[u8]>,
|
||||||
|
) -> io::Result<Encoder<'a, T>> {
|
||||||
let output = output.wrap_multi(|output| output.into(), |payload_output| payload_output);
|
let output = output.wrap_multi(|output| output.into(), |payload_output| payload_output);
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: encoder::EncoderImpl::new(output, metadata).await?,
|
inner: encoder::EncoderImpl::new(output, metadata, prelude).await?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,6 +337,7 @@ mod test {
|
|||||||
let mut encoder = Encoder::new(
|
let mut encoder = Encoder::new(
|
||||||
crate::PxarVariant::Unified(DummyOutput),
|
crate::PxarVariant::Unified(DummyOutput),
|
||||||
&Metadata::dir_builder(0o700).build(),
|
&Metadata::dir_builder(0o700).build(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -346,6 +346,7 @@ impl<'a, T: SeqWrite + 'a> EncoderImpl<'a, T> {
|
|||||||
pub async fn new(
|
pub async fn new(
|
||||||
mut output: PxarVariant<EncoderOutput<'a, T>, T>,
|
mut output: PxarVariant<EncoderOutput<'a, T>, T>,
|
||||||
metadata: &Metadata,
|
metadata: &Metadata,
|
||||||
|
prelude: Option<&[u8]>,
|
||||||
) -> io::Result<EncoderImpl<'a, T>> {
|
) -> io::Result<EncoderImpl<'a, T>> {
|
||||||
if !metadata.is_dir() {
|
if !metadata.is_dir() {
|
||||||
io_bail!("directory metadata must contain the directory mode flag");
|
io_bail!("directory metadata must contain the directory mode flag");
|
||||||
@ -372,6 +373,9 @@ impl<'a, T: SeqWrite + 'a> EncoderImpl<'a, T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.encode_format_version().await?;
|
this.encode_format_version().await?;
|
||||||
|
if let Some(prelude) = prelude {
|
||||||
|
this.encode_prelude(prelude).await?;
|
||||||
|
}
|
||||||
this.encode_metadata(metadata).await?;
|
this.encode_metadata(metadata).await?;
|
||||||
let state = this.state_mut()?;
|
let state = this.state_mut()?;
|
||||||
state.files_offset = state.position();
|
state.files_offset = state.position();
|
||||||
@ -773,6 +777,28 @@ impl<'a, T: SeqWrite + 'a> EncoderImpl<'a, T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn encode_prelude(&mut self, prelude: &[u8]) -> io::Result<()> {
|
||||||
|
if self.version == FormatVersion::Version1 {
|
||||||
|
io_bail!("encoding prelude not supported in format version 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut output, state) = self.output_state()?;
|
||||||
|
if state.write_position != (size_of::<u64>() + size_of::<format::Header>()) as u64 {
|
||||||
|
io_bail!(
|
||||||
|
"prelude must be encoded following the version header, current position {}",
|
||||||
|
state.write_position,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
seq_write_pxar_entry(
|
||||||
|
output.archive_mut(),
|
||||||
|
format::PXAR_PRELUDE,
|
||||||
|
prelude,
|
||||||
|
&mut state.write_position,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
async fn encode_format_version(&mut self) -> io::Result<()> {
|
async fn encode_format_version(&mut self) -> io::Result<()> {
|
||||||
if let Some(version_bytes) = self.version.serialize() {
|
if let Some(version_bytes) = self.version.serialize() {
|
||||||
let (mut output, state) = self.output_state()?;
|
let (mut output, state) = self.output_state()?;
|
||||||
|
@ -28,7 +28,11 @@ impl<'a, T: io::Write + 'a> Encoder<'a, StandardWriter<T>> {
|
|||||||
/// Encode a `pxar` archive into a regular `std::io::Write` output.
|
/// Encode a `pxar` archive into a regular `std::io::Write` output.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_std(output: T, metadata: &Metadata) -> io::Result<Encoder<'a, StandardWriter<T>>> {
|
pub fn from_std(output: T, metadata: &Metadata) -> io::Result<Encoder<'a, StandardWriter<T>>> {
|
||||||
Encoder::new(PxarVariant::Unified(StandardWriter::new(output)), metadata)
|
Encoder::new(
|
||||||
|
PxarVariant::Unified(StandardWriter::new(output)),
|
||||||
|
metadata,
|
||||||
|
None,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +45,7 @@ impl<'a> Encoder<'a, StandardWriter<std::fs::File>> {
|
|||||||
Encoder::new(
|
Encoder::new(
|
||||||
PxarVariant::Unified(StandardWriter::new(std::fs::File::create(path.as_ref())?)),
|
PxarVariant::Unified(StandardWriter::new(std::fs::File::create(path.as_ref())?)),
|
||||||
metadata,
|
metadata,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,11 +57,15 @@ impl<'a, T: SeqWrite + 'a> Encoder<'a, T> {
|
|||||||
/// not allowed to use the `Waker`, as this will cause a `panic!`.
|
/// not allowed to use the `Waker`, as this will cause a `panic!`.
|
||||||
// Optionally attach a dedicated writer to redirect the payloads of regular files to a separate
|
// Optionally attach a dedicated writer to redirect the payloads of regular files to a separate
|
||||||
// output.
|
// output.
|
||||||
pub fn new(output: PxarVariant<T, T>, metadata: &Metadata) -> io::Result<Self> {
|
pub fn new(
|
||||||
|
output: PxarVariant<T, T>,
|
||||||
|
metadata: &Metadata,
|
||||||
|
prelude: Option<&[u8]>,
|
||||||
|
) -> io::Result<Self> {
|
||||||
let output = output.wrap_multi(|output| output.into(), |payload_output| payload_output);
|
let output = output.wrap_multi(|output| output.into(), |payload_output| payload_output);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: poll_result_once(encoder::EncoderImpl::new(output, metadata))?,
|
inner: poll_result_once(encoder::EncoderImpl::new(output, metadata, prelude))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +87,7 @@ pub const PXAR_FORMAT_VERSION: u64 = 0x730f6c75df16a40d;
|
|||||||
pub const PXAR_ENTRY: u64 = 0xd5956474e588acef;
|
pub const PXAR_ENTRY: u64 = 0xd5956474e588acef;
|
||||||
/// Previous version of the entry struct
|
/// Previous version of the entry struct
|
||||||
pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff;
|
pub const PXAR_ENTRY_V1: u64 = 0x11da850a1c1cceff;
|
||||||
|
pub const PXAR_PRELUDE: u64 = 0xe309d79d9f7b771b;
|
||||||
pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
|
pub const PXAR_FILENAME: u64 = 0x16701121063917b3;
|
||||||
pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
|
pub const PXAR_SYMLINK: u64 = 0x27f971e7dbf5dc5f;
|
||||||
pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
|
pub const PXAR_DEVICE: u64 = 0x9fc9e906586d5ce9;
|
||||||
@ -147,6 +148,7 @@ impl Header {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn max_content_size(&self) -> u64 {
|
pub fn max_content_size(&self) -> u64 {
|
||||||
match self.htype {
|
match self.htype {
|
||||||
|
PXAR_PRELUDE => u64::MAX - (size_of::<Self>() as u64),
|
||||||
// + null-termination
|
// + null-termination
|
||||||
PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1,
|
PXAR_FILENAME => crate::util::MAX_FILENAME_LEN + 1,
|
||||||
// + null-termination
|
// + null-termination
|
||||||
@ -190,6 +192,7 @@ impl Display for Header {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let readable = match self.htype {
|
let readable = match self.htype {
|
||||||
PXAR_FORMAT_VERSION => "FORMAT_VERSION",
|
PXAR_FORMAT_VERSION => "FORMAT_VERSION",
|
||||||
|
PXAR_PRELUDE => "PRELUDE",
|
||||||
PXAR_FILENAME => "FILENAME",
|
PXAR_FILENAME => "FILENAME",
|
||||||
PXAR_SYMLINK => "SYMLINK",
|
PXAR_SYMLINK => "SYMLINK",
|
||||||
PXAR_HARDLINK => "HARDLINK",
|
PXAR_HARDLINK => "HARDLINK",
|
||||||
@ -712,6 +715,29 @@ impl Device {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Prelude {
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prelude {
|
||||||
|
pub fn as_os_str(&self) -> &OsStr {
|
||||||
|
self.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for Prelude {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<OsStr> for Prelude {
|
||||||
|
fn as_ref(&self) -> &OsStr {
|
||||||
|
OsStr::from_bytes(&self.data[..self.data.len().max(1) - 1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(all(test, target_os = "linux"))]
|
#[cfg(all(test, target_os = "linux"))]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_linux_devices() {
|
fn test_linux_devices() {
|
||||||
|
@ -345,6 +345,9 @@ pub enum EntryKind {
|
|||||||
/// Pxar file format version
|
/// Pxar file format version
|
||||||
Version(format::FormatVersion),
|
Version(format::FormatVersion),
|
||||||
|
|
||||||
|
/// Pxar prelude blob
|
||||||
|
Prelude(format::Prelude),
|
||||||
|
|
||||||
/// Symbolic links.
|
/// Symbolic links.
|
||||||
Symlink(format::Symlink),
|
Symlink(format::Symlink),
|
||||||
|
|
||||||
|
@ -230,6 +230,7 @@ impl Entry {
|
|||||||
};
|
};
|
||||||
match item.kind() {
|
match item.kind() {
|
||||||
PxarEntryKind::Version(_) => continue,
|
PxarEntryKind::Version(_) => continue,
|
||||||
|
PxarEntryKind::Prelude(_) => continue,
|
||||||
PxarEntryKind::GoodbyeTable => break,
|
PxarEntryKind::GoodbyeTable => break,
|
||||||
PxarEntryKind::File { size, .. } => {
|
PxarEntryKind::File { size, .. } => {
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
Loading…
Reference in New Issue
Block a user