1#[cfg(feature = "fs")]
5use std::{fs, io, path::Path};
6
7pub use heddle_object_model::name_encoding::*;
8
9#[cfg(feature = "fs")]
12pub fn write_name_entry(root: &Path, name: &str) -> io::Result<()> {
13 let relative = name_path(name);
14 if !is_digest_name_path(&relative) {
15 return Ok(());
16 }
17 let entry = root.join(&relative);
18 let identity = entry.join("name");
19 if identity.try_exists()? {
20 verify_name_entry(root, name)?;
21 } else {
22 crate::fs_atomic::write_file_atomic(&identity, name.as_bytes())?;
23 }
24 Ok(())
25}
26
27#[cfg(feature = "fs")]
30pub fn verify_name_entry(root: &Path, name: &str) -> io::Result<()> {
31 let relative = name_path(name);
32 if is_digest_name_path(&relative) && root.join(&relative).try_exists()? {
33 let stored = read_name_entry(root, &relative)?;
34 if stored.as_deref() != Some(name) {
35 return Err(io::Error::new(
36 io::ErrorKind::InvalidData,
37 "digest entry name does not match requested name",
38 ));
39 }
40 }
41 Ok(())
42}
43
44#[cfg(feature = "fs")]
47pub fn read_name_entry(root: &Path, relative: &Path) -> io::Result<Option<String>> {
48 if !is_digest_name_path(relative) {
49 return Ok(decode_name_path(relative));
50 }
51 let name = fs::read_to_string(root.join(relative).join("name"))?;
52 if name_path(&name) != relative {
53 return Err(io::Error::new(
54 io::ErrorKind::InvalidData,
55 "digest entry name does not match storage path",
56 ));
57 }
58 Ok(Some(name))
59}
60
61#[cfg(all(test, feature = "fs"))]
62mod tests {
63 use std::collections::HashMap;
64
65 use super::*;
66
67 #[test]
68 fn long_names_with_shared_digest_prefix_round_trip_and_verify() {
69 let root = tempfile::TempDir::new().expect("entries");
70 let mut prefixes = HashMap::new();
71 let (left, right) = (0..100_000)
72 .find_map(|index| {
73 let name = format!("{}{index}", "界".repeat(330));
74 let path = name_path(&name);
75 let digest = path
76 .components()
77 .next()
78 .expect("digest")
79 .as_os_str()
80 .to_str()
81 .expect("ASCII");
82 prefixes
83 .insert(digest[..6].to_owned(), name.clone())
84 .map(|other| (other, name))
85 })
86 .expect("16-bit digest prefix collision");
87 assert_ne!(name_path(&left), name_path(&right));
88 for name in [&left, &right] {
89 write_name_entry(root.path(), name).expect("identity write");
90 assert_eq!(
91 read_name_entry(root.path(), &name_path(name))
92 .expect("read")
93 .as_deref(),
94 Some(name.as_str())
95 );
96 verify_name_entry(root.path(), name).expect("verify");
97 }
98 fs::write(root.path().join(name_path(&left)).join("name"), &right)
99 .expect("corrupt identity");
100 assert!(verify_name_entry(root.path(), &left).is_err());
101 assert!(write_name_entry(root.path(), &left).is_err());
102 }
103}