Skip to main content

nix_compat/nix_daemon/
mod.rs

1pub mod worker_protocol;
2
3use std::{collections::BTreeMap, io::Result};
4
5use tokio::io::{AsyncBufRead, AsyncRead};
6use tracing::warn;
7use types::{QueryMissingResult, QueryValidPaths, UnkeyedValidPathInfo, ValidPathInfo};
8
9use crate::{
10    derivation::OutputName,
11    derived_path::DerivedPath,
12    nix_daemon::types::{BuildMode, KeyedBuildResult},
13    store_path::StorePath,
14};
15
16pub mod framing;
17pub mod handler;
18pub mod types;
19
20/// Represents all possible operations over the nix-daemon protocol.
21#[cfg_attr(test, mockall::automock)]
22pub trait NixDaemonIO: Sync {
23    fn is_valid_path(
24        &self,
25        path: &StorePath,
26    ) -> impl std::future::Future<Output = Result<bool>> + Send {
27        async move { Ok(self.query_path_info(path).await?.is_some()) }
28    }
29
30    fn ensure_path(
31        &self,
32        path: &StorePath,
33    ) -> impl std::future::Future<Output = Result<()>> + Send {
34        async move {
35            if self.is_valid_path(path).await? {
36                Ok(())
37            } else {
38                Err(std::io::Error::other(format!("unknown path {}", path)))
39            }
40        }
41    }
42
43    fn query_path_info(
44        &self,
45        path: &StorePath,
46    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
47
48    fn query_path_from_hash_part(
49        &self,
50        hash: &[u8],
51    ) -> impl std::future::Future<Output = Result<Option<UnkeyedValidPathInfo>>> + Send;
52
53    fn query_valid_paths(
54        &self,
55        request: &QueryValidPaths,
56    ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
57        async move {
58            if request.substitute {
59                warn!("snix does not yet support substitution, ignoring the 'substitute' flag...");
60            }
61
62            let mut results: Vec<StorePath> = Vec::with_capacity(request.paths.len());
63
64            for path in request.paths.iter() {
65                if self.is_valid_path(path).await? {
66                    results.push(path.clone());
67                }
68            }
69
70            Ok(results)
71        }
72    }
73
74    fn query_valid_derivers(
75        &self,
76        path: &StorePath,
77    ) -> impl std::future::Future<Output = Result<Vec<StorePath>>> + Send {
78        async move {
79            let result = self.query_path_info(path).await?;
80            let result: Vec<_> = result.into_iter().filter_map(|info| info.deriver).collect();
81            Ok(result)
82        }
83    }
84
85    fn query_missing(
86        &self,
87        derived_paths: Vec<DerivedPath>,
88    ) -> impl std::future::Future<Output = Result<QueryMissingResult>> + Send;
89
90    fn query_derivation_output_map(
91        &self,
92        drv_path: &StorePath,
93    ) -> impl std::future::Future<Output = Result<BTreeMap<OutputName, Option<StorePath>>>> + Send;
94
95    #[cfg_attr(test, mockall::concretize)]
96    fn add_to_store_nar<R>(
97        &self,
98        info: ValidPathInfo,
99        reader: &mut R,
100        repair: bool,
101        dont_check_sigs: bool,
102    ) -> impl std::future::Future<Output = Result<()>> + Send
103    where
104        R: AsyncRead + Send + Unpin;
105
106    fn nar_from_path(
107        &self,
108        path: &StorePath,
109    ) -> impl std::future::Future<Output = Result<Box<dyn AsyncBufRead + Unpin + Send>>> + Send;
110
111    fn build_paths(
112        &self,
113        derived_paths: Vec<DerivedPath>,
114        mode: BuildMode,
115    ) -> impl std::future::Future<Output = Result<()>> + Send;
116
117    fn build_paths_with_results(
118        &self,
119        derived_paths: Vec<DerivedPath>,
120        mode: BuildMode,
121    ) -> impl std::future::Future<Output = Result<Vec<KeyedBuildResult>>> + Send;
122}
123
124#[cfg(test)]
125mod tests {
126
127    use std::collections::BTreeMap;
128
129    use crate::{
130        derivation::OutputName,
131        derived_path::DerivedPath,
132        nix_daemon::types::{NarHash, QueryValidPaths},
133        store_path::StorePath,
134    };
135
136    use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
137
138    // Very simple mock
139    // Unable to use mockall as it does not support unboxed async traits.
140    pub struct MockNixDaemonIO {
141        query_path_info_result: Option<UnkeyedValidPathInfo>,
142    }
143
144    impl NixDaemonIO for MockNixDaemonIO {
145        async fn query_path_info(
146            &self,
147            _path: &StorePath,
148        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
149            Ok(self.query_path_info_result.clone())
150        }
151
152        async fn query_path_from_hash_part(
153            &self,
154            _hash: &[u8],
155        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
156            Ok(None)
157        }
158
159        async fn add_to_store_nar<R>(
160            &self,
161            _info: super::types::ValidPathInfo,
162            _reader: &mut R,
163            _repair: bool,
164            _dont_check_sigs: bool,
165        ) -> std::io::Result<()>
166        where
167            R: tokio::io::AsyncRead + Send + Unpin,
168        {
169            Ok(())
170        }
171
172        async fn nar_from_path(
173            &self,
174            _path: &StorePath,
175        ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
176            Err(std::io::Error::other(
177                "Operation NarFromPath is not implemented",
178            ))
179        }
180
181        async fn build_paths(
182            &self,
183            _derived_paths: Vec<DerivedPath>,
184            _mode: super::types::BuildMode,
185        ) -> std::io::Result<()> {
186            Ok(())
187        }
188
189        async fn build_paths_with_results(
190            &self,
191            _derived_paths: Vec<DerivedPath>,
192            _mode: super::types::BuildMode,
193        ) -> std::io::Result<Vec<super::types::KeyedBuildResult>> {
194            Err(std::io::Error::other(
195                "Operation BuildPathsWithResults is not implemented",
196            ))
197        }
198
199        async fn query_missing(
200            &self,
201            _derived_paths: Vec<DerivedPath>,
202        ) -> std::io::Result<super::types::QueryMissingResult> {
203            Err(std::io::Error::other(
204                "Operation QueryMissing is not implemented",
205            ))
206        }
207
208        async fn query_derivation_output_map(
209            &self,
210            _drv_path: &StorePath,
211        ) -> std::io::Result<BTreeMap<OutputName, Option<StorePath>>> {
212            Err(std::io::Error::other(
213                "Operation QueryDerivationOutputMap is not implemented",
214            ))
215        }
216    }
217
218    #[tokio::test]
219    async fn test_is_valid_path_returns_true() {
220        let path =
221            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
222        let io = MockNixDaemonIO {
223            query_path_info_result: Some(UnkeyedValidPathInfo {
224                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
225                nar_hash: NarHash::from_digest([0u8; 32]),
226                references: Vec::new(),
227                registration_time: 0,
228                nar_size: 0,
229                ultimate: true,
230                signatures: Vec::new(),
231                ca: None,
232            }),
233        };
234
235        let result = io
236            .is_valid_path(&path)
237            .await
238            .expect("expected to get a non-empty response");
239        assert!(result, "expected to get true");
240    }
241
242    #[tokio::test]
243    async fn test_is_valid_path_returns_false() {
244        let path =
245            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
246        let io = MockNixDaemonIO {
247            query_path_info_result: None,
248        };
249
250        let result = io
251            .is_valid_path(&path)
252            .await
253            .expect("expected to get a non-empty response");
254        assert!(!result, "expected to get false");
255    }
256
257    #[tokio::test]
258    async fn test_query_valid_paths_returns_empty_response() {
259        let path =
260            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
261        let io = MockNixDaemonIO {
262            query_path_info_result: None,
263        };
264
265        let result = io
266            .query_valid_paths(&QueryValidPaths {
267                paths: vec![path],
268                substitute: false,
269            })
270            .await
271            .expect("expected to get a non-empty response");
272        assert_eq!(result, vec![], "expected to get empty response");
273    }
274
275    #[tokio::test]
276    async fn test_query_valid_paths_returns_non_empty_response() {
277        let path =
278            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
279        let io = MockNixDaemonIO {
280            query_path_info_result: Some(UnkeyedValidPathInfo {
281                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
282                nar_hash: NarHash::from_digest([0u8; 32]),
283                references: Vec::new(),
284                registration_time: 0,
285                nar_size: 0,
286                ultimate: true,
287                signatures: Vec::new(),
288                ca: None,
289            }),
290        };
291
292        let result = io
293            .query_valid_paths(&QueryValidPaths {
294                paths: vec![path.clone()],
295                substitute: false,
296            })
297            .await
298            .expect("expected to get a non-empty response");
299        assert_eq!(result, vec![path], "expected to get non empty response");
300    }
301
302    #[tokio::test]
303    async fn test_query_valid_derivers_returns_empty_response() {
304        let path =
305            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
306        let io = MockNixDaemonIO {
307            query_path_info_result: None,
308        };
309
310        let result = io
311            .query_valid_derivers(&path)
312            .await
313            .expect("expected to get a non-empty response");
314        assert_eq!(result, vec![], "expected to get empty response");
315    }
316
317    #[tokio::test]
318    async fn test_query_valid_derivers_returns_non_empty_response() {
319        let path =
320            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
321        let deriver =
322            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes()).unwrap();
323        let io = MockNixDaemonIO {
324            query_path_info_result: Some(UnkeyedValidPathInfo {
325                deriver: Some(deriver.clone()),
326                nar_hash: NarHash::from_digest([0u8; 32]),
327                references: vec![],
328                registration_time: 0,
329                nar_size: 1,
330                ultimate: true,
331                signatures: vec![],
332                ca: None,
333            }),
334        };
335
336        let result = io
337            .query_valid_derivers(&path)
338            .await
339            .expect("expected to get a non-empty response");
340        assert_eq!(result, vec![deriver], "expected to get non empty response");
341    }
342}