snix_store/nar/
renderer.rs

1use crate::{pathinfoservice, utils::AsyncIoBridge};
2
3use super::{NarCalculationService, RenderError};
4use count_write::CountWrite;
5use nix_compat::nar::writer::r#async as nar_writer;
6use sha2::{Digest, Sha256};
7use snix_castore::{Node, blobservice::BlobService, directoryservice::DirectoryService};
8use tokio::io::{self, AsyncWrite, BufReader};
9use tonic::async_trait;
10use tracing::instrument;
11
12pub struct SimpleRenderer<BS, DS> {
13    blob_service: BS,
14    directory_service: DS,
15}
16
17impl<BS, DS> SimpleRenderer<BS, DS> {
18    pub fn new(blob_service: BS, directory_service: DS) -> Self {
19        Self {
20            blob_service,
21            directory_service,
22        }
23    }
24}
25
26#[async_trait]
27impl<BS, DS> NarCalculationService for SimpleRenderer<BS, DS>
28where
29    BS: BlobService + Clone,
30    DS: DirectoryService + Clone,
31{
32    async fn calculate_nar(
33        &self,
34        root_node: &Node,
35    ) -> Result<(u64, [u8; 32]), pathinfoservice::Error> {
36        Ok(calculate_size_and_sha256(
37            root_node,
38            self.blob_service.clone(),
39            self.directory_service.clone(),
40        )
41        .await?)
42    }
43}
44
45/// Invoke [write_nar], and return the size and sha256 digest of the produced
46/// NAR output.
47#[instrument(skip_all)]
48pub async fn calculate_size_and_sha256<BS, DS>(
49    root_node: &Node,
50    blob_service: BS,
51    directory_service: DS,
52) -> Result<(u64, [u8; 32]), RenderError>
53where
54    BS: BlobService + Send,
55    DS: DirectoryService + Send,
56{
57    let mut h = Sha256::new();
58    let mut cw = CountWrite::from(&mut h);
59
60    write_nar(
61        // The hasher doesn't speak async. It doesn't
62        // actually do any I/O, so it's fine to wrap.
63        AsyncIoBridge(&mut cw),
64        root_node,
65        blob_service,
66        directory_service,
67    )
68    .await?;
69
70    Ok((cw.count(), h.finalize().into()))
71}
72
73/// Accepts a [Node] pointing to the root of a (store) path,
74/// and uses the passed blob_service and directory_service to perform the
75/// necessary lookups as it traverses the structure.
76/// The contents in NAR serialization are writen to the passed [AsyncWrite].
77pub async fn write_nar<W, BS, DS>(
78    mut w: W,
79    root_node: &Node,
80    blob_service: BS,
81    directory_service: DS,
82) -> Result<(), RenderError>
83where
84    W: AsyncWrite + Unpin + Send,
85    BS: BlobService + Send,
86    DS: DirectoryService + Send,
87{
88    // Initialize NAR writer
89    let nar_root_node = nar_writer::open(&mut w)
90        .await
91        .map_err(RenderError::NARWriterError)?;
92
93    walk_node(
94        nar_root_node,
95        root_node,
96        b"",
97        blob_service,
98        directory_service,
99    )
100    .await?;
101
102    Ok(())
103}
104
105/// Process an intermediate node in the structure.
106/// This consumes the node.
107async fn walk_node<BS, DS>(
108    nar_node: nar_writer::Node<'_, '_>,
109    castore_node: &Node,
110    name: &[u8],
111    blob_service: BS,
112    directory_service: DS,
113) -> Result<(BS, DS), RenderError>
114where
115    BS: BlobService + Send,
116    DS: DirectoryService + Send,
117{
118    match castore_node {
119        Node::Symlink { target, .. } => {
120            nar_node
121                .symlink(target.as_ref())
122                .await
123                .map_err(RenderError::NARWriterError)?;
124        }
125        Node::File {
126            digest,
127            size,
128            executable,
129        } => {
130            let mut blob_reader = match blob_service
131                .open_read(digest)
132                .await
133                .map_err(RenderError::BlobService)?
134            {
135                Some(blob_reader) => Ok(BufReader::new(blob_reader)),
136                None => Err(RenderError::NARWriterError(io::Error::new(
137                    io::ErrorKind::NotFound,
138                    format!("blob with digest {} not found", &digest),
139                ))),
140            }?;
141
142            nar_node
143                .file(*executable, *size, &mut blob_reader)
144                .await
145                .map_err(RenderError::NARWriterError)?;
146        }
147        Node::Directory { digest, .. } => {
148            // look it up with the directory service
149            match directory_service
150                .get(digest)
151                .await
152                .map_err(RenderError::DirectoryService)?
153            {
154                // if it's None, that's an error!
155                None => Err(RenderError::DirectoryNotFound(
156                    *digest,
157                    bytes::Bytes::copy_from_slice(name),
158                ))?,
159                Some(directory) => {
160                    // start a directory node
161                    let mut nar_node_directory = nar_node
162                        .directory()
163                        .await
164                        .map_err(RenderError::NARWriterError)?;
165
166                    // We put blob_service, directory_service back here whenever we come up from
167                    // the recursion.
168                    let mut blob_service = blob_service;
169                    let mut directory_service = directory_service;
170
171                    // for each node in the directory, create a new entry with its name,
172                    // and then recurse on that entry.
173                    for (name, node) in directory.nodes() {
174                        let child_node = nar_node_directory
175                            .entry(name.as_ref())
176                            .await
177                            .map_err(RenderError::NARWriterError)?;
178
179                        (blob_service, directory_service) = Box::pin(walk_node(
180                            child_node,
181                            node,
182                            name.as_ref(),
183                            blob_service,
184                            directory_service,
185                        ))
186                        .await?;
187                    }
188
189                    // close the directory
190                    nar_node_directory
191                        .close()
192                        .await
193                        .map_err(RenderError::NARWriterError)?;
194
195                    return Ok((blob_service, directory_service));
196                }
197            }
198        }
199    }
200
201    Ok((blob_service, directory_service))
202}