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
41impl 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
138fn 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}