Skip to main content

pijul_core/output/
archive.rs

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                    // Since jiff::Timestamp can represent negative values before the Unix epoch,
248                    // clamp all unrepresentable (negative) values to 0
249                    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, // unused
259                        &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}