snix_store/nar/
renderer.rs

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