Skip to main content

haematite_cli/serve/
runtime.rs

1//! Private runtime endpoints derived from the operating system and store name.
2
3use std::fmt;
4use std::fs::{self, DirBuilder, Metadata, OpenOptions};
5use std::io;
6use std::os::unix::ffi::OsStrExt;
7use std::os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt, PermissionsExt};
8use std::os::unix::net::SocketAddr;
9use std::path::{Component, Path, PathBuf};
10use std::sync::atomic::{AtomicU64, Ordering};
11
12use sha2::{Digest, Sha256};
13
14static PROBES: AtomicU64 = AtomicU64::new(0);
15
16#[derive(Debug)]
17pub struct Refusal {
18    pub code: &'static str,
19    detail: String,
20}
21
22impl Refusal {
23    fn new(code: &'static str, detail: impl fmt::Display) -> Self {
24        Self {
25            code,
26            detail: detail.to_string(),
27        }
28    }
29}
30
31impl fmt::Display for Refusal {
32    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
33        write!(formatter, "{}: {}", self.code, self.detail)
34    }
35}
36
37impl std::error::Error for Refusal {}
38
39#[derive(Debug)]
40pub struct Endpoint {
41    pub name: String,
42    pub socket: PathBuf,
43    pub data_dir: PathBuf,
44}
45
46impl Endpoint {
47    /// Refuses a created store whose canonical location changed during creation.
48    ///
49    /// # Errors
50    /// Returns `data_dir_changed` if the created directory cannot be checked or changed location.
51    pub fn check_created(&self) -> Result<(), Refusal> {
52        let actual = fs::canonicalize(&self.data_dir)
53            .map_err(|error| Refusal::new("data_dir_changed", error))?;
54        let metadata =
55            fs::metadata(&actual).map_err(|error| Refusal::new("data_dir_changed", error))?;
56        if actual != self.data_dir || !metadata.is_dir() {
57            return Err(Refusal::new(
58                "data_dir_changed",
59                "created store location changed",
60            ));
61        }
62        Ok(())
63    }
64}
65
66/// Resolves one endpoint without opening a store or binding a socket.
67///
68/// # Errors
69/// Names invalid store names, data locations, runtime locations and endpoints,
70/// unavailable OS queries, uid-probe failures and probe-cleanup failures.
71pub fn resolve(
72    data_dir: &Path,
73    name: Option<&str>,
74    socket: Option<&Path>,
75    create: bool,
76) -> Result<Endpoint, Refusal> {
77    resolve_with(data_dir, name, socket, create, os_root, probe_uid)
78}
79
80fn resolve_with(
81    data_dir: &Path,
82    name: Option<&str>,
83    socket: Option<&Path>,
84    create: bool,
85    root: impl FnOnce() -> Result<PathBuf, Refusal>,
86    uid: impl FnOnce(&Path) -> Result<u32, Refusal>,
87) -> Result<Endpoint, Refusal> {
88    if let Some(name) = name {
89        validate_name(name)?;
90    }
91    let data_dir = canonical_data(data_dir, create)?;
92    let name = match name {
93        Some(name) => name.to_owned(),
94        None => data_dir
95            .file_name()
96            .and_then(|name| name.to_str())
97            .ok_or_else(|| {
98                Refusal::new(
99                    "invalid_store_name",
100                    "data directory needs a UTF-8 basename",
101                )
102            })?
103            .to_owned(),
104    };
105    validate_name(&name)?;
106    if let Some(socket) = socket {
107        check_endpoint(socket)?;
108        let parent = socket
109            .parent()
110            .ok_or_else(|| Refusal::new("invalid_socket", "endpoint needs a parent"))?;
111        let metadata = private_directory(parent)?;
112        let owner = uid(parent)?;
113        check_identity(parent, &metadata, owner)?;
114        return Ok(Endpoint {
115            name,
116            socket: socket.to_path_buf(),
117            data_dir,
118        });
119    }
120    let root = root()?;
121    let metadata = private_directory(&root)?;
122    let namespace = root.join("haem");
123    let socket = namespace.join(filename(&name));
124    check_endpoint(&socket)?;
125    let owner = uid(&root)?;
126    check_identity(&root, &metadata, owner)?;
127    match DirBuilder::new().mode(0o700).create(&namespace) {
128        Ok(()) => {}
129        Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
130        Err(error) => return Err(Refusal::new("invalid_runtime_location", error)),
131    }
132    let namespace_metadata = private_directory(&namespace)?;
133    check_identity(&namespace, &namespace_metadata, owner)?;
134    check_identity(&root, &metadata, owner)?;
135    Ok(Endpoint {
136        name,
137        socket,
138        data_dir,
139    })
140}
141
142fn canonical_data(data: &Path, create: bool) -> Result<PathBuf, Refusal> {
143    if !data.is_absolute() {
144        return Err(Refusal::new(
145            "invalid_data_dir",
146            "data directory must be absolute",
147        ));
148    }
149    if create {
150        let Some(Component::Normal(name)) = data.components().next_back() else {
151            return Err(Refusal::new(
152                "invalid_data_dir",
153                "creation needs a normal final component",
154            ));
155        };
156        if name.to_str().is_none() {
157            return Err(Refusal::new(
158                "invalid_store_name",
159                "creation needs a UTF-8 final component",
160            ));
161        }
162        let parent = data
163            .parent()
164            .ok_or_else(|| Refusal::new("invalid_data_dir", "creation needs an existing parent"))?;
165        let parent =
166            fs::canonicalize(parent).map_err(|error| Refusal::new("invalid_data_dir", error))?;
167        let metadata =
168            fs::metadata(&parent).map_err(|error| Refusal::new("invalid_data_dir", error))?;
169        if !metadata.is_dir() {
170            return Err(Refusal::new(
171                "invalid_data_dir",
172                "creation parent is not a directory",
173            ));
174        }
175        Ok(parent.join(name))
176    } else {
177        let canonical =
178            fs::canonicalize(data).map_err(|error| Refusal::new("invalid_data_dir", error))?;
179        let metadata =
180            fs::metadata(&canonical).map_err(|error| Refusal::new("invalid_data_dir", error))?;
181        if !metadata.is_dir() {
182            return Err(Refusal::new("invalid_data_dir", "store is not a directory"));
183        }
184        Ok(canonical)
185    }
186}
187
188fn validate_name(name: &str) -> Result<(), Refusal> {
189    if name.is_empty()
190        || name.len() > 128
191        || matches!(name, "." | "..")
192        || name.contains(['/', '\\', '\0'])
193    {
194        return Err(Refusal::new(
195            "invalid_store_name",
196            "name must be 1 to 128 UTF-8 bytes without separators or NUL",
197        ));
198    }
199    Ok(())
200}
201
202fn check_endpoint(socket: &Path) -> Result<(), Refusal> {
203    if !socket.is_absolute() || socket.as_os_str().as_bytes().contains(&0) {
204        return Err(Refusal::new(
205            "invalid_socket",
206            "endpoint must be an absolute path without NUL",
207        ));
208    }
209    SocketAddr::from_pathname(socket).map_err(|error| Refusal::new("endpoint_too_long", error))?;
210    Ok(())
211}
212
213fn private_directory(path: &Path) -> Result<Metadata, Refusal> {
214    if !path.is_absolute() {
215        return Err(Refusal::new(
216            "invalid_runtime_location",
217            "runtime directory must be absolute",
218        ));
219    }
220    let metadata = fs::symlink_metadata(path)
221        .map_err(|error| Refusal::new("invalid_runtime_location", error))?;
222    if !metadata.is_dir() || metadata.permissions().mode() & 0o7777 != 0o700 {
223        return Err(Refusal::new(
224            "invalid_runtime_location",
225            "runtime directory must be a non-symlink directory with mode 0700",
226        ));
227    }
228    Ok(metadata)
229}
230
231fn check_identity(path: &Path, expected: &Metadata, owner: u32) -> Result<(), Refusal> {
232    let actual = private_directory(path)?;
233    if actual.uid() != owner || (actual.dev(), actual.ino()) != (expected.dev(), expected.ino()) {
234        return Err(Refusal::new(
235            "invalid_runtime_location",
236            "runtime directory owner or identity changed",
237        ));
238    }
239    Ok(())
240}
241
242fn probe_uid(root: &Path) -> Result<u32, Refusal> {
243    let sequence = PROBES
244        .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
245            value.checked_add(1)
246        })
247        .map_err(|error| Refusal::new("runtime_uid_unavailable", error))?;
248    let path = root.join(format!(".haem-uid-{}-{sequence}", std::process::id()));
249    let file = OpenOptions::new()
250        .write(true)
251        .create_new(true)
252        .mode(0o600)
253        .open(&path)
254        .map_err(|error| Refusal::new("runtime_uid_unavailable", error))?;
255    let metadata = file.metadata();
256    let cleanup = fs::remove_file(&path);
257    drop(file);
258    finish_probe(metadata, cleanup)
259}
260
261fn finish_probe(metadata: io::Result<Metadata>, cleanup: io::Result<()>) -> Result<u32, Refusal> {
262    match (metadata, cleanup) {
263        (Ok(metadata), Ok(())) => {
264            if !metadata.is_file() || metadata.permissions().mode() & 0o7777 != 0o600 {
265                return Err(Refusal::new(
266                    "runtime_uid_unavailable",
267                    "uid probe must be a regular mode-0600 file",
268                ));
269            }
270            Ok(metadata.uid())
271        }
272        (Err(error), Ok(())) => Err(Refusal::new("runtime_uid_unavailable", error)),
273        (Ok(_), Err(error)) => Err(Refusal::new("runtime_cleanup_failed", error)),
274        (Err(read), Err(cleanup)) => Err(Refusal::new(
275            "runtime_cleanup_failed",
276            format!("uid read: {read}; probe removal: {cleanup}"),
277        )),
278    }
279}
280
281fn filename(name: &str) -> String {
282    const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
283    let mut encoded = String::with_capacity(43);
284    let mut pending = 0_u16;
285    let mut bits = 0_u32;
286    for byte in Sha256::digest(name.as_bytes()) {
287        pending = ((pending & ((1 << bits) - 1)) << 8) | u16::from(byte);
288        bits += 8;
289        while bits >= 6 {
290            bits -= 6;
291            encoded.push(char::from(ALPHABET[usize::from((pending >> bits) & 63)]));
292        }
293    }
294    if bits != 0 {
295        encoded.push(char::from(
296            ALPHABET[usize::from((pending << (6 - bits)) & 63)],
297        ));
298    }
299    encoded
300}
301
302#[cfg(target_os = "linux")]
303fn os_root() -> Result<PathBuf, Refusal> {
304    linux_root(std::env::var_os("XDG_RUNTIME_DIR"))
305}
306
307#[cfg(target_os = "macos")]
308fn os_root() -> Result<PathBuf, Refusal> {
309    getconf_root(
310        std::process::Command::new("/usr/bin/getconf")
311            .arg("DARWIN_USER_TEMP_DIR")
312            .output(),
313    )
314}
315
316#[cfg(not(any(target_os = "linux", target_os = "macos")))]
317fn os_root() -> Result<PathBuf, Refusal> {
318    Err(Refusal::new(
319        "runtime_location_unavailable",
320        "platform has no supported OS runtime query",
321    ))
322}
323
324#[cfg(any(target_os = "linux", test))]
325fn linux_root(input: Option<std::ffi::OsString>) -> Result<PathBuf, Refusal> {
326    let input = input.filter(|value| !value.is_empty()).ok_or_else(|| {
327        Refusal::new(
328            "runtime_location_unavailable",
329            "XDG_RUNTIME_DIR is unavailable",
330        )
331    })?;
332    let path = PathBuf::from(input);
333    if !path.is_absolute() || path.to_str().is_none() || path.as_os_str().as_bytes().contains(&0) {
334        return Err(Refusal::new(
335            "invalid_runtime_location",
336            "XDG_RUNTIME_DIR is not an absolute runtime path",
337        ));
338    }
339    Ok(path)
340}
341
342#[cfg(any(target_os = "macos", test))]
343fn getconf_root(input: io::Result<std::process::Output>) -> Result<PathBuf, Refusal> {
344    let output = input.map_err(|error| Refusal::new("runtime_location_unavailable", error))?;
345    if !output.status.success() {
346        return Err(Refusal::new(
347            "runtime_location_unavailable",
348            format!("OS runtime query exited {}", output.status),
349        ));
350    }
351    let output = std::str::from_utf8(&output.stdout)
352        .map_err(|error| Refusal::new("invalid_runtime_location", error))?;
353    let output = output.strip_suffix('\n').map_or(output, |path| path);
354    if output.is_empty() {
355        return Err(Refusal::new(
356            "runtime_location_unavailable",
357            "OS runtime query returned no path",
358        ));
359    }
360    if output.contains(['\n', '\r', '\0']) || !Path::new(output).is_absolute() {
361        return Err(Refusal::new(
362            "invalid_runtime_location",
363            "OS runtime query needs one absolute UTF-8 path",
364        ));
365    }
366    Ok(PathBuf::from(output))
367}
368
369#[cfg(test)]
370pub(crate) fn resolve_input(
371    data_dir: &Path,
372    name: Option<&str>,
373    socket: Option<&Path>,
374    create: bool,
375    root: &Path,
376    uid: u32,
377) -> Result<Endpoint, Refusal> {
378    resolve_with(
379        data_dir,
380        name,
381        socket,
382        create,
383        || Ok(root.to_path_buf()),
384        |_| Ok(uid),
385    )
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use std::os::unix::process::ExitStatusExt;
392
393    type TestResult = Result<(), Box<dyn std::error::Error>>;
394
395    fn fixture() -> Result<tempfile::TempDir, std::io::Error> {
396        // A fixed short root, not TMPDIR (STOR-021): valid-endpoint fixtures must fit
397        // sun_path (104 bytes on macOS), which the workspace target folder overruns;
398        // these fixtures open no store, so a tmpfs /tmp on Linux is no concern.
399        let directory = tempfile::Builder::new().prefix("r").tempdir_in("/tmp")?;
400        fs::set_permissions(directory.path(), fs::Permissions::from_mode(0o700))?;
401        Ok(directory)
402    }
403
404    fn query(bytes: &[u8], status: i32) -> std::process::Output {
405        std::process::Output {
406            status: std::process::ExitStatus::from_raw(status),
407            stdout: bytes.to_vec(),
408            stderr: Vec::new(),
409        }
410    }
411
412    fn injected(
413        root: &Path,
414        uid: u32,
415        data: &Path,
416        name: Option<&str>,
417        socket: Option<&Path>,
418        create: bool,
419    ) -> Result<Endpoint, Refusal> {
420        resolve_with(
421            data,
422            name,
423            socket,
424            create,
425            || Ok(root.to_path_buf()),
426            |_| Ok(uid),
427        )
428    }
429
430    #[test]
431    fn unavailable_os_queries_are_named() -> TestResult {
432        assert_eq!(
433            linux_root(None).err().ok_or("accepted missing root")?.code,
434            "runtime_location_unavailable"
435        );
436        let missing = io::Error::new(io::ErrorKind::NotFound, "missing query");
437        assert_eq!(
438            getconf_root(Err(missing))
439                .err()
440                .ok_or("accepted missing query")?
441                .code,
442            "runtime_location_unavailable"
443        );
444        assert_eq!(
445            getconf_root(Ok(query(b"ignored\n", 256)))
446                .err()
447                .ok_or("accepted failed query")?
448                .code,
449            "runtime_location_unavailable"
450        );
451        for bytes in [b"".as_slice(), b"\n"] {
452            assert_eq!(
453                getconf_root(Ok(query(bytes, 0)))
454                    .err()
455                    .ok_or("accepted empty query")?
456                    .code,
457                "runtime_location_unavailable"
458            );
459        }
460        Ok(())
461    }
462
463    #[test]
464    fn query_output_requires_one_absolute_utf8_path() -> TestResult {
465        for bytes in [
466            b"relative\n".as_slice(),
467            b"\xff\n",
468            b"/one\n/two\n",
469            b"/one\0\n",
470        ] {
471            assert_eq!(
472                getconf_root(Ok(query(bytes, 0)))
473                    .err()
474                    .ok_or("accepted invalid query output")?
475                    .code,
476                "invalid_runtime_location"
477            );
478        }
479        assert_eq!(
480            getconf_root(Ok(query(b"/private/runtime/\n", 0)))?,
481            PathBuf::from("/private/runtime/")
482        );
483        Ok(())
484    }
485
486    #[test]
487    fn root_refusals_leave_no_namespace() -> TestResult {
488        let directory = fixture()?;
489        let root = directory.path();
490        let uid = fs::metadata(root)?.uid();
491        let data = root.join("store");
492        fs::create_dir(&data)?;
493        for candidate in [PathBuf::from("relative"), root.join("missing")] {
494            assert_eq!(
495                injected(&candidate, uid, &data, None, None, false)
496                    .err()
497                    .ok_or("accepted invalid root")?
498                    .code,
499                "invalid_runtime_location"
500            );
501        }
502        let wrong_uid = uid.checked_add(1).ok_or("uid overflow")?;
503        assert_eq!(
504            injected(root, wrong_uid, &data, None, None, false)
505                .err()
506                .ok_or("accepted foreign owner")?
507                .code,
508            "invalid_runtime_location"
509        );
510        for mode in [0o720, 0o702, 0o755] {
511            fs::set_permissions(root, fs::Permissions::from_mode(mode))?;
512            assert_eq!(
513                injected(root, uid, &data, None, None, false)
514                    .err()
515                    .ok_or("accepted nonprivate root")?
516                    .code,
517                "invalid_runtime_location"
518            );
519            assert!(!root.join("haem").try_exists()?);
520        }
521        fs::set_permissions(root, fs::Permissions::from_mode(0o700))?;
522        let link = root.join("substituted");
523        std::os::unix::fs::symlink(root, &link)?;
524        assert_eq!(
525            injected(&link, uid, &data, None, None, false)
526                .err()
527                .ok_or("accepted root symlink")?
528                .code,
529            "invalid_runtime_location"
530        );
531        assert!(!root.join("haem").try_exists()?);
532        directory.close()?;
533        Ok(())
534    }
535
536    #[test]
537    fn names_are_validated_before_runtime_entries() -> TestResult {
538        let directory = fixture()?;
539        let root = directory.path();
540        let data = root.join("store");
541        fs::create_dir(&data)?;
542        let uid = fs::metadata(root)?.uid();
543        let long = "x".repeat(129);
544        for name in [
545            "",
546            ".",
547            "..",
548            "../store",
549            "back\\slash",
550            "nul\0name",
551            long.as_str(),
552        ] {
553            assert_eq!(
554                injected(root, uid, &data, Some(name), None, false)
555                    .err()
556                    .ok_or("accepted invalid name")?
557                    .code,
558                "invalid_store_name"
559            );
560            assert_eq!(fs::read_dir(root)?.collect::<Result<Vec<_>, _>>()?.len(), 1);
561        }
562        directory.close()?;
563        Ok(())
564    }
565
566    #[test]
567    fn directory_names_and_explicit_names_choose_deterministic_private_endpoints() -> TestResult {
568        let directory = fixture()?;
569        let root = directory.path();
570        let uid = fs::metadata(root)?.uid();
571        let first = root.join("first");
572        let second = root.join("second");
573        fs::create_dir(&first)?;
574        fs::create_dir(&second)?;
575        let a = injected(root, uid, &first, None, None, false)?;
576        let b = injected(root, uid, &second, None, None, false)?;
577        assert_eq!(a.name, "first");
578        assert_eq!(b.name, "second");
579        assert_ne!(a.socket, b.socket);
580        assert_eq!(a.socket.parent(), Some(root.join("haem").as_path()));
581        assert_eq!(
582            fs::symlink_metadata(root.join("haem"))?
583                .permissions()
584                .mode()
585                & 0o7777,
586            0o700
587        );
588        assert_eq!(
589            injected(root, uid, &first, None, None, false)?.socket,
590            a.socket
591        );
592        let named = injected(root, uid, &first, Some("chosen"), None, false)?;
593        assert_eq!(named.name, "chosen");
594        assert_ne!(named.socket, a.socket);
595        SocketAddr::from_pathname(&named.socket)?;
596        let left = first.join("same");
597        let right = second.join("same");
598        fs::create_dir(&left)?;
599        fs::create_dir(&right)?;
600        assert_eq!(
601            injected(root, uid, &left, None, None, false)?.socket,
602            injected(root, uid, &right, None, None, false)?.socket
603        );
604        assert_ne!(
605            injected(root, uid, &left, Some("left"), None, false)?.socket,
606            injected(root, uid, &right, Some("right"), None, false)?.socket
607        );
608        directory.close()?;
609        Ok(())
610    }
611
612    #[test]
613    fn explicit_socket_is_preserved_without_querying_the_os() -> TestResult {
614        let directory = fixture()?;
615        let data = directory.path().join("store");
616        fs::create_dir(&data)?;
617        let socket = directory.path().join("legacy.sock");
618        let uid = fs::metadata(directory.path())?.uid();
619        let endpoint = resolve_with(
620            &data,
621            None,
622            Some(&socket),
623            false,
624            || {
625                Err(Refusal::new(
626                    "runtime_location_unavailable",
627                    "must not query",
628                ))
629            },
630            |_| Ok(uid),
631        )?;
632        assert_eq!(endpoint.socket, socket);
633        assert_eq!(endpoint.name, "store");
634        assert!(!directory.path().join("haem").try_exists()?);
635        directory.close()?;
636        Ok(())
637    }
638
639    #[test]
640    fn namespace_permission_and_symlink_substitution_are_refused() -> TestResult {
641        let directory = fixture()?;
642        let root = directory.path();
643        let uid = fs::metadata(root)?.uid();
644        let data = root.join("store");
645        fs::create_dir(&data)?;
646        let namespace = root.join("haem");
647        fs::create_dir(&namespace)?;
648        fs::set_permissions(&namespace, fs::Permissions::from_mode(0o755))?;
649        assert_eq!(
650            injected(root, uid, &data, None, None, false)
651                .err()
652                .ok_or("accepted wrong namespace mode")?
653                .code,
654            "invalid_runtime_location"
655        );
656        assert_eq!(
657            fs::metadata(&namespace)?.permissions().mode() & 0o777,
658            0o755
659        );
660        fs::remove_dir(&namespace)?;
661        std::os::unix::fs::symlink(&data, &namespace)?;
662        assert_eq!(
663            injected(root, uid, &data, None, None, false)
664                .err()
665                .ok_or("accepted namespace symlink")?
666                .code,
667            "invalid_runtime_location"
668        );
669        assert!(fs::symlink_metadata(&namespace)?.file_type().is_symlink());
670        directory.close()?;
671        Ok(())
672    }
673
674    #[test]
675    fn endpoint_length_is_refused_before_namespace_creation() -> TestResult {
676        let directory = fixture()?;
677        let root = directory.path().join("x".repeat(110));
678        fs::create_dir(&root)?;
679        fs::set_permissions(&root, fs::Permissions::from_mode(0o700))?;
680        let data = directory.path().join("store");
681        fs::create_dir(&data)?;
682        assert_eq!(
683            injected(&root, fs::metadata(&root)?.uid(), &data, None, None, false)
684                .err()
685                .ok_or("accepted oversized endpoint")?
686                .code,
687            "endpoint_too_long"
688        );
689        assert!(!root.join("haem").try_exists()?);
690        directory.close()?;
691        Ok(())
692    }
693
694    #[test]
695    fn creation_canonicalizes_parent_and_checks_the_created_path() -> TestResult {
696        let directory = fixture()?;
697        let root = directory.path();
698        let uid = fs::metadata(root)?.uid();
699        let data = root.join("new-store");
700        let endpoint = injected(root, uid, &data, None, None, true)?;
701        assert_eq!(endpoint.name, "new-store");
702        assert_eq!(endpoint.data_dir, fs::canonicalize(root)?.join("new-store"));
703        fs::create_dir(&data)?;
704        endpoint.check_created()?;
705        fs::remove_dir(&data)?;
706        let other = root.join("other");
707        fs::create_dir(&other)?;
708        std::os::unix::fs::symlink(&other, &data)?;
709        assert_eq!(
710            endpoint
711                .check_created()
712                .err()
713                .ok_or("accepted changed created path")?
714                .code,
715            "data_dir_changed"
716        );
717        directory.close()?;
718        Ok(())
719    }
720
721    #[test]
722    fn uid_probe_is_removed_and_matches_the_created_file_owner() -> TestResult {
723        let directory = fixture()?;
724        assert_eq!(
725            probe_uid(directory.path())?,
726            fs::metadata(directory.path())?.uid()
727        );
728        assert_eq!(
729            fs::read_dir(directory.path())?
730                .collect::<Result<Vec<_>, _>>()?
731                .len(),
732            0
733        );
734        directory.close()?;
735        Ok(())
736    }
737
738    #[test]
739    fn uid_read_and_cleanup_failures_are_preserved() -> TestResult {
740        let directory = fixture()?;
741        assert_eq!(
742            finish_probe(fs::metadata(directory.path()), Ok(()))
743                .err()
744                .ok_or("accepted invalid probe shape")?
745                .code,
746            "runtime_uid_unavailable"
747        );
748        let read = io::Error::other("read failed");
749        assert_eq!(
750            finish_probe(Err(read), Ok(()))
751                .err()
752                .ok_or("accepted failed uid read")?
753                .code,
754            "runtime_uid_unavailable"
755        );
756        let cleanup = io::Error::other("cleanup failed");
757        assert_eq!(
758            finish_probe(fs::metadata(directory.path()), Err(cleanup))
759                .err()
760                .ok_or("accepted failed cleanup")?
761                .code,
762            "runtime_cleanup_failed"
763        );
764        let both = finish_probe(
765            Err(io::Error::other("read failed")),
766            Err(io::Error::other("cleanup failed")),
767        )
768        .err()
769        .ok_or("accepted both failures")?;
770        assert_eq!(both.code, "runtime_cleanup_failed");
771        assert!(both.to_string().contains("read failed"));
772        assert!(both.to_string().contains("cleanup failed"));
773        directory.close()?;
774        Ok(())
775    }
776
777    #[test]
778    fn byte_limits_and_full_digest_encoding_preserve_names() -> TestResult {
779        validate_name(&"é".repeat(64))?;
780        assert_eq!(
781            validate_name(&"é".repeat(65))
782                .err()
783                .ok_or("accepted 130-byte name")?
784                .code,
785            "invalid_store_name"
786        );
787        let a = filename("store");
788        let b = filename("other");
789        assert_eq!(a.len(), 43);
790        assert_eq!(a, "gk2A1xmF8IKiaZeo24i10d1Ft3fXNYXQPSNjA-Ib3pc");
791        assert!(
792            a.bytes()
793                .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
794        );
795        assert_ne!(a, b);
796        assert_eq!(a, filename("store"));
797        Ok(())
798    }
799}