use eyre::{Result, format_err}; use log::info; use std::path::Path; use tokio::fs; use crate::{base64_decode, File}; 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<()> { let path = chroot(root, &file.path); let path = Path::new(&path); if !dry_run && let Some(parent) = path.parent() { fs::create_dir_all(parent).await?; } use crate::{FileKind as K, FilePart as P}; match file.kind().as_ref() { K::Skip => { info!("{}: kind is skip", file.path); return Ok(()) }, K::Content(content) => { if dry_run { info!( "would create {} ({} bytes from content)", file.path, content.len() ); } else { fs::write(path, content.as_bytes()).await?; } } K::Content64(content) => { let content = base64_decode(content)?; if dry_run { info!( "would create {} ({} bytes from content64)", file.path, content.len() ); } else { fs::write(path, content).await? } } 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? } } K::Dir => { if dry_run { info!("would create {} (directory)", file.path); } else { fs::create_dir(path).await?; } } K::Symlink(tgt) => { if dry_run { info!("would create {} (symlink to {})", file.path, tgt); } else { let _ = fs::remove_file(path).await; // we're ln --force fs::symlink(tgt, path).await?; } } } if dry_run { return Ok(()); } if file.is_symlink() { set_perms(path, file.mode).await?; } info!("created {}", file.path); Ok(()) } pub async fn set_perms(path: impl AsRef, mode: Option) -> 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 == '/')) }