nix_compat/nar/writer/
sync.rs

1//! Implements an interface for writing the Nix archive format (NAR).
2//!
3//! NAR files (and their hashed representations) are used in C++ Nix for
4//! addressing fixed-output derivations and a variety of other things.
5//!
6//! NAR files can be output to any type that implements [`Write`], and content
7//! can be read from any type that implementes [`BufRead`].
8//!
9//! Writing a single file might look like this:
10//!
11//! ```rust
12//! # use std::io::BufReader;
13//! # let some_file: Vec<u8> = vec![0, 1, 2, 3, 4];
14//!
15//! // Output location to write the NAR to.
16//! let mut sink: Vec<u8> = Vec::new();
17//!
18//! // Instantiate writer for this output location.
19//! let mut nar = nix_compat::nar::writer::open(&mut sink)?;
20//!
21//! // Acquire metadata for the single file to output, and pass it in a
22//! // `BufRead`-implementing type.
23//!
24//! let executable = false;
25//! let size = some_file.len() as u64;
26//! let mut reader = BufReader::new(some_file.as_slice());
27//! nar.file(executable, size, &mut reader)?;
28//! # Ok::<(), std::io::Error>(())
29//! ```
30
31use crate::nar::wire;
32use std::io::{
33    self, BufRead,
34    ErrorKind::{InvalidInput, UnexpectedEof},
35    Write,
36};
37
38/// Create a new NAR, writing the output to the specified writer.
39pub fn open<W: Write>(writer: &mut W) -> io::Result<Node<W>> {
40    let mut node = Node { writer };
41    node.write(&wire::TOK_NAR)?;
42    Ok(node)
43}
44
45/// Single node in a NAR file.
46///
47/// A NAR can be thought of as a tree of nodes represented by this type. Each
48/// node can be a file, a symlink or a directory containing other nodes.
49pub struct Node<'a, W: Write> {
50    writer: &'a mut W,
51}
52
53impl<'a, W: Write> Node<'a, W> {
54    fn write(&mut self, data: &[u8]) -> io::Result<()> {
55        self.writer.write_all(data)
56    }
57
58    fn pad(&mut self, n: u64) -> io::Result<()> {
59        match (n & 7) as usize {
60            0 => Ok(()),
61            n => self.write(&[0; 8][n..]),
62        }
63    }
64
65    /// Make this node a symlink.
66    pub fn symlink(mut self, target: &[u8]) -> io::Result<()> {
67        debug_assert!(
68            target.len() <= wire::MAX_TARGET_LEN,
69            "target.len() > {}",
70            wire::MAX_TARGET_LEN
71        );
72        debug_assert!(!target.is_empty(), "target is empty");
73        debug_assert!(!target.contains(&0), "target contains null byte");
74
75        self.write(&wire::TOK_SYM)?;
76        self.write(&target.len().to_le_bytes())?;
77        self.write(target)?;
78        self.pad(target.len() as u64)?;
79        self.write(&wire::TOK_PAR)?;
80        Ok(())
81    }
82
83    /// Make this node a single file.
84    pub fn file(mut self, executable: bool, size: u64, reader: &mut dyn BufRead) -> io::Result<()> {
85        self.write(if executable {
86            &wire::TOK_EXE
87        } else {
88            &wire::TOK_REG
89        })?;
90
91        self.write(&size.to_le_bytes())?;
92
93        let mut need = size;
94        while need != 0 {
95            let data = reader.fill_buf()?;
96
97            if data.is_empty() {
98                return Err(UnexpectedEof.into());
99            }
100
101            let n = need.min(data.len() as u64) as usize;
102            self.write(&data[..n])?;
103
104            need -= n as u64;
105            reader.consume(n);
106        }
107
108        // bail if there's still data left in the passed reader.
109        // This uses the same code as [BufRead::has_data_left] (unstable).
110        if reader.fill_buf().map(|b| !b.is_empty())? {
111            return Err(io::Error::new(
112                InvalidInput,
113                "reader contained more data than specified size",
114            ));
115        }
116
117        self.pad(size)?;
118        self.write(&wire::TOK_PAR)?;
119
120        Ok(())
121    }
122
123    /// Make this node a single file but let the user handle the writing of the file contents.
124    /// The user gets access to a writer to write the file contents to, plus a struct they must
125    /// invoke a function on to finish writing the NAR file.
126    ///
127    /// It is the caller's responsibility to write the correct number of bytes to the writer and
128    /// invoke [`FileManualWrite::close`], or invalid archives will be produced silently.
129    ///
130    /// ```rust
131    /// # use std::io::BufReader;
132    /// # use std::io::Write;
133    /// #
134    /// # // Output location to write the NAR to.
135    /// # let mut sink: Vec<u8> = Vec::new();
136    /// #
137    /// # // Instantiate writer for this output location.
138    /// # let mut nar = nix_compat::nar::writer::open(&mut sink)?;
139    /// #
140    /// let contents = "Hello world\n".as_bytes();
141    /// let size = contents.len() as u64;
142    /// let executable = false;
143    ///
144    /// let (writer, skip) = nar
145    ///     .file_manual_write(executable, size)?;
146    ///
147    /// // Write the contents
148    /// writer.write_all(&contents)?;
149    ///
150    /// // Close the file node
151    /// skip.close(writer)?;
152    /// # Ok::<(), std::io::Error>(())
153    /// ```
154    pub fn file_manual_write(
155        mut self,
156        executable: bool,
157        size: u64,
158    ) -> io::Result<(&'a mut W, FileManualWrite)> {
159        self.write(if executable {
160            &wire::TOK_EXE
161        } else {
162            &wire::TOK_REG
163        })?;
164
165        self.write(&size.to_le_bytes())?;
166
167        Ok((self.writer, FileManualWrite { size }))
168    }
169
170    /// Make this node a directory, the content of which is set using the
171    /// resulting [`Directory`] value.
172    ///
173    /// It is the caller's responsibility to invoke [`Directory::close`],
174    /// or invalid archives will be produced silently.
175    pub fn directory(mut self) -> io::Result<Directory<'a, W>> {
176        self.write(&wire::TOK_DIR)?;
177        Ok(Directory::new(self))
178    }
179}
180
181#[cfg(debug_assertions)]
182type Name = Vec<u8>;
183#[cfg(not(debug_assertions))]
184type Name = ();
185
186fn into_name(_name: &[u8]) -> Name {
187    #[cfg(debug_assertions)]
188    _name.to_owned()
189}
190
191/// Content of a NAR node that represents a directory.
192pub struct Directory<'a, W: Write> {
193    node: Node<'a, W>,
194    prev_name: Option<Name>,
195}
196
197impl<'a, W: Write> Directory<'a, W> {
198    fn new(node: Node<'a, W>) -> Self {
199        Self {
200            node,
201            prev_name: None,
202        }
203    }
204
205    /// Add an entry to the directory.
206    ///
207    /// The entry is simply another [`Node`], which can then be filled like the
208    /// root of a NAR (including, of course, by nesting directories).
209    ///
210    /// It is the caller's responsibility to ensure that directory entries are
211    /// written in order of ascending name. If this is not ensured, this method
212    /// may panic or silently produce invalid archives.
213    pub fn entry(&mut self, name: &[u8]) -> io::Result<Node<'_, W>> {
214        debug_assert!(
215            name.len() <= wire::MAX_NAME_LEN,
216            "name.len() > {}",
217            wire::MAX_NAME_LEN
218        );
219        debug_assert!(!name.is_empty(), "name is empty");
220        debug_assert!(!name.contains(&0), "name contains null byte");
221        debug_assert!(!name.contains(&b'/'), "name contains {:?}", '/');
222        debug_assert!(name != b".", "name == {:?}", ".");
223        debug_assert!(name != b"..", "name == {:?}", "..");
224
225        match self.prev_name {
226            None => {
227                self.prev_name = Some(into_name(name));
228            }
229            Some(ref mut _prev_name) => {
230                #[cfg(debug_assertions)]
231                {
232                    use bstr::ByteSlice;
233                    assert!(
234                        &**_prev_name < name,
235                        "misordered names: {:?} >= {:?}",
236                        _prev_name.as_bstr(),
237                        name.as_bstr()
238                    );
239                    name.clone_into(_prev_name);
240                }
241                self.node.write(&wire::TOK_PAR)?;
242            }
243        }
244
245        self.node.write(&wire::TOK_ENT)?;
246        self.node.write(&name.len().to_le_bytes())?;
247        self.node.write(name)?;
248        self.node.pad(name.len() as u64)?;
249        self.node.write(&wire::TOK_NOD)?;
250
251        Ok(Node {
252            writer: &mut *self.node.writer,
253        })
254    }
255
256    /// Close a directory and write terminators for the directory to the NAR.
257    ///
258    /// **Important:** This *must* be called when all entries have been written
259    /// in a directory, otherwise the resulting NAR file will be invalid.
260    pub fn close(mut self) -> io::Result<()> {
261        if self.prev_name.is_some() {
262            self.node.write(&wire::TOK_PAR)?;
263        }
264
265        self.node.write(&wire::TOK_PAR)?;
266        Ok(())
267    }
268}
269
270/// Content of a NAR node that represents a file whose contents are being written out manually.
271/// Returned by the `file_manual_write` function.
272#[must_use]
273pub struct FileManualWrite {
274    size: u64,
275}
276
277impl FileManualWrite {
278    /// Finish writing the file structure to the NAR after having manually written the file contents.
279    ///
280    /// **Important:** This *must* be called with the writer returned by file_manual_write after
281    /// the file contents have been manually and fully written. Otherwise the resulting NAR file
282    /// will be invalid.
283    pub fn close<W: Write>(self, writer: &mut W) -> io::Result<()> {
284        let mut node = Node { writer };
285        node.pad(self.size)?;
286        node.write(&wire::TOK_PAR)?;
287        Ok(())
288    }
289}