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, FilePayload},
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    /// Returns the underlying writer without finalizing the archive.
41    ///
42    /// Use [`archive_trait::Builder::finish_into_inner`] first to recover the
43    /// writer from a completed archive.
44    pub fn into_inner(self) -> W {
45        self.writer
46    }
47}
48
49impl<W: AsyncWrite + Unpin> TarEncoder<W> {
50    async fn write_member(
51        &mut self,
52        member: PaxMember<'_>,
53    ) -> Result<(), BuildFailure<EncodeError>> {
54        let next_sequence = self.sequence.checked_add(1).ok_or_else(|| {
55            BuildFailure::recoverable(BuildError::Encoder(EncodeError::ArithmeticOverflow {
56                context: "pax member sequence",
57            }))
58        })?;
59        frame_pax_member_into(self.sequence, member, &mut self.framing_buffer)
60            .map_err(EncodeError::Framing)
61            .map_err(BuildError::Encoder)
62            .map_err(BuildFailure::recoverable)?;
63        if let Err(source) = self.writer.write_all(&self.framing_buffer).await {
64            return Err(BuildFailure::poisoned(BuildError::Encoder(
65                EncodeError::Write { source },
66            )));
67        }
68        self.sequence = next_sequence;
69        Ok(())
70    }
71
72    async fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), BuildFailure<EncodeError>> {
73        if let Err(source) = self.writer.write_all(bytes).await {
74            return Err(BuildFailure::poisoned(BuildError::Encoder(
75                EncodeError::Write { source },
76            )));
77        }
78        Ok(())
79    }
80}
81
82impl<W: AsyncWrite + Unpin> ArchiveBuilder for TarEncoder<W> {
83    type Error = EncodeError;
84
85    async fn finish_archive(&mut self) -> Result<(), BuildFailure<Self::Error>> {
86        self.write_bytes(end_marker_bytes()).await
87    }
88
89    async fn write_file_member(
90        &mut self,
91        path: &str,
92        payload: &mut FilePayload<'_>,
93        metadata: EntryMetadata,
94    ) -> Result<(), BuildFailure<Self::Error>> {
95        self.write_member(PaxMember {
96            path,
97            kind: UstarKind::Regular,
98            size: payload.size(),
99            link_path: None,
100            executable: metadata.is_executable(),
101        })
102        .await?;
103        while let Some(chunk) = payload.next_chunk().await.map_err(BuildFailure::poisoned)? {
104            self.write_bytes(chunk).await?;
105        }
106        let padding = payload_padding(payload.size());
107        if !padding.is_empty() {
108            self.write_bytes(padding).await?;
109        }
110        Ok(())
111    }
112
113    async fn write_directory_member(
114        &mut self,
115        path: &str,
116    ) -> Result<(), BuildFailure<Self::Error>> {
117        self.write_member(PaxMember {
118            path,
119            kind: UstarKind::Directory,
120            size: 0,
121            link_path: None,
122            executable: false,
123        })
124        .await
125    }
126
127    async fn write_symbolic_link_member(
128        &mut self,
129        path: &str,
130        target: &str,
131    ) -> Result<(), BuildFailure<Self::Error>> {
132        self.write_member(PaxMember {
133            path,
134            kind: UstarKind::SymbolicLink,
135            size: 0,
136            link_path: Some(target),
137            executable: false,
138        })
139        .await
140    }
141}
142
143/// A tar-specific failure while creating a pure-pax archive.
144#[derive(Debug, Error)]
145pub enum EncodeError {
146    /// A wire-format member could not be framed.
147    #[error(transparent)]
148    Framing(#[from] FramingWriteError),
149    /// Writing the output archive failed.
150    #[error("failed to write archive output")]
151    Write {
152        /// The underlying writer error.
153        #[source]
154        source: io::Error,
155    },
156    /// A tar sequence computation exceeded this API's range.
157    #[error("arithmetic overflow while computing {context}")]
158    ArithmeticOverflow {
159        /// The failed computation.
160        context: &'static str,
161    },
162}