Skip to main content

nix_daemon/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::{
4    collections::BTreeMap,
5    io::{Error, Result},
6    sync::Arc,
7};
8
9use nix_compat::{
10    derivation::OutputName,
11    derived_path::DerivedPath,
12    nix_daemon::{
13        NixDaemonIO,
14        types::{
15            BuildMode, KeyedBuildResult, NarHash, QueryMissingResult, UnkeyedValidPathInfo,
16            ValidPathInfo,
17        },
18    },
19    nixbase32,
20    nixhash::CAHashMode,
21    store_path::{StorePath, build_ca_path},
22};
23use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
24use snix_store::{nar::ingest_nar_and_hash, path_info::PathInfo, pathinfoservice::PathInfoService};
25use tokio::io::BufReader;
26use tracing::{instrument, warn};
27
28const NAR_BUF_SIZE: usize = 8 * 1024;
29
30#[allow(dead_code)]
31pub struct SnixDaemon {
32    blob_service: Arc<dyn BlobService>,
33    directory_service: Arc<dyn DirectoryService>,
34    path_info_service: Arc<dyn PathInfoService>,
35}
36
37impl SnixDaemon {
38    pub fn new(
39        blob_service: Arc<dyn BlobService>,
40        directory_service: Arc<dyn DirectoryService>,
41        path_info_service: Arc<dyn PathInfoService>,
42    ) -> Self {
43        Self {
44            blob_service,
45            directory_service,
46            path_info_service,
47        }
48    }
49}
50
51/// Implements [NixDaemonIO] backed by snix services.
52impl NixDaemonIO for SnixDaemon {
53    #[instrument(skip_all, fields(path), level = "debug", ret(Debug))]
54    async fn query_path_info(&self, path: &StorePath) -> Result<Option<UnkeyedValidPathInfo>> {
55        if let Some(path_info) = self
56            .path_info_service
57            .get(*path.digest())
58            .await
59            .map_err(std::io::Error::other)?
60            && path_info.store_path.name() == path.name()
61        {
62            return Ok(Some(into_unkeyed_path_info(path_info)));
63        }
64        Ok(None)
65    }
66
67    #[instrument(skip_all, fields(hash=nix_compat::nixbase32::encode(hash)), level = "debug", ret(Debug))]
68    async fn query_path_from_hash_part(&self, hash: &[u8]) -> Result<Option<UnkeyedValidPathInfo>> {
69        let digest = hash
70            .try_into()
71            .map_err(|_| Error::other("invalid digest length"))?;
72        match self
73            .path_info_service
74            .get(digest)
75            .await
76            .map_err(std::io::Error::other)?
77        {
78            Some(path_info) => Ok(Some(into_unkeyed_path_info(path_info))),
79            None => Ok(None),
80        }
81    }
82
83    #[instrument(skip_all, fields(request), level = "debug", ret(Debug))]
84    async fn add_to_store_nar<R>(
85        &self,
86        info: ValidPathInfo,
87        reader: &mut R,
88        _repair: bool,
89        _dont_check_sigs: bool,
90    ) -> Result<()>
91    where
92        R: tokio::io::AsyncRead + Send + Unpin,
93    {
94        let (root_node, nar_sha256, nar_size) = ingest_nar_and_hash(
95            self.blob_service.clone(),
96            &self.directory_service,
97            reader,
98            &info.info.ca,
99        )
100        .await
101        .map_err(|e| Error::other(e.to_string()))?;
102
103        if nar_size != info.info.nar_size || nar_sha256 != *info.info.nar_hash {
104            warn!(
105                nar_hash.expected = nixbase32::encode(&*info.info.nar_hash),
106                nar_hash.actual = nixbase32::encode(&nar_sha256),
107                "nar hash mismatch"
108            );
109            return Err(Error::other(
110                "ingested nar ended up different from what was specified in the request",
111            ));
112        }
113
114        if let Some(cahash) = &info.info.ca {
115            let actual_path = build_ca_path(
116                info.path.name(),
117                cahash.mode() == CAHashMode::Nar,
118                &cahash.hash(),
119                info.info.references.iter().map(|p| p.as_ref()),
120                false,
121            )
122            .map_err(Error::other)?;
123
124            if actual_path != info.path.as_ref() {
125                return Err(Error::other("path mismatch"));
126            }
127        }
128
129        let path_info = PathInfo {
130            store_path: info.path,
131            node: root_node,
132            references: info.info.references,
133            nar_size,
134            nar_sha256,
135            signatures: info.info.signatures,
136            deriver: info.info.deriver,
137            ca: info.info.ca,
138        };
139        self.path_info_service
140            .put(path_info)
141            .await
142            .map_err(|e| Error::other(e.to_string()))?;
143        Ok(())
144    }
145
146    async fn nar_from_path(
147        &self,
148        path: &StorePath,
149    ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
150        let path_info = self
151            .path_info_service
152            .get(*path.digest())
153            .await
154            .map_err(std::io::Error::other)?
155            .ok_or_else(|| std::io::Error::other("unknown store path"))?;
156
157        let (r, w) = tokio::io::simplex(NAR_BUF_SIZE);
158        let r = BufReader::new(r);
159        let blob_service = self.blob_service.clone();
160        let directory_service = self.directory_service.clone();
161
162        // spawn a task rendering the NAR to the client.
163        tokio::spawn(async move {
164            if let Err(e) =
165                snix_store::nar::write_nar(w, &path_info.node, &blob_service, &directory_service)
166                    .await
167            {
168                warn!(err=%e, "failed to write out NAR");
169            }
170        });
171        Ok(Box::new(r))
172    }
173
174    async fn build_paths(&self, _derived_paths: Vec<DerivedPath>, _mode: BuildMode) -> Result<()> {
175        Ok(())
176    }
177
178    async fn build_paths_with_results(
179        &self,
180        _derived_paths: Vec<DerivedPath>,
181        _mode: BuildMode,
182    ) -> Result<Vec<KeyedBuildResult>> {
183        Err(std::io::Error::other(
184            "Operation BuildPathsWithResults is not implemented",
185        ))
186    }
187
188    async fn query_missing(&self, _derived_paths: Vec<DerivedPath>) -> Result<QueryMissingResult> {
189        Err(std::io::Error::other(
190            "Operation QueryMissing is not implemented",
191        ))
192    }
193
194    async fn query_derivation_output_map(
195        &self,
196        _drv_path: &StorePath,
197    ) -> Result<BTreeMap<OutputName, Option<StorePath>>> {
198        Err(std::io::Error::other(
199            "Operation QueryDerivationOutputMap is not implemented",
200        ))
201    }
202}
203
204// PathInfo lives in the snix-store crate, but does not depend on nix-compat's wire feature,
205// while UnkeyedValidPathInfo is only available if that feature is enabled. To avoid complexity
206// we manually convert as opposed to creating a From<PathInfo>.
207fn into_unkeyed_path_info(info: PathInfo) -> UnkeyedValidPathInfo {
208    UnkeyedValidPathInfo {
209        deriver: info.deriver,
210        nar_hash: NarHash::from_digest(info.nar_sha256),
211        references: info.references,
212        registration_time: 0,
213        nar_size: info.nar_size,
214        ultimate: false,
215        signatures: info.signatures,
216        ca: info.ca,
217    }
218}