kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit b8daff4719a017bf3f397759e6cd85245b1f6ceb
parent 324f8448f5eb6f6259e3870d714024379995936d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 13:55:42 -0700

Set executable permissions on linked outputs

Diffstat:
Mdriver/cc.c | 7+++++++
Mdriver/driver.h | 4++++
Mdriver/env.c | 12++++++++++++
Mdriver/ld.c | 13++++++++-----
Atest/driver/run.sh | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test.mk | 9+++++++--
6 files changed, 119 insertions(+), 7 deletions(-)

diff --git a/driver/cc.c b/driver/cc.c @@ -1574,6 +1574,13 @@ static int cc_run_link_exe(DriverEnv* env, const CcOptions* o, out: if (out_w) cfree_writer_close(out_w); + if (rc == 0 && o->output_path) { + if (driver_mark_executable_output(o->output_path) != 0) { + driver_errf(CC_TOOL, "failed to set executable mode: %s", + o->output_path); + rc = 1; + } + } if (script && pipe) cfree_link_script_free(cfree_pipeline_compiler(pipe), script); if (pipe) driver_pipeline_free(pipe); diff --git a/driver/driver.h b/driver/driver.h @@ -151,6 +151,10 @@ CfreeWriter *driver_stdout_writer(DriverEnv *); * loops don't slurp file contents on a hit. */ int driver_path_exists(const char *path); +/* Set a linked binary output's final mode according to the active umask. + * Returns 0 on success, nonzero on chmod failure. */ +int driver_mark_executable_output(const char *path); + /* Diagnostic printing to host stderr. Format is `"<tool>: <fmt>\n"`. */ void driver_errf(const char *tool, const char *fmt, ...); diff --git a/driver/env.c b/driver/env.c @@ -1244,6 +1244,18 @@ int driver_path_exists(const char *path) { return stat(path, &sb) == 0; } +int driver_mark_executable_output(const char *path) { + mode_t mask; + mode_t mode; + + if (!path) + return 1; + mask = umask(0); + (void)umask(mask); + mode = (mode_t)(0777 & ~mask); + return chmod(path, mode) == 0 ? 0 : 1; +} + void *driver_alloc(DriverEnv *e, size_t n) { return e->heap->alloc(e->heap, n, _Alignof(max_align_t)); } diff --git a/driver/ld.c b/driver/ld.c @@ -1,5 +1,4 @@ #include <stdint.h> -#include <sys/stat.h> #include "driver.h" #include "lib_resolve.h" @@ -857,11 +856,15 @@ static int ld_run_link(LdOptions* o) { out: if (writer) cfree_writer_close(writer); - /* Match GNU ld / lld: a successful link chmods the output to 0755 - * so the file is directly runnable. Done after closing the writer - * so the bits are stable on disk. */ + /* Match compiler/linker drivers: successful link outputs get executable + * file modes while still respecting the process umask. Done after closing + * the writer so the bits are stable on disk. */ if (rc == 0 && o->output_path) { - (void)chmod(o->output_path, 0755); + if (driver_mark_executable_output(o->output_path) != 0) { + driver_errf(LD_TOOL, "failed to set executable mode: %s", + o->output_path); + rc = 1; + } } if (script && compiler) cfree_link_script_free(compiler, script); if (compiler) driver_compiler_free(compiler); diff --git a/test/driver/run.sh b/test/driver/run.sh @@ -0,0 +1,81 @@ +#!/bin/sh +# Driver-level permission checks for executable outputs. + +set -u + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../.." && pwd) + +CFREE="${CFREE:-$repo_root/build/cfree}" + +if [ ! -x "$CFREE" ]; then + echo "driver: cfree binary not found at $CFREE" >&2 + exit 2 +fi + +stat_mode() { + if mode=$(stat -f '%Lp' "$1" 2>/dev/null); then + printf '%s' "$mode" + else + stat -c '%a' "$1" + fi +} + +work=$(mktemp -d "${TMPDIR:-/tmp}/cfree-driver-test.XXXXXX") +trap 'rm -rf "$work"' EXIT + +cat > "$work/main.c" <<'SRC' +int main(void) { return 0; } +int _start(void) { return 0; } +SRC + +pass=0 +fail=0 + +check_mode() { + name=$1 + path=$2 + want=$3 + got=$(stat_mode "$path") + if [ "$got" = "$want" ]; then + printf 'PASS %s\n' "$name" + pass=$((pass + 1)) + else + printf 'FAIL %s (mode %s, want %s)\n' "$name" "$got" "$want" + fail=$((fail + 1)) + fi +} + +if (umask 077; "$CFREE" cc "$work/main.c" -o "$work/cc-exe") \ + > "$work/cc.out" 2> "$work/cc.err"; then + check_mode "cc-executable-mode" "$work/cc-exe" 700 +else + printf 'FAIL cc-executable-mode (cfree cc failed)\n' + sed 's/^/ | /' "$work/cc.err" + fail=$((fail + 1)) +fi + +if (umask 077; "$CFREE" cc -c "$work/main.c" -o "$work/main.o") \ + > "$work/cc-c.out" 2> "$work/cc-c.err"; then + : > "$work/ld-exe" + chmod 0644 "$work/ld-exe" + if (umask 077; "$CFREE" ld "$work/main.o" -o "$work/ld-exe") \ + > "$work/ld.out" 2> "$work/ld.err"; then + check_mode "ld-executable-mode" "$work/ld-exe" 700 + else + printf 'FAIL ld-executable-mode (cfree ld failed)\n' + sed 's/^/ | /' "$work/ld.err" + fail=$((fail + 1)) + fi +else + printf 'FAIL ld-executable-mode (cfree cc -c failed)\n' + sed 's/^/ | /' "$work/cc-c.err" + fail=$((fail + 1)) +fi + +total=$((pass + fail)) +if [ "$fail" -gt 0 ]; then + printf '\ndriver: %d/%d passed\n' "$pass" "$total" + exit 1 +fi +printf '\ndriver: %d/%d passed\n' "$pass" "$total" diff --git a/test/test.mk b/test/test.mk @@ -1,5 +1,7 @@ # Data-driven tests. Included from the top-level Makefile. # +# - test-driver: narrow CLI behavior checks that do not belong to a specific +# frontend/linker corpus. Depends on the cfree driver binary. # - test-lex / test-pp: C frontend runners; depend on the cfree driver # binary, which today fails to link (most of libcfree is header-only). # - test-elf: ELF roundtrip harness in test/elf/; depends only on @@ -25,9 +27,12 @@ # asm_parse / cfree_disasm_iter_* are still stubs; the harness builds # and runs end-to-end so the wiring stays exercised. See doc/ASM.md. -.PHONY: test test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg-api test-toy test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-wasm-front test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64 +.PHONY: test test-driver test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-cg-api test-toy test-opt test-dwarf test-debug test-parse test-parse-err test-asm test-wasm-front test-isa test-aa64-inline test-libc test-musl test-glibc test-lib-deps test-smoke-x64 test-smoke-rv64 -test: test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-toy test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps +test: test-driver test-lex test-pp test-pp-err test-elf test-ar test-ar-driver test-link test-toy test-dwarf test-debug test-parse test-parse-err test-asm test-isa test-aa64-inline test-lib-deps + +test-driver: bin + @CFREE=$(abspath $(BIN)) sh test/driver/run.sh test-lex: bin @CFREE=$(abspath $(BIN)) test/lex/run.sh