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, directory_service: Arc, path_info_service: Arc, } impl TvixDaemon { pub fn new( blob_service: Arc, directory_service: Arc, path_info_service: Arc, ) -> 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, ) -> Result> { 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> { 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(&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 = 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. 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, } }