1use bstr::ByteSlice;
2use snix_castore::{
3 Node, blobservice::BlobService, directoryservice::DirectoryService, import::fs::ingest_path,
4};
5use std::path::Path;
6use tracing::{debug, instrument};
7
8use nix_compat::{
9 nixhash::{CAHash, NixHash},
10 store_path::{self, StorePath},
11};
12
13use crate::{
14 nar::NarCalculationService,
15 pathinfoservice::{PathInfo, PathInfoService},
16 proto::nar_info,
17};
18
19impl From<CAHash> for nar_info::Ca {
20 fn from(value: CAHash) -> Self {
21 let hash_type: nar_info::ca::Hash = (&value).into();
22 let digest: bytes::Bytes = value.hash().to_string().into();
23 nar_info::Ca {
24 r#type: hash_type.into(),
25 digest,
26 }
27 }
28}
29
30pub fn log_node(name: &[u8], node: &Node, path: &Path) {
31 match node {
32 Node::Directory { digest, .. } => {
33 debug!(
34 path = ?path,
35 name = %name.as_bstr(),
36 digest = %digest,
37 "import successful",
38 )
39 }
40 Node::File { digest, .. } => {
41 debug!(
42 path = ?path,
43 name = %name.as_bstr(),
44 digest = %digest,
45 "import successful"
46 )
47 }
48 Node::Symlink { target } => {
49 debug!(
50 path = ?path,
51 name = %name.as_bstr(),
52 target = ?target,
53 "import successful"
54 )
55 }
56 }
57}
58
59#[inline]
62pub fn path_to_name(path: &Path) -> std::io::Result<&str> {
63 path.file_name()
64 .and_then(|file_name| file_name.to_str())
65 .ok_or_else(|| {
66 std::io::Error::new(
67 std::io::ErrorKind::InvalidInput,
68 "path must not be .. and the basename valid unicode",
69 )
70 })
71}
72
73#[instrument(skip_all, fields(store_name=name, path=?path), err)]
78pub async fn import_path_as_nar_ca<BS, DS, PS, NS, P>(
79 path: P,
80 name: &str,
81 blob_service: BS,
82 directory_service: DS,
83 path_info_service: PS,
84 nar_calculation_service: NS,
85) -> Result<PathInfo, std::io::Error>
86where
87 P: AsRef<Path> + std::fmt::Debug,
88 BS: BlobService + Clone,
89 DS: DirectoryService,
90 PS: AsRef<dyn PathInfoService>,
91 NS: NarCalculationService,
92{
93 let root_node =
95 ingest_path::<_, _, _, &[u8]>(blob_service, directory_service, path.as_ref(), None)
96 .await
97 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
98
99 let (nar_size, nar_sha256) = nar_calculation_service.calculate_nar(&root_node).await?;
101
102 let ca = CAHash::Nar(NixHash::Sha256(nar_sha256));
103
104 let output_path: StorePath<String> =
108 store_path::build_ca_path(name, &ca, std::iter::empty::<&str>(), false).map_err(|_| {
109 std::io::Error::new(
110 std::io::ErrorKind::InvalidData,
111 format!("invalid name: {}", name),
112 )
113 })?;
114
115 Ok(path_info_service
117 .as_ref()
118 .put(PathInfo {
119 store_path: output_path.to_owned(),
120 node: root_node,
121 references: vec![],
123 nar_size,
124 nar_sha256,
125 signatures: vec![],
126 deriver: None,
127 ca: Some(ca),
128 })
129 .await?)
130}
131
132#[cfg(test)]
133mod tests {
134 use std::{ffi::OsStr, os::unix::ffi::OsStrExt, path::PathBuf};
135
136 use crate::import::path_to_name;
137 use rstest::rstest;
138
139 #[rstest]
140 #[case::simple_path("a/b/c", "c")]
141 #[case::simple_path_containing_dotdot("a/b/../c", "c")]
142 #[case::path_containing_multiple_dotdot("a/b/../c/d/../e", "e")]
143
144 fn test_path_to_name(#[case] path: &str, #[case] expected_name: &str) {
145 let path: PathBuf = path.into();
146 assert_eq!(path_to_name(&path).expect("must succeed"), expected_name);
147 }
148
149 #[rstest]
150 #[case::path_ending_in_dotdot(b"a/b/..")]
151 #[case::non_unicode_path(b"\xf8\xa1\xa1\xa1\xa1")]
152 fn test_invalid_path_to_name(#[case] invalid_path: &[u8]) {
153 let path: PathBuf = OsStr::from_bytes(invalid_path).into();
154 path_to_name(&path).expect_err("must fail");
155 }
156}