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}