444 lines
14 KiB
Rust
444 lines
14 KiB
Rust
use super::*;
|
|
use crate::package::{
|
|
Alternatives, Build, BuildFlags, BuildType, Dependencies, PackageInfo, Source,
|
|
};
|
|
#[cfg(unix)]
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
fn mk_spec(spec_dir: &Path) -> PackageSpec {
|
|
PackageSpec {
|
|
package: PackageInfo {
|
|
name: "foo".into(),
|
|
real_name: None,
|
|
version: "1.0".into(),
|
|
revision: 1,
|
|
description: "d".into(),
|
|
homepage: "h".into(),
|
|
abi_breaking: false,
|
|
built_against: Vec::new(),
|
|
license: vec!["MIT".into()],
|
|
},
|
|
packages: Vec::new(),
|
|
alternatives: Alternatives::default(),
|
|
manual_sources: Vec::new(),
|
|
source: vec![Source {
|
|
url: "https://example.com/foo.tar.gz".into(),
|
|
sha256: "skip".into(),
|
|
extract_dir: "foo".into(),
|
|
patches: Vec::new(),
|
|
post_extract: Vec::new(),
|
|
cherry_pick: Vec::new(),
|
|
}],
|
|
build: Build {
|
|
build_type: BuildType::Custom,
|
|
flags: BuildFlags::default(),
|
|
},
|
|
dependencies: Dependencies::default(),
|
|
package_alternatives: Default::default(),
|
|
package_dependencies: Default::default(),
|
|
spec_dir: spec_dir.to_path_buf(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stage_scripts_from_spec_dir_copies_tree() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let spec_dir = tmp.path().join("spec");
|
|
let destdir = tmp.path().join("dest");
|
|
std::fs::create_dir_all(spec_dir.join("scripts/lib")).unwrap();
|
|
std::fs::create_dir_all(&destdir).unwrap();
|
|
|
|
std::fs::write(spec_dir.join("scripts/pre_install"), "echo pre").unwrap();
|
|
std::fs::write(spec_dir.join("scripts/lib/common.sh"), "echo lib").unwrap();
|
|
|
|
let spec = mk_spec(&spec_dir);
|
|
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
|
|
assert!(staged);
|
|
assert!(destdir.join("scripts/pre_install").exists());
|
|
assert!(destdir.join("scripts/lib/common.sh").exists());
|
|
#[cfg(unix)]
|
|
{
|
|
let mode = std::fs::metadata(destdir.join("scripts/pre_install"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode();
|
|
assert_ne!(mode & 0o111, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_executes_script() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
std::fs::write(
|
|
scripts.join("pre_install"),
|
|
"echo \"$DEPOT_ACTION:$DEPOT_PHASE:$DEPOT_PACKAGE\" > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs, "foo").unwrap();
|
|
assert!(ran);
|
|
assert_eq!(
|
|
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
|
|
"install:pre:foo\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_uses_safe_script_path() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
std::fs::write(
|
|
scripts.join("pre_install"),
|
|
"echo \"$PATH\" > \"$DEPOT_ROOTFS/path.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs, "foo").unwrap();
|
|
assert!(ran);
|
|
assert_eq!(
|
|
std::fs::read_to_string(rootfs.join("path.out"))
|
|
.unwrap()
|
|
.trim_end(),
|
|
crate::runtime_env::safe_script_path()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_accepts_compact_script_name() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
std::fs::write(
|
|
scripts.join("postinstall.sh"),
|
|
"echo compact > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "foo").unwrap();
|
|
assert!(ran);
|
|
assert_eq!(
|
|
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
|
|
"compact\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_prefers_lib32_specific_script_name() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
std::fs::write(
|
|
scripts.join("post_install"),
|
|
"echo generic > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
std::fs::write(
|
|
scripts.join("post_install-lib32"),
|
|
"echo lib32 > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "lib32-foo").unwrap();
|
|
assert!(ran);
|
|
assert_eq!(
|
|
std::fs::read_to_string(rootfs.join("hook.out")).unwrap(),
|
|
"lib32\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_rejects_ambiguous_names() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
std::fs::write(scripts.join("pre_update"), "echo one").unwrap();
|
|
std::fs::write(scripts.join("pre-update"), "echo two").unwrap();
|
|
|
|
let err = run_hook_if_present(&scripts, Hook::PreUpdate, &rootfs, "foo")
|
|
.expect_err("expected ambiguous script names to fail");
|
|
assert!(err.to_string().contains("Ambiguous lifecycle hook"));
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_with_relative_rootfs_uses_correct_script_and_env_paths() {
|
|
let cwd = std::env::current_dir().unwrap();
|
|
let tmp = tempfile::Builder::new()
|
|
.prefix("depot-hook-rel-rootfs-")
|
|
.tempdir_in(&cwd)
|
|
.unwrap();
|
|
let rootfs_abs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&rootfs_abs).unwrap();
|
|
let rootfs_rel = rootfs_abs.strip_prefix(&cwd).unwrap().to_path_buf();
|
|
let scripts = rootfs_rel.join("scripts");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
|
|
std::fs::write(
|
|
scripts.join("pre_install"),
|
|
"echo ok > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PreInstall, &rootfs_rel, "foo").unwrap();
|
|
assert!(ran);
|
|
assert_eq!(
|
|
std::fs::read_to_string(rootfs_abs.join("hook.out")).unwrap(),
|
|
"ok\n"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_bootstrap_host_shell_only_for_chrooted_root_installs_without_shell() {
|
|
assert!(should_bootstrap_host_shell(true, true, false));
|
|
assert!(!should_bootstrap_host_shell(true, true, true));
|
|
assert!(!should_bootstrap_host_shell(true, false, false));
|
|
assert!(!should_bootstrap_host_shell(false, true, false));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_ldd_dependency_paths_extracts_absolute_paths() {
|
|
let parsed = parse_ldd_dependency_paths(
|
|
"linux-vdso.so.1 (0x0000)\nlibc.so.6 => /lib/libc.so.6 (0x0000)\n/lib64/ld-linux-x86-64.so.2 (0x0000)\nlibc.so.6 => /lib/libc.so.6 (0x0001)\n",
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
parsed,
|
|
vec![
|
|
PathBuf::from("/lib/libc.so.6"),
|
|
PathBuf::from("/lib64/ld-linux-x86-64.so.2")
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_ldd_dependency_paths_rejects_missing_dependencies() {
|
|
let err = parse_ldd_dependency_paths("libedit.so.0 => not found\n")
|
|
.expect_err("expected ldd parse to fail when a dependency is missing");
|
|
assert!(
|
|
err.to_string()
|
|
.contains("Missing shared-library dependency")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_hook_command_candidates_finds_commands_after_assignments_and_operators() {
|
|
let commands = parse_hook_command_candidates(
|
|
"PATH=/tmp:$PATH grep -q foo etc/shells || echo foo >> etc/shells\ncat \"$DEPOT_ROOTFS/usr/bin/find\" | sed 's/x/y/'\n",
|
|
);
|
|
assert_eq!(
|
|
commands,
|
|
vec!["cat".to_string(), "grep".to_string(), "sed".to_string()]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_hook_command_candidates_ignores_builtins_and_control_words() {
|
|
let commands = parse_hook_command_candidates("if true; then export FOO=bar; echo hi; fi\n");
|
|
assert!(commands.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn deferred_hooks_file_roundtrip() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let path = tmp.path().join("hooks.tsv");
|
|
let hooks = vec![
|
|
DeferredHook {
|
|
pkg_name: "foo".into(),
|
|
hook: Hook::PostInstall,
|
|
script_rel: PathBuf::from("usr/share/depot/foo/scripts/post_install"),
|
|
},
|
|
DeferredHook {
|
|
pkg_name: "bar".into(),
|
|
hook: Hook::PostUpdate,
|
|
script_rel: PathBuf::from("usr/share/depot/bar/scripts/post_update"),
|
|
},
|
|
];
|
|
write_deferred_hooks(&path, &hooks).unwrap();
|
|
let loaded = read_deferred_hooks(&path).unwrap();
|
|
assert_eq!(loaded.len(), hooks.len());
|
|
assert_eq!(loaded[0].pkg_name, hooks[0].pkg_name);
|
|
assert_eq!(loaded[0].hook, hooks[0].hook);
|
|
assert_eq!(loaded[0].script_rel, hooks[0].script_rel);
|
|
assert_eq!(loaded[1].pkg_name, hooks[1].pkg_name);
|
|
assert_eq!(loaded[1].hook, hooks[1].hook);
|
|
assert_eq!(loaded[1].script_rel, hooks[1].script_rel);
|
|
}
|
|
|
|
#[test]
|
|
fn queue_deferred_hook_dedupes_entries() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
queue_deferred_hook(
|
|
tmp.path(),
|
|
"foo",
|
|
Hook::PostInstall,
|
|
Path::new("usr/share/depot/foo/scripts/post_install"),
|
|
)
|
|
.unwrap();
|
|
queue_deferred_hook(
|
|
tmp.path(),
|
|
"foo",
|
|
Hook::PostInstall,
|
|
Path::new("usr/share/depot/foo/scripts/post_install"),
|
|
)
|
|
.unwrap();
|
|
|
|
let loaded = read_deferred_hooks(&deferred_hooks_file(tmp.path())).unwrap();
|
|
assert_eq!(loaded.len(), 1);
|
|
assert_eq!(loaded[0].pkg_name, "foo");
|
|
assert_eq!(loaded[0].hook, Hook::PostInstall);
|
|
assert_eq!(
|
|
loaded[0].script_rel,
|
|
PathBuf::from("usr/share/depot/foo/scripts/post_install")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn sync_staged_scripts_to_rootfs_replaces_existing_tree() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let rootfs = tmp.path().join("root");
|
|
let staged = tmp.path().join("staged");
|
|
std::fs::create_dir_all(staged.join("scripts")).unwrap();
|
|
|
|
let installed = installed_scripts_dir(&rootfs, "foo");
|
|
std::fs::create_dir_all(&installed).unwrap();
|
|
std::fs::write(installed.join("old"), "old").unwrap();
|
|
|
|
std::fs::write(staged.join("scripts/post_install"), "echo ok").unwrap();
|
|
let has_scripts =
|
|
sync_staged_scripts_to_rootfs(&staged.join("scripts"), &rootfs, "foo").unwrap();
|
|
|
|
assert!(has_scripts);
|
|
let installed = installed_scripts_dir(&rootfs, "foo");
|
|
assert!(!installed.join("old").exists());
|
|
assert!(installed.join("post_install").exists());
|
|
#[cfg(unix)]
|
|
{
|
|
let mode = std::fs::metadata(installed.join("post_install"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode();
|
|
assert_ne!(mode & 0o111, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sync_staged_scripts_to_rootfs_removes_old_when_none_staged() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let rootfs = tmp.path().join("root");
|
|
let staged = tmp.path().join("staged");
|
|
std::fs::create_dir_all(&staged).unwrap();
|
|
|
|
let installed = installed_scripts_dir(&rootfs, "foo");
|
|
std::fs::create_dir_all(&installed).unwrap();
|
|
std::fs::write(installed.join("pre_remove"), "echo old").unwrap();
|
|
|
|
let has_scripts =
|
|
sync_staged_scripts_to_rootfs(&staged.join("scripts"), &rootfs, "foo").unwrap();
|
|
assert!(!has_scripts);
|
|
assert!(!installed_scripts_dir(&rootfs, "foo").exists());
|
|
}
|
|
|
|
#[test]
|
|
fn stage_scripts_from_spec_dir_stages_legacy_root_hook() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let spec_dir = tmp.path().join("spec");
|
|
let destdir = tmp.path().join("dest");
|
|
std::fs::create_dir_all(&spec_dir).unwrap();
|
|
std::fs::create_dir_all(&destdir).unwrap();
|
|
|
|
std::fs::write(spec_dir.join("postinstall.sh"), "echo post").unwrap();
|
|
|
|
let spec = mk_spec(&spec_dir);
|
|
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
|
|
assert!(staged);
|
|
assert!(destdir.join("scripts/post_install").exists());
|
|
#[cfg(unix)]
|
|
{
|
|
let mode = std::fs::metadata(destdir.join("scripts/post_install"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode();
|
|
assert_ne!(mode & 0o111, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stage_scripts_from_spec_dir_stages_lib32_prefixed_legacy_root_hook() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let spec_dir = tmp.path().join("spec");
|
|
let destdir = tmp.path().join("dest");
|
|
std::fs::create_dir_all(&spec_dir).unwrap();
|
|
std::fs::create_dir_all(&destdir).unwrap();
|
|
|
|
std::fs::write(spec_dir.join("lib32-postinstall.sh"), "echo lib32-post").unwrap();
|
|
|
|
let mut spec = mk_spec(&spec_dir);
|
|
spec.package.name = "lib32-foo".into();
|
|
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
|
|
assert!(staged);
|
|
assert!(destdir.join("scripts/post_install").exists());
|
|
#[cfg(unix)]
|
|
{
|
|
let mode = std::fs::metadata(destdir.join("scripts/post_install"))
|
|
.unwrap()
|
|
.permissions()
|
|
.mode();
|
|
assert_ne!(mode & 0o111, 0);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn stage_scripts_from_spec_dir_lib32_ignores_generic_legacy_root_hook() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let spec_dir = tmp.path().join("spec");
|
|
let destdir = tmp.path().join("dest");
|
|
std::fs::create_dir_all(&spec_dir).unwrap();
|
|
std::fs::create_dir_all(&destdir).unwrap();
|
|
|
|
// No lib32-prefixed hook; native-only scripts must NOT be staged for lib32 packages.
|
|
std::fs::write(spec_dir.join("postinstall.sh"), "echo fallback").unwrap();
|
|
|
|
let mut spec = mk_spec(&spec_dir);
|
|
spec.package.name = "lib32-foo".into();
|
|
let staged = stage_scripts_from_spec_dir(&spec, &destdir).unwrap();
|
|
assert!(!staged);
|
|
assert!(!destdir.join("scripts/post_install").exists());
|
|
}
|
|
|
|
#[test]
|
|
fn run_hook_if_present_lib32_ignores_generic_script() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let scripts = tmp.path().join("scripts");
|
|
let rootfs = tmp.path().join("root");
|
|
std::fs::create_dir_all(&scripts).unwrap();
|
|
std::fs::create_dir_all(&rootfs).unwrap();
|
|
|
|
// Only a generic script exists; lib32 package must NOT execute it.
|
|
std::fs::write(
|
|
scripts.join("post_install"),
|
|
"echo generic > \"$DEPOT_ROOTFS/hook.out\"\n",
|
|
)
|
|
.unwrap();
|
|
|
|
let ran = run_hook_if_present(&scripts, Hook::PostInstall, &rootfs, "lib32-foo").unwrap();
|
|
assert!(!ran);
|
|
assert!(!rootfs.join("hook.out").exists());
|
|
}
|