1use 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
23pub struct TarEncoder<W> {
25 writer: W,
26 sequence: u64,
27 framing_buffer: Vec<u8>,
28}
29
30impl<W> TarEncoder<W> {
31 pub fn new(writer: W) -> Self {
33 Self {
34 writer,
35 sequence: 0,
36 framing_buffer: Vec::new(),
37 }
38 }
39
40 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#[derive(Debug, Error)]
145pub enum EncodeError {
146 #[error(transparent)]
148 Framing(#[from] FramingWriteError),
149 #[error("failed to write archive output")]
151 Write {
152 #[source]
154 source: io::Error,
155 },
156 #[error("arithmetic overflow while computing {context}")]
158 ArithmeticOverflow {
159 context: &'static str,
161 },
162}