2026-05-03 11:34:38 +02:00
|
|
|
use eyre::{Result, format_err};
|
2025-07-17 15:42:08 +02:00
|
|
|
use log::info;
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
use tokio::fs;
|
|
|
|
|
|
2026-05-03 11:34:38 +02:00
|
|
|
use crate::{base64_decode, File};
|
2026-02-10 21:23:11 +01:00
|
|
|
|
2026-05-03 11:34:38 +02:00
|
|
|
pub async fn files(files: &[File], root: &str, dry_run: bool) -> Result<()> {
|
|
|
|
|
for f in files {
|
|
|
|
|
if let Err(e) = file(f, root, dry_run).await {
|
|
|
|
|
return Err(format_err!("{}: {e}", f.path))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn file(file: &File, root: &str, dry_run: bool) -> Result<()> {
|
2025-07-17 15:42:08 +02:00
|
|
|
let path = chroot(root, &file.path);
|
|
|
|
|
let path = Path::new(&path);
|
|
|
|
|
|
2026-02-21 18:15:39 +01:00
|
|
|
if !dry_run && let Some(parent) = path.parent() {
|
2025-07-17 15:42:08 +02:00
|
|
|
fs::create_dir_all(parent).await?;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 21:27:16 +02:00
|
|
|
use crate::{FileKind as K, FilePart as P};
|
2026-05-03 11:34:38 +02:00
|
|
|
match file.kind().as_ref() {
|
|
|
|
|
K::Skip => {
|
|
|
|
|
info!("{}: kind is skip", file.path);
|
|
|
|
|
return Ok(())
|
|
|
|
|
},
|
2026-02-21 18:15:39 +01:00
|
|
|
K::Content(content) => {
|
|
|
|
|
if dry_run {
|
|
|
|
|
info!(
|
|
|
|
|
"would create {} ({} bytes from content)",
|
|
|
|
|
file.path,
|
|
|
|
|
content.len()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
fs::write(path, content.as_bytes()).await?;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-25 20:01:50 +01:00
|
|
|
K::Content64(content) => {
|
2026-02-10 21:23:11 +01:00
|
|
|
let content = base64_decode(content)?;
|
2026-02-21 18:15:39 +01:00
|
|
|
if dry_run {
|
|
|
|
|
info!(
|
|
|
|
|
"would create {} ({} bytes from content64)",
|
|
|
|
|
file.path,
|
|
|
|
|
content.len()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
fs::write(path, content).await?
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-27 21:27:16 +02:00
|
|
|
K::Parts(parts) => {
|
|
|
|
|
let mut assembly = Vec::new();
|
|
|
|
|
for part in parts {
|
|
|
|
|
match part {
|
|
|
|
|
P::Content(content) => assembly.extend(content.as_bytes()),
|
|
|
|
|
P::Content64(content) => assembly.extend(base64_decode(content)?),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if dry_run {
|
|
|
|
|
info!(
|
|
|
|
|
"would create {} ({} bytes from parts)",
|
|
|
|
|
file.path,
|
|
|
|
|
assembly.len()
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
fs::write(path, assembly).await?
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 11:34:38 +02:00
|
|
|
K::Dir => {
|
2026-02-21 18:15:39 +01:00
|
|
|
if dry_run {
|
|
|
|
|
info!("would create {} (directory)", file.path);
|
|
|
|
|
} else {
|
|
|
|
|
fs::create_dir(path).await?;
|
|
|
|
|
}
|
2026-01-25 20:01:50 +01:00
|
|
|
}
|
2026-02-21 18:15:39 +01:00
|
|
|
K::Symlink(tgt) => {
|
|
|
|
|
if dry_run {
|
|
|
|
|
info!("would create {} (symlink to {})", file.path, tgt);
|
|
|
|
|
} else {
|
2026-05-03 11:34:38 +02:00
|
|
|
let _ = fs::remove_file(path).await; // we're ln --force
|
2026-02-21 18:15:39 +01:00
|
|
|
fs::symlink(tgt, path).await?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dry_run {
|
2026-05-03 11:34:38 +02:00
|
|
|
return Ok(());
|
2025-07-17 15:42:08 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-03 11:34:38 +02:00
|
|
|
if file.is_symlink() {
|
|
|
|
|
set_perms(path, file.mode).await?;
|
2025-07-17 15:42:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info!("created {}", file.path);
|
2026-05-03 11:34:38 +02:00
|
|
|
Ok(())
|
2025-07-17 15:42:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn set_perms(path: impl AsRef<Path>, mode: Option<u32>) -> std::io::Result<()> {
|
|
|
|
|
if let Some(mode) = mode.filter(|m| *m != 0) {
|
|
|
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
|
let mode = std::fs::Permissions::from_mode(mode);
|
|
|
|
|
fs::set_permissions(path, mode).await?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn chroot(root: &str, path: &str) -> String {
|
|
|
|
|
format!("{root}/{}", path.trim_start_matches(|c| c == '/'))
|
|
|
|
|
}
|