feat: enhance bootstrap script handling and improve command parsing logic
This commit is contained in:
@@ -346,9 +346,13 @@ mod tests {
|
|||||||
args: vec!["--component".into(), "runtime".into()],
|
args: vec!["--component".into(), "runtime".into()],
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let fakeroot_output = fs::read_to_string(&fakeroot_log)?;
|
if !crate::fakeroot::is_root() {
|
||||||
assert!(fakeroot_output.contains("--"));
|
let fakeroot_output = fs::read_to_string(&fakeroot_log)?;
|
||||||
assert!(fakeroot_output.contains("cmake"));
|
assert!(fakeroot_output.contains("--"));
|
||||||
|
assert!(fakeroot_output.contains("cmake"));
|
||||||
|
} else {
|
||||||
|
assert!(!fakeroot_log.exists());
|
||||||
|
}
|
||||||
|
|
||||||
let cmake_output = fs::read_to_string(&cmake_log)?;
|
let cmake_output = fs::read_to_string(&cmake_log)?;
|
||||||
assert!(cmake_output.contains(&format!("DESTDIR={}", destdir.display())));
|
assert!(cmake_output.contains(&format!("DESTDIR={}", destdir.display())));
|
||||||
|
|||||||
+358
-20
@@ -3,6 +3,7 @@
|
|||||||
use crate::fakeroot;
|
use crate::fakeroot;
|
||||||
use crate::package::PackageSpec;
|
use crate::package::PackageSpec;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use std::collections::BTreeSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{BufRead, Write};
|
use std::io::{BufRead, Write};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
@@ -12,6 +13,7 @@ use walkdir::WalkDir;
|
|||||||
|
|
||||||
const STAGED_SCRIPTS_DIR: &str = "scripts";
|
const STAGED_SCRIPTS_DIR: &str = "scripts";
|
||||||
const DEFERRED_HOOKS_FILE_REL: &str = "var/lib/depot/deferred-hooks.tsv";
|
const DEFERRED_HOOKS_FILE_REL: &str = "var/lib/depot/deferred-hooks.tsv";
|
||||||
|
const BOOTSTRAP_BIN_DIR_REL: &str = "var/lib/depot/bootstrap/bin";
|
||||||
const ALL_HOOKS: [Hook; 6] = [
|
const ALL_HOOKS: [Hook; 6] = [
|
||||||
Hook::PreInstall,
|
Hook::PreInstall,
|
||||||
Hook::PostInstall,
|
Hook::PostInstall,
|
||||||
@@ -371,7 +373,18 @@ fn should_bootstrap_host_shell(should_chroot: bool, is_root: bool, shell_exists:
|
|||||||
should_chroot && is_root && !shell_exists
|
should_chroot && is_root && !shell_exists
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_chroot_filesystems(rootfs: &Path, bootstrap_host_shell: bool) -> Result<ChrootMountGuard> {
|
fn bootstrap_hook_path_env() -> String {
|
||||||
|
format!(
|
||||||
|
"/{}:{}",
|
||||||
|
BOOTSTRAP_BIN_DIR_REL,
|
||||||
|
crate::runtime_env::safe_script_path()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_chroot_filesystems(
|
||||||
|
rootfs: &Path,
|
||||||
|
bootstrap_script_path: Option<&Path>,
|
||||||
|
) -> Result<ChrootMountGuard> {
|
||||||
let proc_dir = rootfs.join("proc");
|
let proc_dir = rootfs.join("proc");
|
||||||
let dev_dir = rootfs.join("dev");
|
let dev_dir = rootfs.join("dev");
|
||||||
let dev_pts_dir = dev_dir.join("pts");
|
let dev_pts_dir = dev_dir.join("pts");
|
||||||
@@ -418,8 +431,8 @@ fn mount_chroot_filesystems(rootfs: &Path, bootstrap_host_shell: bool) -> Result
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if bootstrap_host_shell {
|
if let Some(script_path) = bootstrap_script_path {
|
||||||
bind_host_shell_into_chroot(&mut guard, rootfs)?;
|
bind_host_shell_into_chroot(&mut guard, rootfs, script_path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
maybe_bind_host_file_into_chroot(&mut guard, rootfs, Path::new("/etc/resolv.conf"))?;
|
maybe_bind_host_file_into_chroot(&mut guard, rootfs, Path::new("/etc/resolv.conf"))?;
|
||||||
@@ -492,12 +505,28 @@ fn prepare_host_file_bind_target(rootfs: &Path, target_rel: &Path) -> Result<Pat
|
|||||||
Ok(target)
|
Ok(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_host_shell_into_chroot(guard: &mut ChrootMountGuard, rootfs: &Path) -> Result<()> {
|
fn bind_host_shell_into_chroot(
|
||||||
|
guard: &mut ChrootMountGuard,
|
||||||
|
rootfs: &Path,
|
||||||
|
script_path: &Path,
|
||||||
|
) -> Result<()> {
|
||||||
let shell_path =
|
let shell_path =
|
||||||
fs::canonicalize("/bin/sh").context("Failed to resolve host /bin/sh for hook bootstrap")?;
|
fs::canonicalize("/bin/sh").context("Failed to resolve host /bin/sh for hook bootstrap")?;
|
||||||
bind_host_file_into_chroot_at_required(guard, rootfs, &shell_path, Path::new("bin/sh"))?;
|
bind_host_file_into_chroot_at_required(guard, rootfs, &shell_path, Path::new("bin/sh"))?;
|
||||||
|
|
||||||
for dependency in collect_host_shell_dependencies(&shell_path)? {
|
let mut dependency_paths = BTreeSet::new();
|
||||||
|
for dependency in collect_host_binary_dependencies(&shell_path)? {
|
||||||
|
dependency_paths.insert(dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in collect_bootstrap_tool_bindings(script_path)? {
|
||||||
|
bind_host_file_into_chroot_at_required(guard, rootfs, &tool.host_path, &tool.target_rel)?;
|
||||||
|
for dependency in collect_host_binary_dependencies(&tool.host_path)? {
|
||||||
|
dependency_paths.insert(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for dependency in dependency_paths {
|
||||||
let target_rel = dependency
|
let target_rel = dependency
|
||||||
.strip_prefix(Path::new("/"))
|
.strip_prefix(Path::new("/"))
|
||||||
.with_context(|| format!("Expected absolute host path: {}", dependency.display()))?;
|
.with_context(|| format!("Expected absolute host path: {}", dependency.display()))?;
|
||||||
@@ -507,23 +536,31 @@ fn bind_host_shell_into_chroot(guard: &mut ChrootMountGuard, rootfs: &Path) -> R
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_host_shell_dependencies(shell_path: &Path) -> Result<Vec<PathBuf>> {
|
fn collect_host_binary_dependencies(binary_path: &Path) -> Result<Vec<PathBuf>> {
|
||||||
let output = Command::new("ldd")
|
let output = Command::new("ldd")
|
||||||
.arg(shell_path)
|
.arg(binary_path)
|
||||||
.output()
|
.output()
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to inspect shared-library dependencies for {}",
|
"Failed to inspect shared-library dependencies for {}",
|
||||||
shell_path.display()
|
binary_path.display()
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
bail!("ldd failed for {}: {}", shell_path.display(), stderr.trim());
|
bail!(
|
||||||
|
"ldd failed for {}: {}",
|
||||||
|
binary_path.display(),
|
||||||
|
stderr.trim()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout = String::from_utf8(output.stdout)
|
let stdout = String::from_utf8(output.stdout).with_context(|| {
|
||||||
.with_context(|| format!("ldd returned non-UTF-8 output for {}", shell_path.display()))?;
|
format!(
|
||||||
|
"ldd returned non-UTF-8 output for {}",
|
||||||
|
binary_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
parse_ldd_dependency_paths(&stdout)
|
parse_ldd_dependency_paths(&stdout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,6 +597,273 @@ fn parse_ldd_dependency_paths(output: &str) -> Result<Vec<PathBuf>> {
|
|||||||
Ok(deps)
|
Ok(deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct BootstrapToolBinding {
|
||||||
|
host_path: PathBuf,
|
||||||
|
target_rel: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_bootstrap_tool_bindings(script_path: &Path) -> Result<Vec<BootstrapToolBinding>> {
|
||||||
|
let script = fs::read_to_string(script_path).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to read lifecycle hook for bootstrap tool detection: {}",
|
||||||
|
script_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut bindings = BTreeSet::new();
|
||||||
|
for command in parse_hook_command_candidates(&script) {
|
||||||
|
if is_shell_builtin(&command) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.starts_with('/') {
|
||||||
|
let host_path = fs::canonicalize(&command).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to resolve hook command path for bootstrap: {}",
|
||||||
|
command
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let target_rel = Path::new(&command)
|
||||||
|
.strip_prefix(Path::new("/"))
|
||||||
|
.with_context(|| format!("Expected absolute hook command path: {}", command))?
|
||||||
|
.to_path_buf();
|
||||||
|
bindings.insert(BootstrapToolBinding {
|
||||||
|
host_path,
|
||||||
|
target_rel,
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(host_path) = resolve_host_tool_path(&command)? {
|
||||||
|
bindings.insert(BootstrapToolBinding {
|
||||||
|
host_path,
|
||||||
|
target_rel: Path::new(BOOTSTRAP_BIN_DIR_REL).join(&command),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bindings.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_hook_command_candidates(script: &str) -> Vec<String> {
|
||||||
|
let mut commands = BTreeSet::new();
|
||||||
|
|
||||||
|
for line in script.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.is_empty() || trimmed.starts_with('#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokens = tokenize_hook_script_line(trimmed);
|
||||||
|
let mut expect_command = true;
|
||||||
|
for token in tokens {
|
||||||
|
if token.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(token.as_str(), "&&" | "||" | ";" | "|") {
|
||||||
|
expect_command = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !expect_command {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if looks_like_env_assignment(&token) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_shell_reserved_word(&token) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_shell_builtin(&token) {
|
||||||
|
expect_command = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.insert(token);
|
||||||
|
expect_command = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.into_iter().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tokenize_hook_script_line(line: &str) -> Vec<String> {
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
let mut chars = line.chars().peekable();
|
||||||
|
let mut quote: Option<char> = None;
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
if let Some(active_quote) = quote {
|
||||||
|
current.push(ch);
|
||||||
|
if ch == active_quote {
|
||||||
|
quote = None;
|
||||||
|
} else if ch == '\\'
|
||||||
|
&& active_quote == '"'
|
||||||
|
&& let Some(next) = chars.next()
|
||||||
|
{
|
||||||
|
current.push(next);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ch {
|
||||||
|
'\'' | '"' => {
|
||||||
|
current.push(ch);
|
||||||
|
quote = Some(ch);
|
||||||
|
}
|
||||||
|
'\\' => {
|
||||||
|
current.push(ch);
|
||||||
|
if let Some(next) = chars.next() {
|
||||||
|
current.push(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'#' => break,
|
||||||
|
'&' | '|' => {
|
||||||
|
if !current.is_empty() {
|
||||||
|
tokens.push(std::mem::take(&mut current));
|
||||||
|
}
|
||||||
|
if chars.peek() == Some(&ch) {
|
||||||
|
let _ = chars.next();
|
||||||
|
tokens.push(format!("{ch}{ch}"));
|
||||||
|
} else {
|
||||||
|
tokens.push(ch.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
';' => {
|
||||||
|
if !current.is_empty() {
|
||||||
|
tokens.push(std::mem::take(&mut current));
|
||||||
|
}
|
||||||
|
tokens.push(ch.to_string());
|
||||||
|
}
|
||||||
|
c if c.is_whitespace() => {
|
||||||
|
if !current.is_empty() {
|
||||||
|
tokens.push(std::mem::take(&mut current));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => current.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current.is_empty() {
|
||||||
|
tokens.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_shell_reserved_word(token: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
token,
|
||||||
|
"!" | "{"
|
||||||
|
| "}"
|
||||||
|
| "("
|
||||||
|
| ")"
|
||||||
|
| "then"
|
||||||
|
| "do"
|
||||||
|
| "done"
|
||||||
|
| "else"
|
||||||
|
| "elif"
|
||||||
|
| "fi"
|
||||||
|
| "if"
|
||||||
|
| "case"
|
||||||
|
| "esac"
|
||||||
|
| "for"
|
||||||
|
| "in"
|
||||||
|
| "while"
|
||||||
|
| "until"
|
||||||
|
| "function"
|
||||||
|
| "select"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn looks_like_env_assignment(token: &str) -> bool {
|
||||||
|
let Some((key, _value)) = token.split_once('=') else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
!key.is_empty()
|
||||||
|
&& key
|
||||||
|
.chars()
|
||||||
|
.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
|
||||||
|
&& !key.chars().next().is_some_and(|ch| ch.is_ascii_digit())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_shell_builtin(token: &str) -> bool {
|
||||||
|
matches!(
|
||||||
|
token,
|
||||||
|
"." | ":"
|
||||||
|
| "["
|
||||||
|
| "alias"
|
||||||
|
| "bg"
|
||||||
|
| "break"
|
||||||
|
| "cd"
|
||||||
|
| "command"
|
||||||
|
| "continue"
|
||||||
|
| "echo"
|
||||||
|
| "eval"
|
||||||
|
| "exec"
|
||||||
|
| "exit"
|
||||||
|
| "export"
|
||||||
|
| "false"
|
||||||
|
| "fg"
|
||||||
|
| "getopts"
|
||||||
|
| "hash"
|
||||||
|
| "jobs"
|
||||||
|
| "printf"
|
||||||
|
| "pwd"
|
||||||
|
| "read"
|
||||||
|
| "readonly"
|
||||||
|
| "return"
|
||||||
|
| "set"
|
||||||
|
| "shift"
|
||||||
|
| "test"
|
||||||
|
| "times"
|
||||||
|
| "trap"
|
||||||
|
| "true"
|
||||||
|
| "type"
|
||||||
|
| "ulimit"
|
||||||
|
| "umask"
|
||||||
|
| "unalias"
|
||||||
|
| "unset"
|
||||||
|
| "wait"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_host_tool_path(command: &str) -> Result<Option<PathBuf>> {
|
||||||
|
for dir in crate::runtime_env::safe_script_path().split(':') {
|
||||||
|
if dir.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let candidate = Path::new(dir).join(command);
|
||||||
|
if !candidate.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let metadata = candidate.metadata().with_context(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to inspect host tool candidate {}",
|
||||||
|
candidate.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
if metadata.permissions().mode() & 0o111 == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(Some(fs::canonicalize(&candidate).with_context(|| {
|
||||||
|
format!("Failed to resolve host tool path {}", candidate.display())
|
||||||
|
})?));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
fn run_script_with_rootfs_context(
|
fn run_script_with_rootfs_context(
|
||||||
script_path: &Path,
|
script_path: &Path,
|
||||||
rootfs: &Path,
|
rootfs: &Path,
|
||||||
@@ -582,7 +886,14 @@ fn run_script_with_rootfs_context(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let run_result = if let Ok(rel) = script_path.strip_prefix(rootfs) {
|
let run_result = if let Ok(rel) = script_path.strip_prefix(rootfs) {
|
||||||
run_hook_script_in_chroot(rootfs, rel, pkg_name, hook, false, bootstrap_host_shell)
|
run_hook_script_in_chroot(
|
||||||
|
rootfs,
|
||||||
|
rel,
|
||||||
|
pkg_name,
|
||||||
|
hook,
|
||||||
|
false,
|
||||||
|
bootstrap_host_shell.then_some(script_path),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
run_hook_script_contents_in_chroot(
|
run_hook_script_contents_in_chroot(
|
||||||
rootfs,
|
rootfs,
|
||||||
@@ -590,7 +901,7 @@ fn run_script_with_rootfs_context(
|
|||||||
pkg_name,
|
pkg_name,
|
||||||
hook,
|
hook,
|
||||||
false,
|
false,
|
||||||
bootstrap_host_shell,
|
bootstrap_host_shell.then_some(script_path),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -657,10 +968,15 @@ fn run_hook_script_in_chroot(
|
|||||||
pkg_name: &str,
|
pkg_name: &str,
|
||||||
hook: Hook,
|
hook: Hook,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
bootstrap_host_shell: bool,
|
bootstrap_script_path: Option<&Path>,
|
||||||
) -> Result<std::process::ExitStatus> {
|
) -> Result<std::process::ExitStatus> {
|
||||||
let _mounts = mount_chroot_filesystems(rootfs, bootstrap_host_shell)?;
|
let _mounts = mount_chroot_filesystems(rootfs, bootstrap_script_path)?;
|
||||||
let rel_script = format!("./{}", rel_script.to_string_lossy());
|
let rel_script = format!("./{}", rel_script.to_string_lossy());
|
||||||
|
let path_env = if bootstrap_script_path.is_some() {
|
||||||
|
bootstrap_hook_path_env()
|
||||||
|
} else {
|
||||||
|
crate::runtime_env::safe_script_path().to_string()
|
||||||
|
};
|
||||||
let mut cmd = Command::new("chroot");
|
let mut cmd = Command::new("chroot");
|
||||||
cmd.arg(rootfs)
|
cmd.arg(rootfs)
|
||||||
.arg("/bin/sh")
|
.arg("/bin/sh")
|
||||||
@@ -672,7 +988,7 @@ fn run_hook_script_in_chroot(
|
|||||||
.env("DEPOT_ROOTFS", "/")
|
.env("DEPOT_ROOTFS", "/")
|
||||||
.env("DEPOT_ACTION", hook.action())
|
.env("DEPOT_ACTION", hook.action())
|
||||||
.env("DEPOT_PHASE", hook.phase())
|
.env("DEPOT_PHASE", hook.phase())
|
||||||
.env("PATH", crate::runtime_env::safe_script_path());
|
.env("PATH", &path_env);
|
||||||
if quiet {
|
if quiet {
|
||||||
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
||||||
}
|
}
|
||||||
@@ -691,9 +1007,14 @@ fn run_hook_script_contents_in_chroot(
|
|||||||
pkg_name: &str,
|
pkg_name: &str,
|
||||||
hook: Hook,
|
hook: Hook,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
bootstrap_host_shell: bool,
|
bootstrap_script_path: Option<&Path>,
|
||||||
) -> Result<std::process::ExitStatus> {
|
) -> Result<std::process::ExitStatus> {
|
||||||
let _mounts = mount_chroot_filesystems(rootfs, bootstrap_host_shell)?;
|
let _mounts = mount_chroot_filesystems(rootfs, bootstrap_script_path)?;
|
||||||
|
let path_env = if bootstrap_script_path.is_some() {
|
||||||
|
bootstrap_hook_path_env()
|
||||||
|
} else {
|
||||||
|
crate::runtime_env::safe_script_path().to_string()
|
||||||
|
};
|
||||||
let mut cmd = Command::new("chroot");
|
let mut cmd = Command::new("chroot");
|
||||||
cmd.arg(rootfs)
|
cmd.arg(rootfs)
|
||||||
.arg("/bin/sh")
|
.arg("/bin/sh")
|
||||||
@@ -702,7 +1023,7 @@ fn run_hook_script_contents_in_chroot(
|
|||||||
.env("DEPOT_ROOTFS", "/")
|
.env("DEPOT_ROOTFS", "/")
|
||||||
.env("DEPOT_ACTION", hook.action())
|
.env("DEPOT_ACTION", hook.action())
|
||||||
.env("DEPOT_PHASE", hook.phase())
|
.env("DEPOT_PHASE", hook.phase())
|
||||||
.env("PATH", crate::runtime_env::safe_script_path())
|
.env("PATH", &path_env)
|
||||||
.stdin(Stdio::piped());
|
.stdin(Stdio::piped());
|
||||||
if quiet {
|
if quiet {
|
||||||
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
cmd.stdout(Stdio::null()).stderr(Stdio::null());
|
||||||
@@ -875,7 +1196,7 @@ pub fn run_deferred_hooks_if_possible(rootfs: &Path) -> Result<()> {
|
|||||||
&item.pkg_name,
|
&item.pkg_name,
|
||||||
item.hook,
|
item.hook,
|
||||||
true,
|
true,
|
||||||
false,
|
None,
|
||||||
) {
|
) {
|
||||||
Ok(status) if status.success() => {}
|
Ok(status) if status.success() => {}
|
||||||
Ok(status) => {
|
Ok(status) => {
|
||||||
@@ -1480,6 +1801,23 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn deferred_hooks_file_roundtrip() {
|
fn deferred_hooks_file_roundtrip() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user