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
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>(&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
132fn 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}