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
51impl 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 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
204fn 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}