a3s_box_runtime/cache/
mod.rs1use std::path::{Component, Path};
8
9use a3s_box_core::error::{BoxError, Result};
10
11pub mod layer_cache;
12pub mod rootfs_cache;
13
14pub use layer_cache::LayerCache;
15pub use rootfs_cache::{prune_apfs_rootfs_cache_all, RootfsCache, RootfsPruneResult};
16
17pub(crate) fn validate_cache_key(key: &str, kind: &str) -> Result<()> {
23 if key.is_empty() || key.contains('\0') || key.contains('/') || key.contains('\\') {
24 return Err(BoxError::CacheError(format!(
25 "Invalid {kind} cache key: expected a single path component"
26 )));
27 }
28
29 let mut components = Path::new(key).components();
30 if !matches!(components.next(), Some(Component::Normal(_))) || components.next().is_some() {
31 return Err(BoxError::CacheError(format!(
32 "Invalid {kind} cache key: expected a single path component"
33 )));
34 }
35
36 Ok(())
37}
38
39pub(crate) fn is_real_directory(path: &Path) -> bool {
44 std::fs::symlink_metadata(path)
45 .map(|metadata| metadata.is_dir() && !metadata.file_type().is_symlink())
46 .unwrap_or(false)
47}
48
49pub(crate) fn is_regular_file(path: &Path) -> bool {
51 std::fs::symlink_metadata(path)
52 .map(|metadata| metadata.is_file() && !metadata.file_type().is_symlink())
53 .unwrap_or(false)
54}
55
56pub(crate) fn remove_path_no_follow(path: &Path) -> Result<()> {
58 let metadata = match std::fs::symlink_metadata(path) {
59 Ok(metadata) => metadata,
60 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
61 Err(error) => {
62 return Err(BoxError::CacheError(format!(
63 "Failed to inspect cached path {}: {error}",
64 path.display()
65 )))
66 }
67 };
68 let removed = if metadata.is_dir() && !metadata.file_type().is_symlink() {
69 std::fs::remove_dir_all(path)
70 } else {
71 std::fs::remove_file(path)
72 };
73 match removed {
74 Ok(()) => Ok(()),
75 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
76 Err(error) => Err(BoxError::CacheError(format!(
77 "Failed to remove cached path {}: {error}",
78 path.display()
79 ))),
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn cache_keys_must_be_single_path_components() {
89 for key in ["", ".", "..", "../escape", "nested/key", "nested\\key"] {
90 assert!(validate_cache_key(key, "test").is_err(), "key={key:?}");
91 }
92
93 for key in ["sha256_abc123", "rootfs-key", "键"] {
94 assert!(validate_cache_key(key, "test").is_ok(), "key={key:?}");
95 }
96 }
97}