From 3d4b74f10c964ec1d564217c7ec3eaaee8c4837f Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Wed, 23 Jul 2025 14:56:07 +0200 Subject: [PATCH 1/8] fix(acap-build): Use '--update=none' instead of '-n' --- crates/acap-build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index 16c7f387..20a3fcc9 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -46,7 +46,7 @@ fn copy, Q: AsRef>( cp.arg("--preserve=mode"); } - cp.arg("-dn").arg(src.as_os_str()).arg(dst.as_os_str()); + cp.arg("-d").arg("--update=none").arg(src.as_os_str()).arg(dst.as_os_str()); if !cp.status()?.success() { bail!("Failed to copy symlink: {}", src.display()); From bae6d61b16594251abd67f1cfd0bc0018450d2f3 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 11:16:07 +0200 Subject: [PATCH 2/8] Revert "fix(acap-build): Use '--update=none' instead of '-n'" This reverts commit 3d4b74f10c964ec1d564217c7ec3eaaee8c4837f. --- crates/acap-build/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index 20a3fcc9..16c7f387 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -46,7 +46,7 @@ fn copy, Q: AsRef>( cp.arg("--preserve=mode"); } - cp.arg("-d").arg("--update=none").arg(src.as_os_str()).arg(dst.as_os_str()); + cp.arg("-dn").arg(src.as_os_str()).arg(dst.as_os_str()); if !cp.status()?.success() { bail!("Failed to copy symlink: {}", src.display()); From 494dc332a46fb7fa7392baa40ddb71c050026443 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 11:17:36 +0200 Subject: [PATCH 3/8] Archive and unpack files instead of copying --- Cargo.lock | 1 + crates/acap-build/Cargo.toml | 1 + crates/acap-build/src/lib.rs | 100 ++++++++--------------------------- 3 files changed, 24 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca09715d..23cb9db3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,6 +16,7 @@ dependencies = [ "semver", "serde", "serde_json", + "tar", "tempdir", "tempfile", ] diff --git a/crates/acap-build/Cargo.toml b/crates/acap-build/Cargo.toml index a34eb279..286ca5cb 100644 --- a/crates/acap-build/Cargo.toml +++ b/crates/acap-build/Cargo.toml @@ -16,3 +16,4 @@ serde_json = { workspace = true, features = ["preserve_order"] } tempfile = { workspace = true } regex = { workspace = true } tempdir = { workspace = true } +tar = { workspace = true } \ No newline at end of file diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index 16c7f387..4ef9480f 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -5,7 +5,7 @@ use std::{ env, ffi::OsString, fs, - io::Write, + io::{Cursor, Write}, os::unix::fs::PermissionsExt, path::{Path, PathBuf}, process::Command, @@ -18,6 +18,8 @@ use log::{debug, info}; use semver::Version; use serde_json::Value; +use tar::{Builder, Archive}; + use crate::files::{ cgi_conf::CgiConf, manifest::Manifest, package_conf::PackageConf, param_conf::ParamConf, }; @@ -27,65 +29,6 @@ mod json_ext; mod files; -// TODO: Find a better way to support reproducible builds -fn copy, Q: AsRef>( - src: P, - dst: Q, - copy_permissions: bool, -) -> anyhow::Result<()> { - let src = src.as_ref(); - let dst = dst.as_ref(); - if dst.symlink_metadata().is_ok() { - bail!("Path already exists {dst:?}"); - } - if src.is_symlink() { - // FIXME: Copy symlink in Rust - let mut cp = Command::new("cp"); - - if copy_permissions { - cp.arg("--preserve=mode"); - } - - cp.arg("-dn").arg(src.as_os_str()).arg(dst.as_os_str()); - - if !cp.status()?.success() { - bail!("Failed to copy symlink: {}", src.display()); - } - } else if copy_permissions { - fs::copy(src, dst)?; - } else { - let mut src = fs::File::open(src)?; - let mut dst = fs::File::create(dst)?; - std::io::copy(&mut src, &mut dst)?; - } - Ok(()) -} - -fn copy_recursively(src: &Path, dst: &Path, copy_permissions: bool) -> anyhow::Result<()> { - if !src.is_dir() { - copy(src, dst, copy_permissions)?; - debug!("Created reg {dst:?}"); - return Ok(()); - } - match fs::create_dir(dst) { - Ok(()) => { - debug!("Created dir {dst:?}"); - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(e), - }?; - for entry in fs::read_dir(src)? { - let entry = entry?; - copy_recursively( - &entry.path(), - &dst.join(entry.file_name()), - copy_permissions, - )?; - } - Ok(()) -} - enum AcapBuildImpl { Reference, Equivalent, @@ -111,6 +54,7 @@ pub struct AppBuilder<'a> { files: Vec, default_architecture: Architecture, app_name: String, + ar: Option>>>, } impl<'a> AppBuilder<'a> { @@ -123,6 +67,8 @@ impl<'a> AppBuilder<'a> { let manifest: Value = serde_json::from_reader(fs::File::open(manifest)?)?; let manifest = Manifest::new(manifest, default_architecture)?; let app_name = manifest.try_find_app_name()?.to_string(); + let mut ar = Builder::new(Cursor::new(Vec::new())); + ar.follow_symlinks(false); Ok(Self { preserve_permissions, staging_dir, @@ -130,6 +76,7 @@ impl<'a> AppBuilder<'a> { app_name, files: Vec::new(), default_architecture, + ar: Some(ar), }) } @@ -161,20 +108,15 @@ impl<'a> AppBuilder<'a> { Ok(self) } - // TODO: Remove the file system copy pub fn add_as(&mut self, path: &Path, name: &str) -> anyhow::Result { - let dst = self.staging_dir.join(name); - if dst.symlink_metadata().is_ok() { - bail!("Cannot add {path:?} because {name} already exists"); - } - copy_recursively(path, &dst, self.preserve_permissions)?; - self.files.push(name.to_string()); - if name == self.app_name && !self.preserve_permissions { - let mut permissions = fs::metadata(&dst)?.permissions(); - let mode = permissions.mode(); - permissions.set_mode(mode | 0o111); - fs::set_permissions(&dst, permissions)?; + if path.symlink_metadata().unwrap().file_type().is_dir() { + self.ar.as_mut().unwrap().append_dir_all(name, path)?; + } else { + self.ar.as_mut().unwrap().append_path_with_name(path, name)?; } + + let dst = self.staging_dir.join(name); + debug!("Added {name} from {path:?}"); Ok(dst) } @@ -188,7 +130,13 @@ impl<'a> AppBuilder<'a> { } /// Build the EAP and return its path. - pub fn build(self) -> anyhow::Result { + pub fn build(mut self) -> anyhow::Result { + let cursor = Cursor::new(self.ar.take().unwrap().into_inner()?.into_inner()); + let mut archive = Archive::new(cursor); + archive.set_preserve_permissions(self.preserve_permissions); + archive.set_preserve_mtime(false); + archive.unpack(self.staging_dir)?; + match AcapBuildImpl::from_env_or_default()? { AcapBuildImpl::Reference => { debug!("Using acap-build"); @@ -206,14 +154,9 @@ impl<'a> AppBuilder<'a> { let Self { staging_dir, default_architecture, - manifest, .. } = &self; - fs::File::create_new(staging_dir.join("manifest.json")) - .context("creating manifest.json")? - .write_all(manifest.try_to_string()?.as_bytes())?; - let mut acap_build = Command::new("acap-build"); acap_build.args(["--build", "no-build"]); for file in self.additional_files() { @@ -424,6 +367,7 @@ impl<'a> AppBuilder<'a> { [ Some(self.app_name.as_str()), Some("LICENSE"), + Some("manifest.json"), self.manifest.try_find_post_install_script().ok(), self.manifest.try_find_pre_uninstall_script().ok(), ] From 4b95df285755dee6fa6a2b991c96f505fa737ff2 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 11:48:12 +0200 Subject: [PATCH 4/8] Write manifest like before --- crates/acap-build/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index 4ef9480f..aa319f7a 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -154,9 +154,14 @@ impl<'a> AppBuilder<'a> { let Self { staging_dir, default_architecture, + manifest, .. } = &self; + fs::File::create_new(staging_dir.join("manifest.json")) + .context("creating manifest.json")? + .write_all(manifest.try_to_string()?.as_bytes())?; + let mut acap_build = Command::new("acap-build"); acap_build.args(["--build", "no-build"]); for file in self.additional_files() { @@ -367,7 +372,6 @@ impl<'a> AppBuilder<'a> { [ Some(self.app_name.as_str()), Some("LICENSE"), - Some("manifest.json"), self.manifest.try_find_post_install_script().ok(), self.manifest.try_find_pre_uninstall_script().ok(), ] From b7bdd3bc61855211b5602a4801a60b887b18aceb Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 14:39:55 +0200 Subject: [PATCH 5/8] Add archived files to self.files --- crates/acap-build/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index aa319f7a..d90734c3 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -116,6 +116,7 @@ impl<'a> AppBuilder<'a> { } let dst = self.staging_dir.join(name); + self.files.push(name.to_string()); debug!("Added {name} from {path:?}"); Ok(dst) From c03fa105f872b484667d6c6f5794537fae6e1990 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 15:12:47 +0200 Subject: [PATCH 6/8] set_overwrite(false) like cp -n --- crates/acap-build/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index d90734c3..c0a1243f 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -136,6 +136,7 @@ impl<'a> AppBuilder<'a> { let mut archive = Archive::new(cursor); archive.set_preserve_permissions(self.preserve_permissions); archive.set_preserve_mtime(false); + archive.set_overwrite(false); archive.unpack(self.staging_dir)?; match AcapBuildImpl::from_env_or_default()? { From 66dcf5f244a8e9f842b665de5bb9b6a482fc46d5 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Thu, 24 Jul 2025 15:12:59 +0200 Subject: [PATCH 7/8] Remove set_preserve_permissions It doesn't do what you think... only extended permissions (like suid). --- crates/acap-build/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index c0a1243f..cf447812 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -134,7 +134,6 @@ impl<'a> AppBuilder<'a> { pub fn build(mut self) -> anyhow::Result { let cursor = Cursor::new(self.ar.take().unwrap().into_inner()?.into_inner()); let mut archive = Archive::new(cursor); - archive.set_preserve_permissions(self.preserve_permissions); archive.set_preserve_mtime(false); archive.set_overwrite(false); archive.unpack(self.staging_dir)?; From a488b54a3b9ace03cca7b478de0b62fb51f64c97 Mon Sep 17 00:00:00 2001 From: Philip Johansson Date: Mon, 28 Jul 2025 13:39:15 +0200 Subject: [PATCH 8/8] Respect preserve_permissions --- crates/acap-build/src/lib.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs index cf447812..e20729e4 100644 --- a/crates/acap-build/src/lib.rs +++ b/crates/acap-build/src/lib.rs @@ -136,7 +136,18 @@ impl<'a> AppBuilder<'a> { let mut archive = Archive::new(cursor); archive.set_preserve_mtime(false); archive.set_overwrite(false); - archive.unpack(self.staging_dir)?; + + for entry in archive.entries()? { + let mut entry = entry?; + if !self.preserve_permissions { + entry.set_mask(0o333); // Clear executable bit for everyone + if entry.path()?.to_str().expect("Valid unicode in path") == &self.app_name { + entry.set_mask(0o33); // Clear executable bit for everyone but user + } + } + + entry.unpack_in(&self.staging_dir)?; + } match AcapBuildImpl::from_env_or_default()? { AcapBuildImpl::Reference => {