feat(package): confine maintainer scripts via chroot with .SCRIPTS staging
Author:
Chris Tusa <chris.tusa@leafscale.com>
Date:
Jul 05, 2026 17:20
Changeset:
6a5359a64b22db9220a962b403f058371fed0cf4
Branch:
default
Changed files:
Diff
diff -r 4bd00dffc89d -r 6a5359a64b22 src/core/package.reef --- a/src/core/package.reef Sun Jul 05 17:14:50 2026 -0500 +++ b/src/core/package.reef Sun Jul 05 17:20:31 2026 -0500 @@ -546,26 +546,60 @@ // The single place a maintainer-script shell command is assembled. // is_sh true -> run under zsh; false -> execute the file directly. -// Phase 1: only the non-chroot (host) branch exists. fn build_script_cmd(ctx: ScriptCtx, script_path: string, script_name: string, args: string, is_sh: bool): string - // $CORAL_ROOT value, from the script's point of view: - // confined -> "/" (added in Task 8); host -> ctx.root - let coral_root = ctx.root - - mut body = "" - if is_sh - body = "zsh \"" + script_path + "\"" + mut cmd = "" + if ctx.do_chroot + // Confined: run inside <root>; from the script's view the target is "/". + let in_root_path = "/.SCRIPTS/" + script_name + mut body = "" + if is_sh + body = "zsh \"" + in_root_path + "\"" + else + body = "\"" + in_root_path + "\"" + end if + cmd = "chroot \"" + ctx.root + "\" env CORAL_ROOT=\"/\" " + body else - body = "\"" + script_path + "\"" + // Host: normal install or --no-chroot-scripts. Export CORAL_ROOT=ctx.root. + mut body = "" + if is_sh + body = "zsh \"" + script_path + "\"" + else + body = "\"" + script_path + "\"" + end if + cmd = "CORAL_ROOT=\"" + ctx.root + "\" " + body end if - mut cmd = "CORAL_ROOT=\"" + coral_root + "\" " + body if str.length(args) > 0 cmd = cmd + " " + args end if return cmd end build_script_cmd +// Copy a package's .SCRIPTS dir into <root>/.SCRIPTS so it is reachable post-chroot. +fn stage_scripts_in_root(pkg_scripts_dir: string, root: string): bool + let dest = path.join_path(root, ".SCRIPTS") + // Clean any stale staging first. + let rm_cmd = "rm -rf \"" + dest + "\"" + let rm_pid = process.process_spawn_shell(rm_cmd) + process.process_wait(rm_pid) + // Copy preserving perms/owner/symlinks. + let cp_cmd = "cp -a \"" + pkg_scripts_dir + "\" \"" + dest + "\"" + let pid = process.process_spawn_shell(cp_cmd) + if pid < 0 + return false + end if + let code = process.process_wait(pid) + return code == 0 +end stage_scripts_in_root + +// Remove staged scripts from <root>/.SCRIPTS. +proc unstage_scripts_in_root(root: string) + let dest = path.join_path(root, ".SCRIPTS") + let cmd = "rm -rf \"" + dest + "\"" + let pid = process.process_spawn_shell(cmd) + process.process_wait(pid) +end unstage_scripts_in_root + // Run a script from the .SCRIPTS directory fn run_script(ctx: ScriptCtx, pkg_dir: string, script_name: string, script_args: string): bool let scripts_dir = path.join_path(pkg_dir, ".SCRIPTS") @@ -575,15 +609,26 @@ return true end if + if ctx.do_chroot + if not stage_scripts_in_root(scripts_dir, ctx.root) + return false + end if + end if + let is_sh = str.ends_with(script_name, ".sh") let cmd = build_script_cmd(ctx, script_path, script_name, script_args, is_sh) let pid = process.process_spawn_shell(cmd) - if pid < 0 - return false + mut ok = false + if pid >= 0 + let exit_code = process.process_wait(pid) + ok = exit_code == 0 end if - let exit_code = process.process_wait(pid) - return exit_code == 0 + + if ctx.do_chroot + unstage_scripts_in_root(ctx.root) + end if + return ok end run_script // Run pre-install script (passes $VERSION) @@ -679,15 +724,26 @@ return true end if + if ctx.do_chroot + if not stage_scripts_in_root(scripts_dir, ctx.root) + return false + end if + end if + let is_sh = str.ends_with(script_name, ".sh") let cmd = build_script_cmd(ctx, script_path, script_name, script_args, is_sh) let pid = process.process_spawn_shell(cmd) - if pid < 0 - return false + mut ok = false + if pid >= 0 + let exit_code = process.process_wait(pid) + ok = exit_code == 0 end if - let exit_code = process.process_wait(pid) - return exit_code == 0 + + if ctx.do_chroot + unstage_scripts_in_root(ctx.root) + end if + return ok end run_stored_script // Run pre-remove script from stored scripts directory (passes $VERSION) diff -r 4bd00dffc89d -r 6a5359a64b22 test/script_cmd_test.reef --- a/test/script_cmd_test.reef Sun Jul 05 17:14:50 2026 -0500 +++ b/test/script_cmd_test.reef Sun Jul 05 17:20:31 2026 -0500 @@ -40,6 +40,16 @@ package.build_script_cmd(normal, "/tmp/x/.SCRIPTS/pre-remove.sh", "pre-remove.sh", "", true), "CORAL_ROOT=\"/\" zsh \"/tmp/x/.SCRIPTS/pre-remove.sh\"") + // Confined: root "/mnt", do_chroot true -> chroot prefix, in-root /.SCRIPTS path, CORAL_ROOT="/" + let jail = package.new_script_ctx("/mnt", true) + expect("chroot .sh", + package.build_script_cmd(jail, "/tmp/x/.SCRIPTS/post-install.sh", "post-install.sh", "\"1.0\"", true), + "chroot \"/mnt\" env CORAL_ROOT=\"/\" zsh \"/.SCRIPTS/post-install.sh\" \"1.0\"") + + expect("chroot bare", + package.build_script_cmd(jail, "/tmp/x/.SCRIPTS/post-install", "post-install", "\"1.0\"", false), + "chroot \"/mnt\" env CORAL_ROOT=\"/\" \"/.SCRIPTS/post-install\" \"1.0\"") + println("") if g_fail == 0 println("RESULT: ALL PASS")