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, UnverifiedDerivation},
11    derived_path::DerivedPath,
12    nix_daemon::types::{BuildMode, BuildResult, 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    fn build_derivation(
124        &self,
125        drv_path: StorePath,
126        derivation: UnverifiedDerivation,
127        mode: BuildMode,
128    ) -> impl std::future::Future<Output = Result<BuildResult>> + Send;
129}
130
131#[cfg(test)]
132mod tests {
133
134    use std::collections::BTreeMap;
135
136    use crate::{
137        derivation::OutputName,
138        derived_path::DerivedPath,
139        nix_daemon::types::{NarHash, QueryValidPaths},
140        store_path::StorePath,
141    };
142
143    use super::{NixDaemonIO, types::UnkeyedValidPathInfo};
144
145    // Very simple mock
146    // Unable to use mockall as it does not support unboxed async traits.
147    pub struct MockNixDaemonIO {
148        query_path_info_result: Option<UnkeyedValidPathInfo>,
149    }
150
151    impl NixDaemonIO for MockNixDaemonIO {
152        async fn query_path_info(
153            &self,
154            _path: &StorePath,
155        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
156            Ok(self.query_path_info_result.clone())
157        }
158
159        async fn query_path_from_hash_part(
160            &self,
161            _hash: &[u8],
162        ) -> std::io::Result<Option<UnkeyedValidPathInfo>> {
163            Ok(None)
164        }
165
166        async fn add_to_store_nar<R>(
167            &self,
168            _info: super::types::ValidPathInfo,
169            _reader: &mut R,
170            _repair: bool,
171            _dont_check_sigs: bool,
172        ) -> std::io::Result<()>
173        where
174            R: tokio::io::AsyncRead + Send + Unpin,
175        {
176            Ok(())
177        }
178
179        async fn nar_from_path(
180            &self,
181            _path: &StorePath,
182        ) -> std::io::Result<Box<dyn tokio::io::AsyncBufRead + Unpin + Send>> {
183            Err(std::io::Error::other(
184                "Operation NarFromPath is not implemented",
185            ))
186        }
187
188        async fn build_paths(
189            &self,
190            _derived_paths: Vec<DerivedPath>,
191            _mode: super::types::BuildMode,
192        ) -> std::io::Result<()> {
193            Ok(())
194        }
195
196        async fn build_paths_with_results(
197            &self,
198            _derived_paths: Vec<DerivedPath>,
199            _mode: super::types::BuildMode,
200        ) -> std::io::Result<Vec<super::types::KeyedBuildResult>> {
201            Err(std::io::Error::other(
202                "Operation BuildPathsWithResults is not implemented",
203            ))
204        }
205
206        async fn build_derivation(
207            &self,
208            _drv_path: StorePath,
209            _derivation: crate::derivation::UnverifiedDerivation,
210            _mode: super::types::BuildMode,
211        ) -> std::io::Result<super::types::BuildResult> {
212            Err(std::io::Error::other(
213                "Operation BuildDerivation is not implemented",
214            ))
215        }
216
217        async fn query_missing(
218            &self,
219            _derived_paths: Vec<DerivedPath>,
220        ) -> std::io::Result<super::types::QueryMissingResult> {
221            Err(std::io::Error::other(
222                "Operation QueryMissing is not implemented",
223            ))
224        }
225
226        async fn query_derivation_output_map(
227            &self,
228            _drv_path: &StorePath,
229        ) -> std::io::Result<BTreeMap<OutputName, Option<StorePath>>> {
230            Err(std::io::Error::other(
231                "Operation QueryDerivationOutputMap is not implemented",
232            ))
233        }
234    }
235
236    #[tokio::test]
237    async fn test_is_valid_path_returns_true() {
238        let path =
239            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
240        let io = MockNixDaemonIO {
241            query_path_info_result: Some(UnkeyedValidPathInfo {
242                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
243                nar_hash: NarHash::from_digest([0u8; 32]),
244                references: Vec::new(),
245                registration_time: 0,
246                nar_size: 0,
247                ultimate: true,
248                signatures: Vec::new(),
249                ca: None,
250            }),
251        };
252
253        let result = io
254            .is_valid_path(&path)
255            .await
256            .expect("expected to get a non-empty response");
257        assert!(result, "expected to get true");
258    }
259
260    #[tokio::test]
261    async fn test_is_valid_path_returns_false() {
262        let path =
263            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
264        let io = MockNixDaemonIO {
265            query_path_info_result: None,
266        };
267
268        let result = io
269            .is_valid_path(&path)
270            .await
271            .expect("expected to get a non-empty response");
272        assert!(!result, "expected to get false");
273    }
274
275    #[tokio::test]
276    async fn test_query_valid_paths_returns_empty_response() {
277        let path =
278            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
279        let io = MockNixDaemonIO {
280            query_path_info_result: None,
281        };
282
283        let result = io
284            .query_valid_paths(&QueryValidPaths {
285                paths: vec![path],
286                substitute: false,
287            })
288            .await
289            .expect("expected to get a non-empty response");
290        assert_eq!(result, vec![], "expected to get empty response");
291    }
292
293    #[tokio::test]
294    async fn test_query_valid_paths_returns_non_empty_response() {
295        let path =
296            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
297        let io = MockNixDaemonIO {
298            query_path_info_result: Some(UnkeyedValidPathInfo {
299                deriver: Some("00000000000000000000000000000000-_.drv".parse().unwrap()),
300                nar_hash: NarHash::from_digest([0u8; 32]),
301                references: Vec::new(),
302                registration_time: 0,
303                nar_size: 0,
304                ultimate: true,
305                signatures: Vec::new(),
306                ca: None,
307            }),
308        };
309
310        let result = io
311            .query_valid_paths(&QueryValidPaths {
312                paths: vec![path.clone()],
313                substitute: false,
314            })
315            .await
316            .expect("expected to get a non-empty response");
317        assert_eq!(result, vec![path], "expected to get non empty response");
318    }
319
320    #[tokio::test]
321    async fn test_query_valid_derivers_returns_empty_response() {
322        let path =
323            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
324        let io = MockNixDaemonIO {
325            query_path_info_result: None,
326        };
327
328        let result = io
329            .query_valid_derivers(&path)
330            .await
331            .expect("expected to get a non-empty response");
332        assert_eq!(result, vec![], "expected to get empty response");
333    }
334
335    #[tokio::test]
336    async fn test_query_valid_derivers_returns_non_empty_response() {
337        let path =
338            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello".as_bytes()).unwrap();
339        let deriver =
340            StorePath::from_bytes("z6r3bn5l51679pwkvh9nalp6c317z34m-hello.drv".as_bytes()).unwrap();
341        let io = MockNixDaemonIO {
342            query_path_info_result: Some(UnkeyedValidPathInfo {
343                deriver: Some(deriver.clone()),
344                nar_hash: NarHash::from_digest([0u8; 32]),
345                references: vec![],
346                registration_time: 0,
347                nar_size: 1,
348                ultimate: true,
349                signatures: vec![],
350                ca: None,
351            }),
352        };
353
354        let result = io
355            .query_valid_derivers(&path)
356            .await
357            .expect("expected to get a non-empty response");
358        assert_eq!(result, vec![deriver], "expected to get non empty response");
359    }
360}