summaryrefslogtreecommitdiff
path: root/src/manifest.rs
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 07:48:21 +0000
committerHermes Agent <hermes@localhost>2026-08-12 07:48:21 +0000
commitf967d698efd44473a9285e02d0109f3bb0641622 (patch)
treec94975571db9426cf71b15c9e6d16346e438c7c6 /src/manifest.rs
parent5e6cae20331f8e7cfd551b4656f0de852a4ad44c (diff)
Format native builder and satisfy clippy
Diffstat (limited to 'src/manifest.rs')
-rw-r--r--src/manifest.rs202
1 files changed, 155 insertions, 47 deletions
diff --git a/src/manifest.rs b/src/manifest.rs
index 067dc9a..0821fab 100644
--- a/src/manifest.rs
+++ b/src/manifest.rs
@@ -4,7 +4,7 @@ use std::io::{Read, Write};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tar::{Archive, EntryType};
@@ -34,18 +34,41 @@ impl ArtifactManifest {
validate_file_records(&files)?;
validate_initrd_record(initrd.as_ref())?;
validate_manifest_paths(&files, initrd.as_ref(), &archive_members)?;
- ensure_unique("package", packages.iter().map(|record| record.name.as_str()))?;
+ ensure_unique(
+ "package",
+ packages.iter().map(|record| record.name.as_str()),
+ )?;
ensure_unique("file", files.iter().map(|record| record.path.as_str()))?;
- ensure_unique("service", services.iter().map(|record| record.name.as_str()))?;
- ensure_unique("archive member", archive_members.iter().map(|record| record.path.as_str()))?;
- Ok(Self { packages, files, initrd, services, archive_members })
+ ensure_unique(
+ "service",
+ services.iter().map(|record| record.name.as_str()),
+ )?;
+ ensure_unique(
+ "archive member",
+ archive_members.iter().map(|record| record.path.as_str()),
+ )?;
+ Ok(Self {
+ packages,
+ files,
+ initrd,
+ services,
+ archive_members,
+ })
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
- let text = fs::read_to_string(path).with_context(|| format!("read artifact manifest {}", path.display()))?;
- let manifest: Self = toml::from_str(&text).with_context(|| format!("parse artifact manifest {}", path.display()))?;
- Self::new(manifest.packages, manifest.files, manifest.initrd, manifest.services, manifest.archive_members)
+ let text = fs::read_to_string(path)
+ .with_context(|| format!("read artifact manifest {}", path.display()))?;
+ let manifest: Self = toml::from_str(&text)
+ .with_context(|| format!("parse artifact manifest {}", path.display()))?;
+ Self::new(
+ manifest.packages,
+ manifest.files,
+ manifest.initrd,
+ manifest.services,
+ manifest.archive_members,
+ )
}
pub fn write_beside(&self, artifact: impl AsRef<Path>) -> Result<PathBuf> {
@@ -67,9 +90,9 @@ impl ArtifactManifest {
/// directory.
pub fn path_beside(artifact: impl AsRef<Path>) -> Result<PathBuf> {
let artifact = artifact.as_ref();
- let name = artifact
- .file_name()
- .ok_or_else(|| anyhow::anyhow!("artifact path has no filename: {}", artifact.display()))?;
+ let name = artifact.file_name().ok_or_else(|| {
+ anyhow::anyhow!("artifact path has no filename: {}", artifact.display())
+ })?;
let mut manifest_name = name.to_os_string();
manifest_name.push(".manifest.toml");
Ok(artifact.with_file_name(manifest_name))
@@ -134,7 +157,10 @@ impl ArtifactManifest {
EntryType::Directory => "directory",
EntryType::Symlink => "symlink",
EntryType::Link => "hardlink",
- other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()),
+ other => bail!(
+ "unsupported archive member type {other:?} in {}",
+ archive_path.display()
+ ),
};
archive_members.push(ArchiveMemberRecord::new(path.clone(), kind));
@@ -144,7 +170,10 @@ impl ArtifactManifest {
if path.starts_with("boot/initrd-") && path.ends_with(".img") {
let record = InitrdRecord::new(path.clone(), digest.clone());
if initrd.replace(record).is_some() {
- bail!("multiple initrd images found in archive {}", archive_path.display());
+ bail!(
+ "multiple initrd images found in archive {}",
+ archive_path.display()
+ );
}
}
if !path.starts_with("boot/initrd-") || !path.ends_with(".img") {
@@ -194,7 +223,10 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil
entries.sort_by_key(|entry| entry.path().to_path_buf());
let mut hardlink_targets: BTreeMap<(u64, u64), String> = BTreeMap::new();
for entry in entries {
- let relative = entry.path().strip_prefix(rootfs).expect("walk entry is below rootfs");
+ let relative = entry
+ .path()
+ .strip_prefix(rootfs)
+ .expect("walk entry is below rootfs");
if is_internal_metadata_path(&portable_path(relative)?) {
continue;
}
@@ -204,7 +236,9 @@ fn collect_files(rootfs: &Path, initrd: Option<&InitrdRecord>) -> Result<Vec<Fil
}
let file_type = entry.file_type();
if file_type.is_file() {
- let metadata = entry.metadata().with_context(|| format!("inspect rootfs file {}", entry.path().display()))?;
+ let metadata = entry
+ .metadata()
+ .with_context(|| format!("inspect rootfs file {}", entry.path().display()))?;
let key = (metadata.dev(), metadata.ino());
if let Some(target) = hardlink_targets.get(&key) {
files.push(FileRecord::hardlink(path, target.clone()));
@@ -230,7 +264,8 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> {
}
let mut services = Vec::new();
for entry in WalkDir::new(&system).follow_links(false).min_depth(2) {
- let entry = entry.with_context(|| format!("walk service state directory {}", system.display()))?;
+ let entry =
+ entry.with_context(|| format!("walk service state directory {}", system.display()))?;
let parent_is_wants_directory = entry
.path()
.parent()
@@ -239,10 +274,9 @@ fn collect_services(rootfs: &Path) -> Result<Vec<ServiceRecord>> {
if !parent_is_wants_directory || !entry.file_type().is_symlink() {
continue;
}
- let name = entry
- .file_name()
- .to_str()
- .ok_or_else(|| anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display()))?;
+ let name = entry.file_name().to_str().ok_or_else(|| {
+ anyhow::anyhow!("non-UTF-8 service name in {}", entry.path().display())
+ })?;
if name.ends_with(".service") {
services.push(ServiceRecord::new(name, true));
}
@@ -257,11 +291,16 @@ fn coalesce_enabled_services(mut services: Vec<ServiceRecord>) -> Vec<ServiceRec
}
fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecord>> {
- let file = fs::File::open(archive_path).with_context(|| format!("open archive {}", archive_path.display()))?;
+ let file = fs::File::open(archive_path)
+ .with_context(|| format!("open archive {}", archive_path.display()))?;
let mut archive = Archive::new(file);
let mut members = Vec::new();
- for entry in archive.entries().with_context(|| format!("read archive {}", archive_path.display()))? {
- let entry = entry.with_context(|| format!("read archive member from {}", archive_path.display()))?;
+ for entry in archive
+ .entries()
+ .with_context(|| format!("read archive {}", archive_path.display()))?
+ {
+ let entry = entry
+ .with_context(|| format!("read archive member from {}", archive_path.display()))?;
let path = portable_path(&entry.path()?)?;
if is_archive_root_path(&path) || is_internal_metadata_path(&path) {
continue;
@@ -271,7 +310,10 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor
EntryType::Directory => "directory",
EntryType::Symlink => "symlink",
EntryType::Link => "hardlink",
- other => bail!("unsupported archive member type {other:?} in {}", archive_path.display()),
+ other => bail!(
+ "unsupported archive member type {other:?} in {}",
+ archive_path.display()
+ ),
};
members.push(ArchiveMemberRecord::new(path, kind));
}
@@ -279,7 +321,8 @@ fn collect_archive_members(archive_path: &Path) -> Result<Vec<ArchiveMemberRecor
}
fn sha256_file(path: &Path) -> Result<String> {
- let mut file = fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?;
+ let mut file =
+ fs::File::open(path).with_context(|| format!("open rootfs file {}", path.display()))?;
sha256_reader(&mut file, path)
}
@@ -287,7 +330,9 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> {
let mut hasher = Sha256::new();
let mut buffer = [0; 8192];
loop {
- let read = reader.read(&mut buffer).with_context(|| format!("read {}", source.display()))?;
+ let read = reader
+ .read(&mut buffer)
+ .with_context(|| format!("read {}", source.display()))?;
if read == 0 {
break;
}
@@ -298,9 +343,17 @@ fn sha256_reader(reader: &mut impl Read, source: &Path) -> Result<String> {
fn portable_path(path: &Path) -> Result<String> {
if path.is_absolute() {
- bail!("absolute path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "absolute path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
- let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
+ let value = path.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 path is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
if value.is_empty() {
bail!("empty path is not valid in an artifact manifest");
}
@@ -310,19 +363,28 @@ fn portable_path(path: &Path) -> Result<String> {
std::path::Component::Normal(part) => normalized.push(part),
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
- bail!("parent path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "parent path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
std::path::Component::RootDir | std::path::Component::Prefix(_) => {
- bail!("absolute path is not valid in an artifact manifest: {}", path.display());
+ bail!(
+ "absolute path is not valid in an artifact manifest: {}",
+ path.display()
+ );
}
}
}
if normalized.as_os_str().is_empty() {
return Ok(".".into());
}
- let normalized = normalized
- .to_str()
- .ok_or_else(|| anyhow::anyhow!("non-UTF-8 path is not valid in an artifact manifest: {}", path.display()))?;
+ let normalized = normalized.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 path is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
Ok(normalized.to_owned())
}
@@ -349,7 +411,8 @@ fn is_enabled_service(path: &str) -> bool {
}
fn is_internal_metadata_path(path: &str) -> bool {
- path.split('/').any(|component| matches!(component, ".host" | ".fakedata"))
+ path.split('/')
+ .any(|component| matches!(component, ".host" | ".fakedata"))
}
fn is_archive_root_path(path: &str) -> bool {
@@ -364,7 +427,12 @@ fn service_name(path: &str) -> Result<&str> {
}
fn link_target(path: &Path) -> Result<String> {
- let value = path.to_str().ok_or_else(|| anyhow::anyhow!("non-UTF-8 symlink target is not valid in an artifact manifest: {}", path.display()))?;
+ let value = path.to_str().ok_or_else(|| {
+ anyhow::anyhow!(
+ "non-UTF-8 symlink target is not valid in an artifact manifest: {}",
+ path.display()
+ )
+ })?;
if value.is_empty() {
bail!("empty symlink target is not valid in an artifact manifest");
}
@@ -376,7 +444,11 @@ fn validate_manifest_paths(
initrd: Option<&InitrdRecord>,
archive_members: &[ArchiveMemberRecord],
) -> Result<()> {
- for path in files.iter().map(|record| record.path.as_str()).chain(archive_members.iter().map(|record| record.path.as_str())) {
+ for path in files
+ .iter()
+ .map(|record| record.path.as_str())
+ .chain(archive_members.iter().map(|record| record.path.as_str()))
+ {
portable_path(Path::new(path))?;
}
if let Some(initrd) = initrd {
@@ -386,10 +458,11 @@ fn validate_manifest_paths(
}
fn validate_initrd_record(record: Option<&InitrdRecord>) -> Result<()> {
- if let Some(record) = record {
- if record.sha256.is_empty() {
+ match record {
+ Some(record) if record.sha256.is_empty() => {
bail!("initrd record requires a sha256 digest: {}", record.path);
}
+ _ => {}
}
Ok(())
}
@@ -407,13 +480,21 @@ fn validate_file_records(records: &[FileRecord]) -> Result<()> {
}
"symlink" | "hardlink" => {
if record.digest.is_some() {
- bail!("{} record cannot carry a digest: {}", record.kind, record.path);
+ bail!(
+ "{} record cannot carry a digest: {}",
+ record.kind,
+ record.path
+ );
}
if record.target.as_deref().is_none_or(str::is_empty) {
bail!("{} record requires a target: {}", record.kind, record.path);
}
}
- _ => bail!("unsupported file record kind {}: {}", record.kind, record.path),
+ _ => bail!(
+ "unsupported file record kind {}: {}",
+ record.kind,
+ record.path
+ ),
}
}
Ok(())
@@ -437,7 +518,10 @@ pub struct PackageRecord {
impl PackageRecord {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
- Self { name: name.into(), version: version.into() }
+ Self {
+ name: name.into(),
+ version: version.into(),
+ }
}
}
@@ -451,15 +535,30 @@ pub struct FileRecord {
impl FileRecord {
pub fn file(path: impl Into<String>, digest: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "file".into(), digest: Some(digest.into()), target: None }
+ Self {
+ path: path.into(),
+ kind: "file".into(),
+ digest: Some(digest.into()),
+ target: None,
+ }
}
pub fn symlink(path: impl Into<String>, target: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "symlink".into(), digest: None, target: Some(target.into()) }
+ Self {
+ path: path.into(),
+ kind: "symlink".into(),
+ digest: None,
+ target: Some(target.into()),
+ }
}
pub fn hardlink(path: impl Into<String>, target: impl Into<String>) -> Self {
- Self { path: path.into(), kind: "hardlink".into(), digest: None, target: Some(target.into()) }
+ Self {
+ path: path.into(),
+ kind: "hardlink".into(),
+ digest: None,
+ target: Some(target.into()),
+ }
}
}
@@ -471,7 +570,10 @@ pub struct InitrdRecord {
impl InitrdRecord {
pub fn new(path: impl Into<String>, sha256: impl Into<String>) -> Self {
- Self { path: path.into(), sha256: sha256.into() }
+ Self {
+ path: path.into(),
+ sha256: sha256.into(),
+ }
}
}
@@ -483,7 +585,10 @@ pub struct ServiceRecord {
impl ServiceRecord {
pub fn new(name: impl Into<String>, enabled: bool) -> Self {
- Self { name: name.into(), enabled }
+ Self {
+ name: name.into(),
+ enabled,
+ }
}
}
@@ -495,6 +600,9 @@ pub struct ArchiveMemberRecord {
impl ArchiveMemberRecord {
pub fn new(path: impl Into<String>, kind: impl Into<String>) -> Self {
- Self { path: path.into(), kind: kind.into() }
+ Self {
+ path: path.into(),
+ kind: kind.into(),
+ }
}
}