nix_compat/nar/writer/
async.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 [`AsyncWrite`], and content
7//! can be read from any type that implementes [`AsyncBufRead`].
8//!
9//! Writing a single file might look like this:
10//!
11//! ```rust
12//! # futures::executor::block_on(async {
13//! # use tokio::io::BufReader;
14//! # let some_file: Vec<u8> = vec![0, 1, 2, 3, 4];
15//!
16//! // Output location to write the NAR to.
17//! let mut sink: Vec<u8> = Vec::new();
18//!
19//! // Instantiate writer for this output location.
20//! let mut nar = nix_compat::nar::writer::r#async::open(&mut sink).await?;
21//!
22//! // Acquire metadata for the single file to output, and pass it in a
23//! // `BufRead`-implementing type.
24//!
25//! let executable = false;
26//! let size = some_file.len() as u64;
27//! let mut reader = BufReader::new(some_file.as_slice());
28//! nar.file(executable, size, &mut reader).await?;
29//! # Ok::<(), std::io::Error>(())
30//! # });
31//! ```
32
33use crate::nar::wire;
34use std::{
35    io::{
36        self,
37        ErrorKind::{InvalidInput, UnexpectedEof},
38    },
39    pin::Pin,
40};
41use tokio::io::{AsyncBufRead, AsyncBufReadExt, AsyncWrite, AsyncWriteExt};
42
43/// Convenience type alias for types implementing [`AsyncWrite`].
44pub type Writer<'a> = dyn AsyncWrite + Unpin + Send + 'a;
45
46/// Create a new NAR, writing the output to the specified writer.
47pub async fn open<'a, 'w: 'a>(writer: &'a mut Writer<'w>) -> io::Result<Node<'a, 'w>> {
48    let mut node = Node { writer };
49    node.write(&wire::TOK_NAR).await?;
50    Ok(node)
51}
52
53/// Single node in a NAR file.
54///
55/// A NAR can be thought of as a tree of nodes represented by this type. Each
56/// node can be a file, a symlink or a directory containing other nodes.
57pub struct Node<'a, 'w: 'a> {
58    writer: &'a mut Writer<'w>,
59}
60
61impl<'a, 'w> Node<'a, 'w> {
62    async fn write(&mut self, data: &[u8]) -> io::Result<()> {
63        self.writer.write_all(data).await
64    }
65
66    async fn pad(&mut self, n: u64) -> io::Result<()> {
67        match (n & 7) as usize {
68            0 => Ok(()),
69            n => self.write(&[0; 8][n..]).await,
70        }
71    }
72
73    /// Make this node a symlink.
74    pub async fn symlink(mut self, target: &[u8]) -> io::Result<()> {
75        debug_assert!(
76            target.len() <= wire::MAX_TARGET_LEN,
77            "target.len() > {}",
78            wire::MAX_TARGET_LEN
79        );
80        debug_assert!(!target.is_empty(), "target is empty");
81        debug_assert!(!target.contains(&0), "target contains null byte");
82
83        self.write(&wire::TOK_SYM).await?;
84        self.write(&target.len().to_le_bytes()).await?;
85        self.write(target).await?;
86        self.pad(target.len() as u64).await?;
87        self.write(&wire::TOK_PAR).await?;
88        Ok(())
89    }
90
91    /// Make this node a single file.
92    pub async fn file(
93        mut self,
94        executable: bool,
95        size: u64,
96        reader: &mut (dyn AsyncBufRead + Unpin + Send),
97    ) -> io::Result<()> {
98        self.write(if executable {
99            &wire::TOK_EXE
100        } else {
101            &wire::TOK_REG
102        })
103        .await?;
104
105        self.write(&size.to_le_bytes()).await?;
106
107        let mut need = size;
108        while need != 0 {
109            let data = reader.fill_buf().await?;
110
111            if data.is_empty() {
112                return Err(UnexpectedEof.into());
113            }
114
115            let n = need.min(data.len() as u64) as usize;
116            self.write(&data[..n]).await?;
117
118            need -= n as u64;
119            Pin::new(&mut *reader).consume(n);
120        }
121
122        // bail if there's still data left in the passed reader.
123        // This uses the same code as [BufRead::has_data_left] (unstable).
124        if reader.fill_buf().await.map(|b| !b.is_empty())? {
125            return Err(io::Error::new(
126                InvalidInput,
127                "reader contained more data than specified size",
128            ));
129        }
130
131        self.pad(size).await?;
132        self.write(&wire::TOK_PAR).await?;
133
134        Ok(())
135    }
136
137    /// Make this node a directory, the content of which is set using the
138    /// resulting [`Directory`] value.
139    ///
140    /// It is the caller's responsibility to invoke [`Directory::close`],
141    /// or invalid archives will be produced silently.
142    pub async fn directory(mut self) -> io::Result<Directory<'a, 'w>> {
143        self.write(&wire::TOK_DIR).await?;
144        Ok(Directory::new(self))
145    }
146}
147
148#[cfg(debug_assertions)]
149type Name = Vec<u8>;
150#[cfg(not(debug_assertions))]
151type Name = ();
152
153fn into_name(_name: &[u8]) -> Name {
154    #[cfg(debug_assertions)]
155    _name.to_owned()
156}
157
158/// Content of a NAR node that represents a directory.
159pub struct Directory<'a, 'w> {
160    node: Node<'a, 'w>,
161    prev_name: Option<Name>,
162}
163
164impl<'a, 'w> Directory<'a, 'w> {
165    fn new(node: Node<'a, 'w>) -> Self {
166        Self {
167            node,
168            prev_name: None,
169        }
170    }
171
172    /// Add an entry to the directory.
173    ///
174    /// The entry is simply another [`Node`], which can then be filled like the
175    /// root of a NAR (including, of course, by nesting directories).
176    ///
177    /// It is the caller's responsibility to ensure that directory entries are
178    /// written in order of ascending name. If this is not ensured, this method
179    /// may panic or silently produce invalid archives.
180    pub async fn entry(&mut self, name: &[u8]) -> io::Result<Node<'_, 'w>> {
181        debug_assert!(
182            name.len() <= wire::MAX_NAME_LEN,
183            "name.len() > {}",
184            wire::MAX_NAME_LEN
185        );
186        debug_assert!(!name.is_empty(), "name is empty");
187        debug_assert!(!name.contains(&0), "name contains null byte");
188        debug_assert!(!name.contains(&b'/'), "name contains {:?}", '/');
189        debug_assert!(name != b".", "name == {:?}", ".");
190        debug_assert!(name != b"..", "name == {:?}", "..");
191
192        match self.prev_name {
193            None => {
194                self.prev_name = Some(into_name(name));
195            }
196            Some(ref mut _prev_name) => {
197                #[cfg(debug_assertions)]
198                {
199                    use bstr::ByteSlice;
200                    assert!(
201                        &**_prev_name < name,
202                        "misordered names: {:?} >= {:?}",
203                        _prev_name.as_bstr(),
204                        name.as_bstr()
205                    );
206                    name.clone_into(_prev_name);
207                }
208                self.node.write(&wire::TOK_PAR).await?;
209            }
210        }
211
212        self.node.write(&wire::TOK_ENT).await?;
213        self.node.write(&name.len().to_le_bytes()).await?;
214        self.node.write(name).await?;
215        self.node.pad(name.len() as u64).await?;
216        self.node.write(&wire::TOK_NOD).await?;
217
218        Ok(Node {
219            writer: &mut *self.node.writer,
220        })
221    }
222
223    /// Close a directory and write terminators for the directory to the NAR.
224    ///
225    /// **Important:** This *must* be called when all entries have been written
226    /// in a directory, otherwise the resulting NAR file will be invalid.
227    pub async fn close(mut self) -> io::Result<()> {
228        if self.prev_name.is_some() {
229            self.node.write(&wire::TOK_PAR).await?;
230        }
231
232        self.node.write(&wire::TOK_PAR).await?;
233        Ok(())
234    }
235}