1use super::*;
2use crate::Conflict;
3use crate::changestore::ChangeStore;
4use crate::{HashMap, HashSet};
5use std::collections::hash_map::Entry;
6
7pub trait Archive {
8 type File: std::io::Write;
9 type Error: std::error::Error;
10 fn create_file(&mut self, path: &str, mtime: u64, perm: u16) -> Self::File;
11 fn create_dir(&mut self, path: &str, mtime: u64, permissions: u16) -> Result<(), Self::Error>;
12 fn close_file(&mut self, f: Self::File) -> Result<(), Self::Error>;
13}
14
15#[cfg(feature = "tarball")]
16pub struct Tarball<W: std::io::Write> {
17 pub archive: tar::Builder<flate2::write::GzEncoder<W>>,
18 pub prefix: Option<String>,
19 pub buffer: Vec<u8>,
20 pub umask: u16,
21}
22
23#[cfg(feature = "tarball")]
24pub struct File {
25 buf: Vec<u8>,
26 path: String,
27 permissions: u16,
28 mtime: u64,
29}
30
31#[cfg(feature = "tarball")]
32impl std::io::Write for File {
33 fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
34 self.buf.write(buf)
35 }
36 fn flush(&mut self) -> Result<(), std::io::Error> {
37 Ok(())
38 }
39}
40
41#[cfg(feature = "tarball")]
42impl<W: std::io::Write> Tarball<W> {
43 pub fn new(w: W, prefix: Option<String>, umask: u16) -> Self {
44 let encoder = flate2::write::GzEncoder::new(w, flate2::Compression::best());
45 Tarball {
46 archive: tar::Builder::new(encoder),
47 buffer: Vec::new(),
48 prefix,
49 umask,
50 }
51 }
52}
53
54#[cfg(feature = "tarball")]
55impl<W: std::io::Write> Archive for Tarball<W> {
56 type File = File;
57 type Error = std::io::Error;
58 fn create_file(&mut self, path: &str, mtime: u64, permissions: u16) -> Self::File {
59 self.buffer.clear();
60 File {
61 buf: std::mem::take(&mut self.buffer),
62 path: if let Some(ref prefix) = self.prefix {
63 prefix.clone() + path
64 } else {
65 path.to_string()
66 },
67 mtime,
68 permissions: permissions & !self.umask,
69 }
70 }
71 fn create_dir(&mut self, path: &str, mtime: u64, permissions: u16) -> Result<(), Self::Error> {
72 let mut header = tar::Header::new_gnu();
73 header.set_mode((permissions & !self.umask) as u32);
74 header.set_mtime(mtime);
75 header.set_entry_type(tar::EntryType::Directory);
76 if let Some(ref prefix) = self.prefix {
77 let path = prefix.clone() + path;
78 self.archive.append_data(&mut header, &path, &[][..])?;
79 } else {
80 self.archive.append_data(&mut header, path, &[][..])?;
81 }
82 Ok(())
83 }
84
85 fn close_file(&mut self, file: Self::File) -> Result<(), Self::Error> {
86 let mut header = tar::Header::new_gnu();
87 header.set_size(file.buf.len() as u64);
88 header.set_mode(file.permissions as u32);
89 header.set_mtime(file.mtime);
90 header.set_cksum();
91 self.archive
92 .append_data(&mut header, &file.path, &file.buf[..])?;
93 self.buffer = file.buf;
94 Ok(())
95 }
96}
97
98#[derive(Error)]
99pub enum ArchiveError<
100 P: std::error::Error + 'static,
101 T: GraphTxnT + TreeTxnT,
102 A: std::error::Error + 'static,
103> {
104 #[error(transparent)]
105 A(A),
106 #[error(transparent)]
107 P(P),
108 #[error(transparent)]
109 Txn(#[from] TxnErr<T::GraphError>),
110 #[error(transparent)]
111 Tree(#[from] TreeErr<T::TreeError>),
112 #[error(transparent)]
113 Unrecord(#[from] crate::unrecord::UnrecordError<P, T>),
114 #[error(transparent)]
115 Apply(#[from] crate::apply::ApplyError<P, T>),
116 #[error("State not found: {:?}", state)]
117 StateNotFound { state: Box<crate::pristine::Merkle> },
118 #[error(transparent)]
119 File(#[from] crate::output::FileError<P, T>),
120 #[error(transparent)]
121 Output(#[from] crate::output::PristineOutputError<P, T>),
122}
123
124impl<P: std::error::Error + 'static, T: GraphTxnT + TreeTxnT, A: std::error::Error + 'static>
125 std::fmt::Debug for ArchiveError<P, T, A>
126{
127 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
128 match self {
129 ArchiveError::A(e) => std::fmt::Debug::fmt(e, fmt),
130 ArchiveError::P(e) => std::fmt::Debug::fmt(e, fmt),
131 ArchiveError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
132 ArchiveError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
133 ArchiveError::Unrecord(e) => std::fmt::Debug::fmt(e, fmt),
134 ArchiveError::Apply(e) => std::fmt::Debug::fmt(e, fmt),
135 ArchiveError::File(e) => std::fmt::Debug::fmt(e, fmt),
136 ArchiveError::Output(e) => std::fmt::Debug::fmt(e, fmt),
137 ArchiveError::StateNotFound { state } => write!(fmt, "State not found: {:?}", state),
138 }
139 }
140}
141
142pub(crate) fn archive<
143 'a,
144 T: ChannelTxnT + TreeTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
145 P: ChangeStore,
146 I: Iterator<Item = &'a str>,
147 A: Archive,
148>(
149 changes: &P,
150 txn: &ArcTxn<T>,
151 channel: &ChannelRef<T>,
152 prefix: &mut I,
153 arch: &mut A,
154) -> Result<Vec<Conflict>, ArchiveError<P::Error, T, A::Error>> {
155 let mut conflicts = Vec::new();
156 let mut files = HashMap::default();
157 let mut next_files = HashMap::default();
158 let mut next_prefix_basename = prefix.next();
159 {
160 let txn_ = txn.read();
161 let channel_ = channel.read();
162 collect_children(
163 &*txn_,
164 changes,
165 txn_.graph(&channel_),
166 Position::ROOT,
167 Inode::ROOT,
168 "",
169 None,
170 next_prefix_basename,
171 &mut files,
172 )?;
173 }
174 let mut done: HashMap<_, (Vertex<ChangeId>, String)> = HashMap::default();
175 let mut done_inodes = HashSet::default();
176 while !files.is_empty() {
177 debug!("files {:?}", files.len());
178 next_files.clear();
179 next_prefix_basename = prefix.next();
180
181 for (a, mut b) in files.drain() {
182 debug!("files: {:?} {:?}", a, b);
183 {
184 let txn_ = txn.read();
185 let channel_ = channel.read();
186 b.sort_by(|u, v| {
187 txn_.get_changeset(txn_.changes(&channel_), &u.0.change)
188 .unwrap()
189 .cmp(
190 &txn_
191 .get_changeset(txn_.changes(&channel_), &v.0.change)
192 .unwrap(),
193 )
194 });
195 }
196 let mut is_first_name = None;
197 for (name_key, mut output_item) in b {
198 let txn_ = txn.read();
199 let channel_ = channel.read();
200 let name_entry = match done.entry(output_item.pos) {
201 Entry::Occupied(e) => {
202 debug!("pos already visited: {:?} {:?}", a, output_item.pos);
203 if e.get().0 != name_key {
204 conflicts.push(Conflict::MultipleNames {
205 changes: Vec::new(),
206 pos: [output_item.pos],
207 path: e.get().1.clone(),
208 names: Vec::new(),
209 });
210 }
211 continue;
212 }
213 Entry::Vacant(e) => e,
214 };
215 if !done_inodes.insert(output_item.pos) {
216 debug!("inode already visited: {:?} {:?}", a, output_item.pos);
217 continue;
218 }
219 let name = if let Some(inode) = is_first_name {
220 let inodes = vec![inode, output_item.pos];
221 let txn = txn.read();
222 conflicts.push(Conflict::Name {
223 path: a.to_string(),
224 changes: inodes
225 .iter()
226 .map(|i| txn.get_external(&i.change).unwrap().into())
227 .collect(),
228 inodes,
229 });
230 break;
231 } else {
232 is_first_name = Some(output_item.pos);
233 a.clone()
234 };
235 let file_name = path::file_name(&name).unwrap();
236 path::push(&mut output_item.path, file_name);
237
238 name_entry.insert((name_key, output_item.path.clone()));
239
240 let path = std::mem::take(&mut output_item.path);
241 let (_, latest_touch) =
242 crate::fs::get_latest_touch(&*txn_, &channel_, &output_item.pos)?;
243 let latest_touch = {
244 let ext = txn_.get_external(&latest_touch).optional()?.unwrap();
245 let c = changes.get_header(&ext.into()).map_err(ArchiveError::P)?;
246
247 u64::try_from(c.timestamp.as_second()).unwrap_or(0)
250 };
251 if output_item.meta.is_dir() {
252 let len = next_files.len();
253 collect_children(
254 &*txn_,
255 changes,
256 txn_.graph(&channel_),
257 output_item.pos,
258 Inode::ROOT, &path,
260 None,
261 next_prefix_basename,
262 &mut next_files,
263 )?;
264 if len == next_files.len() {
265 arch.create_dir(&path, latest_touch, 0o777)
266 .map_err(ArchiveError::A)?;
267 }
268 } else {
269 debug!("latest_touch: {:?}", latest_touch);
270 let mut l = crate::alive::retrieve(
271 &*txn_,
272 txn_.graph(&channel_),
273 output_item.pos,
274 false,
275 )?;
276 let perms = if output_item.meta.permissions() & 0o100 != 0 {
277 0o777
278 } else {
279 0o666
280 };
281 let mut f = arch.create_file(&path, latest_touch, perms);
282 {
283 let mut f = crate::vertex_buffer::ConflictsWriter::new(
284 &mut f,
285 &output_item.path,
286 output_item.pos,
287 &mut conflicts,
288 );
289 std::mem::drop(channel_);
290 std::mem::drop(txn_);
291 crate::alive::output_graph(
292 changes,
293 txn,
294 channel,
295 &mut f,
296 &mut l,
297 &mut Vec::new(),
298 )?;
299 }
300 arch.close_file(f).map_err(ArchiveError::A)?;
301 }
302 if let Some(id) = output_item.is_zombie {
303 conflicts.push(Conflict::ZombieFile {
304 path: name.to_string(),
305 changes: id,
306 inode: [output_item.pos],
307 })
308 }
309 }
310 }
311 std::mem::swap(&mut files, &mut next_files);
312 }
313 Ok(conflicts)
314}