Skip to main content

tar_codec/
encode.rs

1//! Pure-pax tar encoding for the format-neutral archive builder.
2//!
3//! [`TarEncoder`] owns tar framing, payload padding, sequence numbers, and the
4//! end marker. [`archive_trait::Builder`] supplies high-level entry addition
5//! and recursive filesystem traversal. Compression remains the caller's
6//! concern.
7
8use std::io;
9
10use archive_trait::{
11    ArchiveBuilder, BuildError, EntryMetadata,
12    builder::{BuildFailure, EntryPayload},
13};
14use tar_framing::{
15    UstarKind,
16    write::{
17        FramingWriteError, PaxMember, end_marker_bytes, frame_pax_member_into, payload_padding,
18    },
19};
20use thiserror::Error;
21use tokio::io::{AsyncWrite, AsyncWriteExt};
22
23/// A pure-pax format writer for use with [`ArchiveBuilder::builder`].
24pub struct TarEncoder<W> {
25    writer: W,
26    sequence: u64,
27    framing_buffer: Vec<u8>,
28}
29
30impl<W> TarEncoder<W> {
31    /// Creates an encoder writing an uncompressed pax archive into `writer`.
32    pub fn new(writer: W) -> Self {
33        Self {
34            writer,
35            sequence: 0,
36            framing_buffer: Vec::new(),
37        }
38    }
39}
40
41impl<W: AsyncWrite + Unpin> TarEncoder<W> {
42    async fn write_member(
43        &mut self,
44        member: PaxMember<'_>,
45    ) -> Result<(), BuildFailure<EncodeError>> {
46        let next_sequence = self.sequence.checked_add(1).ok_or_else(|| {
47            BuildFailure::recoverable(BuildError::Encoder(EncodeError::ArithmeticOverflow {
48                context: "pax member sequence",
49            }))
50        })?;
51        frame_pax_member_into(self.sequence, member, &mut self.framing_buffer)
52            .map_err(EncodeError::Framing)
53            .map_err(BuildError::Encoder)
54            .map_err(BuildFailure::recoverable)?;
55        if let Err(source) = self.writer.write_all(&self.framing_buffer).await {
56            return Err(BuildFailure::poisoned(BuildError::Encoder(
57                EncodeError::Write { source },
58            )));
59        }
60        self.sequence = next_sequence;
61        Ok(())
62    }
63
64    async fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), BuildFailure<EncodeError>> {
65        if let Err(source) = self.writer.write_all(bytes).await {
66            return Err(BuildFailure::poisoned(BuildError::Encoder(
67                EncodeError::Write { source },
68            )));
69        }
70        Ok(())
71    }
72}
73
74impl<W: AsyncWrite + Unpin> ArchiveBuilder for TarEncoder<W> {
75    type Error = EncodeError;
76
77    async fn finish_archive(&mut self) -> Result<(), BuildFailure<Self::Error>> {
78        self.write_bytes(end_marker_bytes()).await
79    }
80
81    async fn write_file_member(
82        &mut self,
83        path: &str,
84        payload: &mut EntryPayload<'_>,
85        metadata: EntryMetadata,
86    ) -> Result<(), BuildFailure<Self::Error>> {
87        self.write_member(PaxMember {
88            path,
89            kind: UstarKind::Regular,
90            size: payload.size(),
91            link_path: None,
92            executable: metadata.is_executable(),
93        })
94        .await?;
95        while let Some(chunk) = payload.next_chunk().await.map_err(BuildFailure::poisoned)? {
96            self.write_bytes(chunk).await?;
97        }
98        let padding = payload_padding(payload.size());
99        if !padding.is_empty() {
100            self.write_bytes(padding).await?;
101        }
102        Ok(())
103    }
104
105    async fn write_directory_member(
106        &mut self,
107        path: &str,
108    ) -> Result<(), BuildFailure<Self::Error>> {
109        self.write_member(PaxMember {
110            path,
111            kind: UstarKind::Directory,
112            size: 0,
113            link_path: None,
114            executable: false,
115        })
116        .await
117    }
118
119    async fn write_symbolic_link_member(
120        &mut self,
121        path: &str,
122        target: &str,
123    ) -> Result<(), BuildFailure<Self::Error>> {
124        self.write_member(PaxMember {
125            path,
126            kind: UstarKind::SymbolicLink,
127            size: 0,
128            link_path: Some(target),
129            executable: false,
130        })
131        .await
132    }
133}
134
135/// A tar-specific failure while creating a pure-pax archive.
136#[derive(Debug, Error)]
137pub enum EncodeError {
138    /// A wire-format member could not be framed.
139    #[error(transparent)]
140    Framing(#[from] FramingWriteError),
141    /// Writing the output archive failed.
142    #[error("failed to write archive output")]
143    Write {
144        /// The underlying writer error.
145        #[source]
146        source: io::Error,
147    },
148    /// A tar sequence computation exceeded this API's range.
149    #[error("arithmetic overflow while computing {context}")]
150    ArithmeticOverflow {
151        /// The failed computation.
152        context: &'static str,
153    },
154}