use std::path::Path;
use microsandbox_utils::index::IndexBuilder;
use super::{OVERRIDE_XATTR_KEY, S_IFLNK, S_IFMT};
use crate::error::{ImageError, ImageResult};
const WHITEOUT_PREFIX: &str = ".wh.";
const OPAQUE_WHITEOUT: &str = ".wh..wh..opq";
pub(crate) async fn build_sidecar_index(
extracted_dir: &Path,
index_path: &Path,
) -> ImageResult<()> {
let extracted = extracted_dir.to_path_buf();
let output = index_path.to_path_buf();
tokio::task::spawn_blocking(move || build_index_sync(&extracted, &output))
.await
.map_err(|e| {
ImageError::IndexBuild(
extracted_dir.display().to_string(),
std::io::Error::other(format!("task join error: {e}")),
)
})?
}
fn build_index_sync(extracted_dir: &Path, index_path: &Path) -> ImageResult<()> {
let builder = IndexBuilder::new();
let builder = walk_dir(extracted_dir, extracted_dir, builder)?;
let index_data = builder.build();
std::fs::write(index_path, &index_data)
.map_err(|e| ImageError::IndexBuild(extracted_dir.display().to_string(), e))?;
Ok(())
}
fn walk_dir(root: &Path, dir: &Path, mut builder: IndexBuilder) -> ImageResult<IndexBuilder> {
let rel_path = dir.strip_prefix(root).unwrap_or(Path::new(""));
let rel_str = if rel_path == Path::new("") {
"".to_string()
} else {
format!("{}", rel_path.display())
};
let opaque = dir.join(OPAQUE_WHITEOUT).exists();
if opaque {
builder = builder.opaque_dir(&rel_str);
} else {
builder = builder.dir(&rel_str);
}
let entries = match std::fs::read_dir(dir) {
Ok(e) => e,
Err(e) => {
tracing::warn!(dir = %dir.display(), error = %e, "failed to read dir for index");
return Ok(builder);
}
};
for entry_result in entries {
let entry = match entry_result {
Ok(e) => e,
Err(e) => {
tracing::warn!(dir = %dir.display(), error = %e, "failed to read dir entry for index");
continue;
}
};
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str == ".complete" {
continue;
}
if name_str == OPAQUE_WHITEOUT {
continue;
}
let entry_path = entry.path();
if let Some(target_name) = name_str.strip_prefix(WHITEOUT_PREFIX) {
if !target_name.is_empty() {
builder = builder.whiteout(&rel_str, target_name);
}
continue;
}
let metadata = match entry.metadata() {
Ok(m) => m,
Err(e) => {
tracing::warn!(path = %entry_path.display(), error = %e, "failed to read metadata for index");
continue;
}
};
let (_uid, _gid, mode) = read_override_stat(&entry_path);
if metadata.is_dir() {
builder = builder.subdir(&rel_str, &name_str, mode & 0o7777);
builder = walk_dir(root, &entry_path, builder)?;
} else if (mode & S_IFMT) == S_IFLNK {
builder = builder.symlink(&rel_str, &name_str);
} else {
builder = builder.file(&rel_str, &name_str, mode & 0o7777);
}
}
Ok(builder)
}
fn read_override_stat(path: &Path) -> (u32, u32, u32) {
let data = match xattr::get(path, OVERRIDE_XATTR_KEY) {
Ok(Some(d)) if d.len() >= 20 => d,
_ => return (0, 0, 0),
};
let uid = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let gid = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let mode = u32::from_le_bytes([data[12], data[13], data[14], data[15]]);
(uid, gid, mode)
}