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