nix_daemon/
lib.rs

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