microsandbox_image/
store.rs1use std::path::{Path, PathBuf};
4
5use oci_client::Reference;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest as Sha2Digest, Sha256};
8
9use crate::{
10 config::ImageConfig,
11 digest::Digest,
12 error::{ImageError, ImageResult},
13};
14
15const LAYERS_DIR: &str = "layers";
21
22const IMAGES_DIR: &str = "images";
24
25pub(crate) const COMPLETE_MARKER: &str = ".complete";
27
28pub struct GlobalCache {
44 layers_dir: PathBuf,
46
47 images_dir: PathBuf,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CachedImageMetadata {
54 pub manifest_digest: String,
56 pub config_digest: String,
58 pub config: ImageConfig,
60 pub layers: Vec<CachedLayerMetadata>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct CachedLayerMetadata {
67 pub digest: String,
69 pub media_type: Option<String>,
71 pub size_bytes: Option<u64>,
73 pub diff_id: String,
75}
76
77impl GlobalCache {
82 pub fn new(cache_dir: &Path) -> ImageResult<Self> {
86 let layers_dir = cache_dir.join(LAYERS_DIR);
87 let images_dir = cache_dir.join(IMAGES_DIR);
88 std::fs::create_dir_all(&layers_dir).map_err(|e| ImageError::Cache {
89 path: layers_dir.clone(),
90 source: e,
91 })?;
92 std::fs::create_dir_all(&images_dir).map_err(|e| ImageError::Cache {
93 path: images_dir.clone(),
94 source: e,
95 })?;
96 Ok(Self {
97 layers_dir,
98 images_dir,
99 })
100 }
101
102 pub fn layers_dir(&self) -> &Path {
104 &self.layers_dir
105 }
106
107 pub fn tar_path(&self, digest: &Digest) -> PathBuf {
109 self.layers_dir
110 .join(format!("{}.tar.gz", digest.to_path_safe()))
111 }
112
113 pub fn part_path(&self, digest: &Digest) -> PathBuf {
115 self.layers_dir
116 .join(format!("{}.tar.gz.part", digest.to_path_safe()))
117 }
118
119 pub fn extracted_dir(&self, digest: &Digest) -> PathBuf {
121 self.layers_dir
122 .join(format!("{}.extracted", digest.to_path_safe()))
123 }
124
125 pub fn extracting_dir(&self, digest: &Digest) -> PathBuf {
127 self.layers_dir
128 .join(format!("{}.extracting", digest.to_path_safe()))
129 }
130
131 pub fn index_path(&self, digest: &Digest) -> PathBuf {
133 self.layers_dir
134 .join(format!("{}.index", digest.to_path_safe()))
135 }
136
137 pub fn implicit_dirs_path(&self, digest: &Digest) -> PathBuf {
139 self.layers_dir
140 .join(format!("{}.implicit_dirs", digest.to_path_safe()))
141 }
142
143 pub fn lock_path(&self, digest: &Digest) -> PathBuf {
145 self.layers_dir
146 .join(format!("{}.lock", digest.to_path_safe()))
147 }
148
149 pub fn download_lock_path(&self, digest: &Digest) -> PathBuf {
151 self.layers_dir
152 .join(format!("{}.download.lock", digest.to_path_safe()))
153 }
154
155 pub fn image_lock_path(&self, reference: &Reference) -> PathBuf {
157 self.images_dir
158 .join(format!("{}.lock", image_cache_key(reference)))
159 }
160
161 pub fn is_extracted(&self, digest: &Digest) -> bool {
163 self.extracted_dir(digest).join(COMPLETE_MARKER).exists()
164 }
165
166 pub fn all_layers_extracted(&self, digests: &[Digest]) -> bool {
168 digests.iter().all(|d| self.is_extracted(d))
169 }
170
171 pub fn read_image_metadata(
173 &self,
174 reference: &Reference,
175 ) -> ImageResult<Option<CachedImageMetadata>> {
176 let path = self.image_metadata_path(reference);
177
178 let data = match std::fs::read_to_string(&path) {
179 Ok(data) => data,
180 Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
181 Err(e) => return Err(ImageError::Cache { path, source: e }),
182 };
183
184 match serde_json::from_str::<CachedImageMetadata>(&data) {
185 Ok(metadata) => Ok(Some(metadata)),
186 Err(e) => {
187 tracing::warn!(path = %path.display(), error = %e, "corrupt image metadata cache, ignoring");
188 Ok(None)
189 }
190 }
191 }
192
193 pub(crate) fn write_image_metadata(
195 &self,
196 reference: &Reference,
197 metadata: &CachedImageMetadata,
198 ) -> ImageResult<()> {
199 let path = self.image_metadata_path(reference);
200 let temp_path = path.with_extension("json.part");
201 let payload = serde_json::to_vec(metadata).map_err(|e| {
202 ImageError::ConfigParse(format!("failed to serialize cached image metadata: {e}"))
203 })?;
204
205 std::fs::write(&temp_path, payload).map_err(|e| ImageError::Cache {
206 path: temp_path.clone(),
207 source: e,
208 })?;
209 std::fs::rename(&temp_path, &path).map_err(|e| ImageError::Cache { path, source: e })?;
210
211 Ok(())
212 }
213
214 pub fn delete_image_metadata(&self, reference: &Reference) -> ImageResult<()> {
216 let path = self.image_metadata_path(reference);
217 match std::fs::remove_file(&path) {
218 Ok(()) => Ok(()),
219 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
220 Err(e) => Err(ImageError::Cache { path, source: e }),
221 }
222 }
223
224 fn image_metadata_path(&self, reference: &Reference) -> PathBuf {
226 self.images_dir
227 .join(format!("{}.json", image_cache_key(reference)))
228 }
229}
230
231fn image_cache_key(reference: &Reference) -> String {
236 let mut hasher = Sha256::new();
237 hasher.update(reference.to_string().as_bytes());
238 hex::encode(hasher.finalize())
239}