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::{AddToStoreNarRequest, UnkeyedValidPathInfo},
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>(&self, request: AddToStoreNarRequest, reader: &mut R) -> Result<()>
78    where
79        R: tokio::io::AsyncRead + Send + Unpin,
80    {
81        let (root_node, nar_sha256, nar_size) = ingest_nar_and_hash(
82            self.blob_service.clone(),
83            &self.directory_service,
84            reader,
85            &request.ca,
86        )
87        .await
88        .map_err(|e| Error::other(e.to_string()))?;
89
90        if nar_size != request.nar_size || nar_sha256 != *request.nar_hash {
91            warn!(
92                nar_hash.expected = nixbase32::encode(&*request.nar_hash),
93                nar_hash.actual = nixbase32::encode(&nar_sha256),
94                "nar hash mismatch"
95            );
96            return Err(Error::other(
97                "ingested nar ended up different from what was specified in the request",
98            ));
99        }
100
101        if let Some(cahash) = &request.ca {
102            let actual_path: StorePath<String> = build_ca_path(
103                request.path.name(),
104                cahash,
105                request.references.iter().map(|p| p.to_absolute_path()),
106                false,
107            )
108            .map_err(Error::other)?;
109            if actual_path != request.path {
110                return Err(Error::other("path mismatch"));
111            }
112        }
113
114        let path_info = PathInfo {
115            store_path: request.path,
116            node: root_node,
117            references: request.references,
118            nar_size,
119            nar_sha256,
120            signatures: request.signatures,
121            deriver: request.deriver,
122            ca: request.ca,
123        };
124        self.path_info_service
125            .put(path_info)
126            .await
127            .map_err(|e| Error::other(e.to_string()))?;
128        Ok(())
129    }
130}
131
132// PathInfo lives in the snix-store crate, but does not depend on nix-compat's wire feature,
133// while UnkeyedValidPathInfo is only available if that feature is enabled. To avoid complexity
134// we manually convert as opposed to creating a From<PathInfo>.
135fn into_unkeyed_path_info(info: PathInfo) -> UnkeyedValidPathInfo {
136    UnkeyedValidPathInfo {
137        deriver: info.deriver,
138        nar_hash: nixbase32::encode(&info.nar_sha256),
139        references: info.references,
140        registration_time: 0,
141        nar_size: info.nar_size,
142        ultimate: false,
143        signatures: info.signatures,
144        ca: info.ca,
145    }
146}