Skip to main content

nix_daemon/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use std::{
4    io::{Error, Result},
5    sync::Arc,
6};
7
8use nix_compat::{
9    nix_daemon::{
10        NixDaemonIO,
11        types::{NarHash, UnkeyedValidPathInfo, ValidPathInfo},
12    },
13    nixbase32,
14    store_path::{StorePath, build_ca_path},
15};
16use snix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
17use snix_store::{nar::ingest_nar_and_hash, path_info::PathInfo, pathinfoservice::PathInfoService};
18use tracing::{instrument, warn};
19
20#[allow(dead_code)]
21pub struct SnixDaemon {
22    blob_service: Arc<dyn BlobService>,
23    directory_service: Arc<dyn DirectoryService>,
24    path_info_service: Arc<dyn PathInfoService>,
25}
26
27impl SnixDaemon {
28    pub fn new(
29        blob_service: Arc<dyn BlobService>,
30        directory_service: Arc<dyn DirectoryService>,
31        path_info_service: Arc<dyn PathInfoService>,
32    ) -> Self {
33        Self {
34            blob_service,
35            directory_service,
36            path_info_service,
37        }
38    }
39}
40
41/// Implements [NixDaemonIO] backed by snix services.
42impl NixDaemonIO for SnixDaemon {
43    #[instrument(skip_all, fields(path), level = "debug", ret(Debug))]
44    async fn query_path_info(
45        &self,
46        path: &StorePath<String>,
47    ) -> Result<Option<UnkeyedValidPathInfo>> {
48        if let Some(path_info) = self
49            .path_info_service
50            .get(*path.digest())
51            .await
52            .map_err(std::io::Error::other)?
53            && path_info.store_path.name() == path.name()
54        {
55            return Ok(Some(into_unkeyed_path_info(path_info)));
56        }
57        Ok(None)
58    }
59
60    #[instrument(skip_all, fields(hash=nix_compat::nixbase32::encode(hash)), level = "debug", ret(Debug))]
61    async fn query_path_from_hash_part(&self, hash: &[u8]) -> Result<Option<UnkeyedValidPathInfo>> {
62        let digest = hash
63            .try_into()
64            .map_err(|_| Error::other("invalid digest length"))?;
65        match self
66            .path_info_service
67            .get(digest)
68            .await
69            .map_err(std::io::Error::other)?
70        {
71            Some(path_info) => Ok(Some(into_unkeyed_path_info(path_info))),
72            None => Ok(None),
73        }
74    }
75
76    #[instrument(skip_all, fields(request), level = "debug", ret(Debug))]
77    async fn add_to_store_nar<R>(
78        &self,
79        info: ValidPathInfo,
80        reader: &mut R,
81        _repair: bool,
82        _dont_check_sigs: bool,
83    ) -> Result<()>
84    where
85        R: tokio::io::AsyncRead + Send + Unpin,
86    {
87        let (root_node, nar_sha256, nar_size) = ingest_nar_and_hash(
88            self.blob_service.clone(),
89            &self.directory_service,
90            reader,
91            &info.info.ca,
92        )
93        .await
94        .map_err(|e| Error::other(e.to_string()))?;
95
96        if nar_size != info.info.nar_size || nar_sha256 != *info.info.nar_hash {
97            warn!(
98                nar_hash.expected = nixbase32::encode(&*info.info.nar_hash),
99                nar_hash.actual = nixbase32::encode(&nar_sha256),
100                "nar hash mismatch"
101            );
102            return Err(Error::other(
103                "ingested nar ended up different from what was specified in the request",
104            ));
105        }
106
107        if let Some(cahash) = &info.info.ca {
108            let actual_path: StorePath<String> = build_ca_path(
109                info.path.name(),
110                cahash,
111                info.info.references.iter().map(|p| p.as_ref()),
112                false,
113            )
114            .map_err(Error::other)?;
115            if actual_path != info.path {
116                return Err(Error::other("path mismatch"));
117            }
118        }
119
120        let path_info = PathInfo {
121            store_path: info.path,
122            node: root_node,
123            references: info.info.references,
124            nar_size,
125            nar_sha256,
126            signatures: info.info.signatures,
127            deriver: info.info.deriver,
128            ca: info.info.ca,
129        };
130        self.path_info_service
131            .put(path_info)
132            .await
133            .map_err(|e| Error::other(e.to_string()))?;
134        Ok(())
135    }
136}
137
138// PathInfo lives in the snix-store crate, but does not depend on nix-compat's wire feature,
139// while UnkeyedValidPathInfo is only available if that feature is enabled. To avoid complexity
140// we manually convert as opposed to creating a From<PathInfo>.
141fn into_unkeyed_path_info(info: PathInfo) -> UnkeyedValidPathInfo {
142    UnkeyedValidPathInfo {
143        deriver: info.deriver,
144        nar_hash: NarHash::from_digest(info.nar_sha256),
145        references: info.references,
146        registration_time: 0,
147        nar_size: info.nar_size,
148        ultimate: false,
149        signatures: info.signatures,
150        ca: info.ca,
151    }
152}