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
39impl 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
121fn 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}