1use 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
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
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#[derive(Debug, Error)]
137pub enum EncodeError {
138 #[error(transparent)]
140 Framing(#[from] FramingWriteError),
141 #[error("failed to write archive output")]
143 Write {
144 #[source]
146 source: io::Error,
147 },
148 #[error("arithmetic overflow while computing {context}")]
150 ArithmeticOverflow {
151 context: &'static str,
153 },
154}