about summary refs log tree commit diff
path: root/tvix/nix-daemon/src/lib.rs
blob: 9a69ced1e5ebdc673dee051b79b5d7264af52bae (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use std::{
    io::{Error, Result},
    sync::Arc,
};

use nix_compat::{
    nix_daemon::{
        types::{AddToStoreNarRequest, UnkeyedValidPathInfo},
        NixDaemonIO,
    },
    nixbase32,
    store_path::{build_ca_path, StorePath},
};
use tracing::warn;
use tvix_castore::{blobservice::BlobService, directoryservice::DirectoryService};
use tvix_store::{nar::ingest_nar_and_hash, path_info::PathInfo, pathinfoservice::PathInfoService};

#[allow(dead_code)]
pub struct TvixDaemon {
    blob_service: Arc<dyn BlobService>,
    directory_service: Arc<dyn DirectoryService>,
    path_info_service: Arc<dyn PathInfoService>,
}

impl TvixDaemon {
    pub fn new(
        blob_service: Arc<dyn BlobService>,
        directory_service: Arc<dyn DirectoryService>,
        path_info_service: Arc<dyn PathInfoService>,
    ) -> Self {
        Self {
            blob_service,
            directory_service,
            path_info_service,
        }
    }
}

/// Implements [NixDaemonIO] backed by tvix services.
impl NixDaemonIO for TvixDaemon {
    async fn query_path_info(
        &self,
        path: &StorePath<String>,
    ) -> Result<Option<UnkeyedValidPathInfo>> {
        match self.path_info_service.get(*path.digest()).await? {
            Some(path_info) => {
                if path_info.store_path.name() == path.name() {
                    Ok(Some(into_unkeyed_path_info(path_info)))
                } else {
                    Ok(None)
                }
            }
            None => Ok(None),
        }
    }

    async fn query_path_from_hash_part(&self, hash: &[u8]) -> Result<Option<UnkeyedValidPathInfo>> {
        let digest = hash
            .try_into()
            .map_err(|_| Error::other("invalid digest length"))?;
        match self.path_info_service.get(digest).await? {
            Some(path_info) => Ok(Some(into_unkeyed_path_info(path_info))),
            None => Ok(None),
        }
    }

    async fn add_to_store_nar<R>(&self, request: AddToStoreNarRequest, reader: &mut R) -> Result<()>
    where
        R: tokio::io::AsyncRead + Send + Unpin,
    {
        let (root_node, nar_sha256, nar_size) = ingest_nar_and_hash(
            self.blob_service.clone(),
            self.directory_service.clone(),
            reader,
            &request.ca,
        )
        .await
        .map_err(|e| Error::other(e.to_string()))?;

        if nar_size != request.nar_size || nar_sha256 != *request.nar_hash {
            warn!(
                nar_hash.expected = nixbase32::encode(&*request.nar_hash),
                nar_hash.actual = nixbase32::encode(&nar_sha256),
                "nar hash mismatch"
            );
            return Err(Error::other(
                "ingested nar ended up different from what was specified in the request",
            ));
        }

        if let Some(cahash) = &request.ca {
            let actual_path: StorePath<String> = build_ca_path(
                request.path.name(),
                cahash,
                request.references.iter().map(|p| p.to_absolute_path()),
                false,
            )
            .map_err(Error::other)?;
            if actual_path != request.path {
                return Err(Error::other("path mismatch"));
            }
        }

        let path_info = PathInfo {
            store_path: request.path,
            node: root_node,
            references: request.references,
            nar_size,
            nar_sha256,
            signatures: request.signatures,
            deriver: request.deriver,
            ca: request.ca,
        };
        self.path_info_service
            .put(path_info)
            .await
            .map_err(|e| Error::other(e.to_string()))?;
        Ok(())
    }
}

// PathInfo lives in the tvix-store crate, but does not depend on nix-compat's wire feature,
// while UnkeyedValidPathInfo is only available if that feature is enabled. To avoid complexity
// we manually convert as opposed to creating a From<PathInfo>.
fn into_unkeyed_path_info(info: PathInfo) -> UnkeyedValidPathInfo {
    UnkeyedValidPathInfo {
        deriver: info.deriver,
        nar_hash: nixbase32::encode(&info.nar_sha256),
        references: info.references,
        registration_time: 0,
        nar_size: info.nar_size,
        ultimate: false,
        signatures: info.signatures,
        ca: info.ca,
    }
}