Skip to main content

snix_store/nar/renderer/
simple.rs

1use nix_compat::nar::writer::r#async as nar_writer;
2use snix_castore::{Node, blobservice::BlobService, directoryservice::DirectoryService};
3use tokio::io::{self, AsyncWrite, BufReader};
4
5use crate::nar::RenderError;
6
7/// Accepts a [Node] pointing to the root of a (store) path,
8/// and uses the passed blob_service and directory_service to perform the
9/// necessary lookups as it traverses the structure.
10/// The contents in NAR serialization are writen to the passed [AsyncWrite].
11///
12/// This function is very linear, so roundtrip times add up quickly.
13/// You might want to use [crate::nar::write_nar] instead, which opens more
14/// blobs concurrently.
15pub async fn write_nar<W, BS, DS>(
16    mut w: W,
17    root_node: &Node,
18    blob_service: BS,
19    directory_service: DS,
20) -> Result<(), RenderError>
21where
22    W: AsyncWrite + Unpin + Send,
23    BS: BlobService,
24    DS: DirectoryService,
25{
26    // Initialize NAR writer
27    let nar_root_node = nar_writer::open(&mut w)
28        .await
29        .map_err(RenderError::NARWriterError)?;
30
31    walk_node(
32        nar_root_node,
33        root_node,
34        b"",
35        blob_service,
36        directory_service,
37    )
38    .await?;
39
40    Ok(())
41}
42
43/// Process an intermediate node in the structure.
44/// This consumes the node.
45async fn walk_node<BS, DS>(
46    nar_node: nar_writer::Node<'_, '_>,
47    castore_node: &Node,
48    name: &[u8],
49    blob_service: BS,
50    directory_service: DS,
51) -> Result<(BS, DS), RenderError>
52where
53    BS: BlobService + Send,
54    DS: DirectoryService + Send,
55{
56    match castore_node {
57        Node::Symlink { target, .. } => {
58            nar_node
59                .symlink(target.as_ref())
60                .await
61                .map_err(RenderError::NARWriterError)?;
62        }
63        Node::File {
64            digest,
65            size,
66            executable,
67        } => {
68            let mut blob_reader = match blob_service
69                .open_read(digest)
70                .await
71                .map_err(RenderError::BlobService)?
72            {
73                Some(blob_reader) => Ok(BufReader::new(blob_reader)),
74                None => Err(RenderError::NARWriterError(io::Error::new(
75                    io::ErrorKind::NotFound,
76                    format!("blob with digest {} not found", &digest),
77                ))),
78            }?;
79
80            nar_node
81                .file(*executable, *size, &mut blob_reader)
82                .await
83                .map_err(|err| match err.kind() {
84                    io::ErrorKind::UnexpectedEof => {
85                        io::Error::new(io::ErrorKind::InvalidData, "blob short read")
86                    }
87                    io::ErrorKind::InvalidInput => io::Error::new(
88                        io::ErrorKind::InvalidData,
89                        "blob continued to yield data beyond end",
90                    ),
91                    _ => err,
92                })
93                .map_err(RenderError::NARWriterError)?;
94        }
95        Node::Directory { digest, .. } => {
96            // look it up with the directory service
97            let directory = directory_service
98                .get(digest)
99                .await
100                .map_err(RenderError::DirectoryService)?
101                .ok_or_else(|| {
102                    RenderError::DirectoryNotFound(*digest, bytes::Bytes::copy_from_slice(name))
103                })?;
104
105            // start a directory node
106            let mut nar_node_directory = nar_node
107                .directory()
108                .await
109                .map_err(RenderError::NARWriterError)?;
110
111            // We put blob_service, directory_service back here whenever we come up from
112            // the recursion.
113            let mut blob_service = blob_service;
114            let mut directory_service = directory_service;
115
116            // for each node in the directory, create a new entry with its name,
117            // and then recurse on that entry.
118            for (name, node) in directory.nodes() {
119                let child_node = nar_node_directory
120                    .entry(name.as_ref())
121                    .await
122                    .map_err(RenderError::NARWriterError)?;
123
124                (blob_service, directory_service) = Box::pin(walk_node(
125                    child_node,
126                    node,
127                    name.as_ref(),
128                    blob_service,
129                    directory_service,
130                ))
131                .await?;
132            }
133
134            // close the directory
135            nar_node_directory
136                .close()
137                .await
138                .map_err(RenderError::NARWriterError)?;
139
140            return Ok((blob_service, directory_service));
141        }
142    }
143
144    Ok((blob_service, directory_service))
145}