diff options
author | Robert Hensing <roberth@users.noreply.github.com> | 2021-12-10 00:45:30 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-10 00:45:30 +0100 |
commit | 9fb7d91888fa24a7f96fd1915da2d4bc5e9b834e (patch) | |
tree | 2fcea539901c914ae08d574e8e88e32ffa1aca33 /pkgs | |
parent | 296081d9fa5a0606de52c72c3a9f2eb19f42a6ca (diff) | |
parent | 39b0aa415c67b6bcaabbb7110344503cc184575a (diff) |
Merge pull request #124556 from bergkvist/bergkvist/make-c-wrapper
Generate tiny compiled binary for wrapping executables
Diffstat (limited to 'pkgs')
34 files changed, 717 insertions, 0 deletions
diff --git a/pkgs/build-support/setup-hooks/make-binary-wrapper.sh b/pkgs/build-support/setup-hooks/make-binary-wrapper.sh new file mode 100644 index 0000000000000..abc929cb89dbc --- /dev/null +++ b/pkgs/build-support/setup-hooks/make-binary-wrapper.sh @@ -0,0 +1,384 @@ + +set -euo pipefail + +# Assert that FILE exists and is executable +# +# assertExecutable FILE +assertExecutable() { + local file="$1" + [[ -f "$file" && -x "$file" ]] || \ + die "Cannot wrap '$file' because it is not an executable file" +} + +# Generate a binary executable wrapper for wrapping an executable. +# The binary is compiled from generated C-code using gcc. +# makeWrapper EXECUTABLE OUT_PATH ARGS + +# ARGS: +# --argv0 NAME : set name of executed process to NAME +# (otherwise it’s called …-wrapped) +# --inherit-argv0 : the executable inherits argv0 from the wrapper. +# (use instead of --argv0 '$0') +# --set VAR VAL : add VAR with value VAL to the executable’s +# environment +# --set-default VAR VAL : like --set, but only adds VAR if not already set in +# the environment +# --unset VAR : remove VAR from the environment +# --chdir DIR : change working directory (use instead of --run "cd DIR") +# --add-flags FLAGS : add FLAGS to invocation of executable + +# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP +# --suffix + +# To troubleshoot a binary wrapper after you compiled it, +# use the `strings` command or open the binary file in a text editor. +makeWrapper() { + assertExecutable "$1" + makeDocumentedCWrapper "$1" "${@:3}" | \ + @CC@ \ + -Wall -Werror -Wpedantic \ + -Os \ + -x c \ + -o "$2" - +} + +# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...> +wrapProgram() { + local prog="$1" + local hidden + + assertExecutable "$prog" + + hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped + while [ -e "$hidden" ]; do + hidden="${hidden}_" + done + mv "$prog" "$hidden" + # Silence warning about unexpanded $0: + # shellcheck disable=SC2016 + makeWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}" +} + +# Generate source code for the wrapper in such a way that the wrapper inputs +# will still be readable even after compilation +# makeDocumentedCWrapper EXECUTABLE ARGS +# ARGS: same as makeWrapper +makeDocumentedCWrapper() { + local src docs + src=$(makeCWrapper "$@") + docs=$(docstring "$@") + printf '%s\n\n' "$src" + printf '%s\n' "$docs" +} + +# makeCWrapper EXECUTABLE ARGS +# ARGS: same as makeWrapper +makeCWrapper() { + local argv0 inherit_argv0 n params cmd main flagsBefore flags executable length + local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf + executable=$(escapeStringLiteral "$1") + params=("$@") + length=${#params[*]} + for ((n = 1; n < length; n += 1)); do + p="${params[n]}" + case $p in + --set) + cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}") + main="$main$cmd"$'\n' + n=$((n + 2)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' + ;; + --set-default) + cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}") + main="$main$cmd"$'\n' + uses_stdio=1 + uses_assert_success=1 + n=$((n + 2)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' + ;; + --unset) + cmd=$(unsetEnv "${params[n + 1]}") + main="$main$cmd"$'\n' + uses_stdio=1 + uses_assert_success=1 + n=$((n + 1)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' + ;; + --prefix) + cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") + main="$main$cmd"$'\n' + uses_prefix=1 + uses_asprintf=1 + uses_stdio=1 + uses_assert_success=1 + uses_assert=1 + n=$((n + 3)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' + ;; + --suffix) + cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") + main="$main$cmd"$'\n' + uses_suffix=1 + uses_asprintf=1 + uses_stdio=1 + uses_assert_success=1 + uses_assert=1 + n=$((n + 3)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' + ;; + --chdir) + cmd=$(changeDir "${params[n + 1]}") + main="$main$cmd"$'\n' + uses_stdio=1 + uses_assert_success=1 + n=$((n + 1)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' + ;; + --add-flags) + flags="${params[n + 1]}" + flagsBefore="$flagsBefore $flags" + uses_assert=1 + n=$((n + 1)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' + ;; + --argv0) + argv0=$(escapeStringLiteral "${params[n + 1]}") + inherit_argv0= + n=$((n + 1)) + [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' + ;; + --inherit-argv0) + # Whichever comes last of --argv0 and --inherit-argv0 wins + inherit_argv0=1 + ;; + *) # Using an error macro, we will make sure the compiler gives an understandable error message + main="$main#error makeCWrapper: Unknown argument ${p}"$'\n' + ;; + esac + done + # shellcheck disable=SC2086 + [ -z "$flagsBefore" ] || main="$main"${main:+$'\n'}$(addFlags $flagsBefore)$'\n'$'\n' + [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n' + main="${main}return execv(\"${executable}\", argv);"$'\n' + + [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */" + printf '%s\n' "#include <unistd.h>" + printf '%s\n' "#include <stdlib.h>" + [ -z "$uses_assert" ] || printf '%s\n' "#include <assert.h>" + [ -z "$uses_stdio" ] || printf '%s\n' "#include <stdio.h>" + [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)" + [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)" + [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)" + printf '\n%s' "int main(int argc, char **argv) {" + printf '\n%s' "$(indent4 "$main")" + printf '\n%s\n' "}" +} + +addFlags() { + local result n flag flags var + var="argv_tmp" + flags=("$@") + for ((n = 0; n < ${#flags[*]}; n += 1)); do + flag=$(escapeStringLiteral "${flags[$n]}") + result="$result${var}[$((n+1))] = \"$flag\";"$'\n' + done + printf '%s\n' "char **$var = calloc($((n+1)) + argc, sizeof(*$var));" + printf '%s\n' "assert($var != NULL);" + printf '%s\n' "${var}[0] = argv[0];" + printf '%s' "$result" + printf '%s\n' "for (int i = 1; i < argc; ++i) {" + printf '%s\n' " ${var}[$n + i] = argv[i];" + printf '%s\n' "}" + printf '%s\n' "${var}[$n + argc] = NULL;" + printf '%s\n' "argv = $var;" +} + +# chdir DIR +changeDir() { + local dir + dir=$(escapeStringLiteral "$1") + printf '%s' "assert_success(chdir(\"$dir\"));" +} + +# prefix ENV SEP VAL +setEnvPrefix() { + local env sep val + env=$(escapeStringLiteral "$1") + sep=$(escapeStringLiteral "$2") + val=$(escapeStringLiteral "$3") + printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");" + assertValidEnvName "$1" +} + +# suffix ENV SEP VAL +setEnvSuffix() { + local env sep val + env=$(escapeStringLiteral "$1") + sep=$(escapeStringLiteral "$2") + val=$(escapeStringLiteral "$3") + printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");" + assertValidEnvName "$1" +} + +# setEnv KEY VALUE +setEnv() { + local key value + key=$(escapeStringLiteral "$1") + value=$(escapeStringLiteral "$2") + printf '%s' "putenv(\"$key=$value\");" + assertValidEnvName "$1" +} + +# setDefaultEnv KEY VALUE +setDefaultEnv() { + local key value + key=$(escapeStringLiteral "$1") + value=$(escapeStringLiteral "$2") + printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));" + assertValidEnvName "$1" +} + +# unsetEnv KEY +unsetEnv() { + local key + key=$(escapeStringLiteral "$1") + printf '%s' "assert_success(unsetenv(\"$key\"));" + assertValidEnvName "$1" +} + +# Makes it safe to insert STRING within quotes in a C String Literal. +# escapeStringLiteral STRING +escapeStringLiteral() { + local result + result=${1//$'\\'/$'\\\\'} + result=${result//\"/'\"'} + result=${result//$'\n'/"\n"} + result=${result//$'\r'/"\r"} + printf '%s' "$result" +} + +# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines +# indent4 TEXT_BLOCK +indent4() { + printf '%s' "$1" | awk '{ if ($0 != "") { print " "$0 } else { print $0 }}' +} + +assertValidEnvName() { + case "$1" in + *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";; + "") printf '\n%s\n' "#error Environment variable name can't be empty.";; + esac +} + +setEnvPrefixFn() { + printf '%s' "\ +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} +" +} + +setEnvSuffixFn() { + printf '%s' "\ +void set_env_suffix(char *env, char *sep, char *suffix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, suffix, 1)); + } +} +" +} + +# Embed a C string which shows up as readable text in the compiled binary wrapper +# documentationString ARGS +docstring() { + printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral " + + +# ------------------------------------------------------------------------------------ +# The C-code for this binary wrapper has been generated using the following command: + + +makeCWrapper $(formatArgs "$@") + + +# (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell) +# ------------------------------------------------------------------------------------ + + +")\";" +} + +# formatArgs EXECUTABLE ARGS +formatArgs() { + printf '%s' "$1" + shift + while [ $# -gt 0 ]; do + case "$1" in + --set) + formatArgsLine 2 "$@" + shift 2 + ;; + --set-default) + formatArgsLine 2 "$@" + shift 2 + ;; + --unset) + formatArgsLine 1 "$@" + shift 1 + ;; + --prefix) + formatArgsLine 3 "$@" + shift 3 + ;; + --suffix) + formatArgsLine 3 "$@" + shift 3 + ;; + --chdir) + formatArgsLine 1 "$@" + shift 1 + ;; + --add-flags) + formatArgsLine 1 "$@" + shift 1 + ;; + --argv0) + formatArgsLine 1 "$@" + shift 1 + ;; + --inherit-argv0) + formatArgsLine 0 "$@" + ;; + esac + shift + done + printf '%s\n' "" +} + +# formatArgsLine ARG_COUNT ARGS +formatArgsLine() { + local ARG_COUNT LENGTH + ARG_COUNT=$1 + LENGTH=$# + shift + printf '%s' $' \\\n '"$1" + shift + while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do + printf ' %s' "${1@Q}" + shift + done +} diff --git a/pkgs/test/default.nix b/pkgs/test/default.nix index e3ef7839c4b4d..b73617daa8952 100644 --- a/pkgs/test/default.nix +++ b/pkgs/test/default.nix @@ -35,6 +35,8 @@ with pkgs; macOSSierraShared = callPackage ./macos-sierra-shared {}; + make-binary-wrapper = callPackage ./make-binary-wrapper { inherit makeBinaryWrapper; }; + cross = callPackage ./cross {}; php = recurseIntoAttrs (callPackages ./php {}); diff --git a/pkgs/test/make-binary-wrapper/add-flags.c b/pkgs/test/make-binary-wrapper/add-flags.c new file mode 100644 index 0000000000000..7ce682c6be647 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/add-flags.c @@ -0,0 +1,21 @@ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> + +int main(int argc, char **argv) { + char **argv_tmp = calloc(5 + argc, sizeof(*argv_tmp)); + assert(argv_tmp != NULL); + argv_tmp[0] = argv[0]; + argv_tmp[1] = "-x"; + argv_tmp[2] = "-y"; + argv_tmp[3] = "-z"; + argv_tmp[4] = "-abc"; + for (int i = 1; i < argc; ++i) { + argv_tmp[4 + i] = argv[i]; + } + argv_tmp[4 + argc] = NULL; + argv = argv_tmp; + + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/add-flags.cmdline b/pkgs/test/make-binary-wrapper/add-flags.cmdline new file mode 100644 index 0000000000000..f840c772e3494 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/add-flags.cmdline @@ -0,0 +1,2 @@ + --add-flags "-x -y -z" \ + --add-flags -abc diff --git a/pkgs/test/make-binary-wrapper/add-flags.env b/pkgs/test/make-binary-wrapper/add-flags.env new file mode 100644 index 0000000000000..9b8d1fb9f6a5d --- /dev/null +++ b/pkgs/test/make-binary-wrapper/add-flags.env @@ -0,0 +1,6 @@ +CWD=SUBST_CWD +SUBST_ARGV0 +-x +-y +-z +-abc diff --git a/pkgs/test/make-binary-wrapper/argv0.c b/pkgs/test/make-binary-wrapper/argv0.c new file mode 100644 index 0000000000000..70c36889dc890 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/argv0.c @@ -0,0 +1,7 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + argv[0] = "alternative-name"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/argv0.cmdline b/pkgs/test/make-binary-wrapper/argv0.cmdline new file mode 100644 index 0000000000000..1cadce8312a44 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/argv0.cmdline @@ -0,0 +1 @@ + --argv0 alternative-name diff --git a/pkgs/test/make-binary-wrapper/argv0.env b/pkgs/test/make-binary-wrapper/argv0.env new file mode 100644 index 0000000000000..04c13d32ee6d6 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/argv0.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +alternative-name diff --git a/pkgs/test/make-binary-wrapper/basic.c b/pkgs/test/make-binary-wrapper/basic.c new file mode 100644 index 0000000000000..1c1266181377a --- /dev/null +++ b/pkgs/test/make-binary-wrapper/basic.c @@ -0,0 +1,7 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/basic.cmdline b/pkgs/test/make-binary-wrapper/basic.cmdline new file mode 100644 index 0000000000000..e69de29bb2d1d --- /dev/null +++ b/pkgs/test/make-binary-wrapper/basic.cmdline diff --git a/pkgs/test/make-binary-wrapper/basic.env b/pkgs/test/make-binary-wrapper/basic.env new file mode 100644 index 0000000000000..b0da31959447c --- /dev/null +++ b/pkgs/test/make-binary-wrapper/basic.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/pkgs/test/make-binary-wrapper/chdir.c b/pkgs/test/make-binary-wrapper/chdir.c new file mode 100644 index 0000000000000..c67c695b1c3b9 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/chdir.c @@ -0,0 +1,11 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + assert_success(chdir("/tmp/foo")); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/chdir.cmdline b/pkgs/test/make-binary-wrapper/chdir.cmdline new file mode 100644 index 0000000000000..15235f20621c8 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/chdir.cmdline @@ -0,0 +1 @@ + --chdir /tmp/foo diff --git a/pkgs/test/make-binary-wrapper/chdir.env b/pkgs/test/make-binary-wrapper/chdir.env new file mode 100644 index 0000000000000..db129d68af741 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/chdir.env @@ -0,0 +1,2 @@ +CWD=/tmp/foo +SUBST_ARGV0 diff --git a/pkgs/test/make-binary-wrapper/combination.c b/pkgs/test/make-binary-wrapper/combination.c new file mode 100644 index 0000000000000..e9ce5f1d72440 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/combination.c @@ -0,0 +1,53 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} + +void set_env_suffix(char *env, char *sep, char *suffix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", existing, sep, suffix)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, suffix, 1)); + } +} + +int main(int argc, char **argv) { + assert_success(setenv("MESSAGE", "HELLO", 0)); + set_env_prefix("PATH", ":", "/usr/bin/"); + set_env_suffix("PATH", ":", "/usr/local/bin/"); + putenv("MESSAGE2=WORLD"); + + char **argv_tmp = calloc(4 + argc, sizeof(*argv_tmp)); + assert(argv_tmp != NULL); + argv_tmp[0] = argv[0]; + argv_tmp[1] = "-x"; + argv_tmp[2] = "-y"; + argv_tmp[3] = "-z"; + for (int i = 1; i < argc; ++i) { + argv_tmp[3 + i] = argv[i]; + } + argv_tmp[3 + argc] = NULL; + argv = argv_tmp; + + argv[0] = "my-wrapper"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/combination.cmdline b/pkgs/test/make-binary-wrapper/combination.cmdline new file mode 100644 index 0000000000000..fb3861235c8b4 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/combination.cmdline @@ -0,0 +1,6 @@ + --argv0 my-wrapper \ + --set-default MESSAGE HELLO \ + --prefix PATH : /usr/bin/ \ + --suffix PATH : /usr/local/bin/ \ + --add-flags "-x -y -z" \ + --set MESSAGE2 WORLD diff --git a/pkgs/test/make-binary-wrapper/combination.env b/pkgs/test/make-binary-wrapper/combination.env new file mode 100644 index 0000000000000..886420c01d1ea --- /dev/null +++ b/pkgs/test/make-binary-wrapper/combination.env @@ -0,0 +1,8 @@ +MESSAGE=HELLO +PATH=/usr/bin/:/usr/local/bin/ +MESSAGE2=WORLD +CWD=SUBST_CWD +my-wrapper +-x +-y +-z diff --git a/pkgs/test/make-binary-wrapper/default.nix b/pkgs/test/make-binary-wrapper/default.nix new file mode 100644 index 0000000000000..c5bb6970aac07 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/default.nix @@ -0,0 +1,54 @@ +{ lib, coreutils, python3, gcc, writeText, writeScript, runCommand, makeBinaryWrapper }: + +let + env = { nativeBuildInputs = [ makeBinaryWrapper ]; }; + envCheck = runCommand "envcheck" env '' + ${gcc}/bin/cc -Wall -Werror -Wpedantic -o $out ${./envcheck.c} + ''; + makeGoldenTest = testname: runCommand "test-wrapper_${testname}" env '' + mkdir -p /tmp/foo + + params=$(<"${./.}/${testname}.cmdline") + eval "makeCWrapper /send/me/flags $params" > wrapper.c + + diff wrapper.c "${./.}/${testname}.c" + + if [ -f "${./.}/${testname}.env" ]; then + eval "makeWrapper ${envCheck} wrapped $params" + env -i ./wrapped > env.txt + sed "s#SUBST_ARGV0#${envCheck}#;s#SUBST_CWD#$PWD#" \ + "${./.}/${testname}.env" > golden-env.txt + if ! diff env.txt golden-env.txt; then + echo "env/argv should be:" + cat golden-env.txt + echo "env/argv output is:" + cat env.txt + exit 1 + fi + else + # without a golden env, we expect the wrapper compilation to fail + ! eval "makeWrapper ${envCheck} wrapped $params" &> error.txt + fi + + cp wrapper.c $out + ''; + tests = let + names = [ + "add-flags" + "argv0" + "basic" + "chdir" + "combination" + "env" + "inherit-argv0" + "invalid-env" + "prefix" + "suffix" + ]; + f = name: lib.nameValuePair name (makeGoldenTest name); + in builtins.listToAttrs (builtins.map f names); +in writeText "make-binary-wrapper-test" '' + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (_: test: '' + "${test.name}" "${test}" + '') tests)} +'' // tests diff --git a/pkgs/test/make-binary-wrapper/env.c b/pkgs/test/make-binary-wrapper/env.c new file mode 100644 index 0000000000000..7e0422dee3bdc --- /dev/null +++ b/pkgs/test/make-binary-wrapper/env.c @@ -0,0 +1,14 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + putenv("PART1=HELLO"); + assert_success(setenv("PART2", "WORLD", 0)); + assert_success(unsetenv("SOME_OTHER_VARIABLE")); + putenv("PART3=\"!!\n\""); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/env.cmdline b/pkgs/test/make-binary-wrapper/env.cmdline new file mode 100644 index 0000000000000..3c89f33e2dceb --- /dev/null +++ b/pkgs/test/make-binary-wrapper/env.cmdline @@ -0,0 +1,4 @@ + --set PART1 HELLO \ + --set-default PART2 WORLD \ + --unset SOME_OTHER_VARIABLE \ + --set PART3 $'"!!\n"' diff --git a/pkgs/test/make-binary-wrapper/env.env b/pkgs/test/make-binary-wrapper/env.env new file mode 100644 index 0000000000000..c7661e165e09e --- /dev/null +++ b/pkgs/test/make-binary-wrapper/env.env @@ -0,0 +1,6 @@ +PART1=HELLO +PART2=WORLD +PART3="!! +" +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/pkgs/test/make-binary-wrapper/envcheck.c b/pkgs/test/make-binary-wrapper/envcheck.c new file mode 100644 index 0000000000000..848fbdaa80f26 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/envcheck.c @@ -0,0 +1,22 @@ +#include <limits.h> +#include <stdio.h> +#include <unistd.h> + +int main(int argc, char **argv, char **envp) { + for (char **env = envp; *env != 0; ++env) { + puts(*env); + } + + char cwd[PATH_MAX]; + if (getcwd(cwd, sizeof(cwd))) { + printf("CWD=%s\n", cwd); + } else { + perror("getcwd() error"); + return 1; + } + + for (int i=0; i < argc; ++i) { + puts(argv[i]); + } + return 0; +} diff --git a/pkgs/test/make-binary-wrapper/inherit-argv0.c b/pkgs/test/make-binary-wrapper/inherit-argv0.c new file mode 100644 index 0000000000000..e1c2bc926aa72 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/inherit-argv0.c @@ -0,0 +1,6 @@ +#include <unistd.h> +#include <stdlib.h> + +int main(int argc, char **argv) { + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline b/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline new file mode 100644 index 0000000000000..0880767998357 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/inherit-argv0.cmdline @@ -0,0 +1 @@ + --inherit-argv0 diff --git a/pkgs/test/make-binary-wrapper/inherit-argv0.env b/pkgs/test/make-binary-wrapper/inherit-argv0.env new file mode 100644 index 0000000000000..c46ca95eefbc7 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/inherit-argv0.env @@ -0,0 +1,2 @@ +CWD=SUBST_CWD +./wrapped diff --git a/pkgs/test/make-binary-wrapper/invalid-env.c b/pkgs/test/make-binary-wrapper/invalid-env.c new file mode 100644 index 0000000000000..4dfd36fb68a0b --- /dev/null +++ b/pkgs/test/make-binary-wrapper/invalid-env.c @@ -0,0 +1,14 @@ +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +int main(int argc, char **argv) { + putenv("==TEST1"); + #error Illegal environment variable name `=` (cannot contain `=`) + assert_success(setenv("", "TEST2", 0)); + #error Environment variable name can't be empty. + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/invalid-env.cmdline b/pkgs/test/make-binary-wrapper/invalid-env.cmdline new file mode 100644 index 0000000000000..a03b001e754e3 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/invalid-env.cmdline @@ -0,0 +1,2 @@ + --set "=" "TEST1" \ + --set-default "" "TEST2" diff --git a/pkgs/test/make-binary-wrapper/prefix.c b/pkgs/test/make-binary-wrapper/prefix.c new file mode 100644 index 0000000000000..ea8fbdc64a84e --- /dev/null +++ b/pkgs/test/make-binary-wrapper/prefix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_prefix(char *env, char *sep, char *prefix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", prefix, sep, existing)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, prefix, 1)); + } +} + +int main(int argc, char **argv) { + set_env_prefix("PATH", ":", "/usr/bin/"); + set_env_prefix("PATH", ":", "/usr/local/bin/"); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/prefix.cmdline b/pkgs/test/make-binary-wrapper/prefix.cmdline new file mode 100644 index 0000000000000..99cebf9503f47 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/prefix.cmdline @@ -0,0 +1,2 @@ + --prefix PATH : /usr/bin/ \ + --prefix PATH : /usr/local/bin/ diff --git a/pkgs/test/make-binary-wrapper/prefix.env b/pkgs/test/make-binary-wrapper/prefix.env new file mode 100644 index 0000000000000..033676457c57c --- /dev/null +++ b/pkgs/test/make-binary-wrapper/prefix.env @@ -0,0 +1,3 @@ +PATH=/usr/local/bin/:/usr/bin/ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/pkgs/test/make-binary-wrapper/suffix.c b/pkgs/test/make-binary-wrapper/suffix.c new file mode 100644 index 0000000000000..d33f86c070ca5 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/suffix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include <unistd.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> + +#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0) + +void set_env_suffix(char *env, char *sep, char *suffix) { + char *existing = getenv(env); + if (existing) { + char *val; + assert_success(asprintf(&val, "%s%s%s", existing, sep, suffix)); + assert_success(setenv(env, val, 1)); + free(val); + } else { + assert_success(setenv(env, suffix, 1)); + } +} + +int main(int argc, char **argv) { + set_env_suffix("PATH", ":", "/usr/bin/"); + set_env_suffix("PATH", ":", "/usr/local/bin/"); + argv[0] = "/send/me/flags"; + return execv("/send/me/flags", argv); +} diff --git a/pkgs/test/make-binary-wrapper/suffix.cmdline b/pkgs/test/make-binary-wrapper/suffix.cmdline new file mode 100644 index 0000000000000..95d291f3c169e --- /dev/null +++ b/pkgs/test/make-binary-wrapper/suffix.cmdline @@ -0,0 +1,2 @@ + --suffix PATH : /usr/bin/ \ + --suffix PATH : /usr/local/bin/ diff --git a/pkgs/test/make-binary-wrapper/suffix.env b/pkgs/test/make-binary-wrapper/suffix.env new file mode 100644 index 0000000000000..3ce4cc54de41b --- /dev/null +++ b/pkgs/test/make-binary-wrapper/suffix.env @@ -0,0 +1,3 @@ +PATH=/usr/bin/:/usr/local/bin/ +CWD=SUBST_CWD +SUBST_ARGV0 diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 337b830a34856..92ab74ab2889a 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -688,6 +688,21 @@ with pkgs; makeWrapper = makeSetupHook { deps = [ dieHook ]; substitutions = { shell = targetPackages.runtimeShell; }; } ../build-support/setup-hooks/make-wrapper.sh; + makeBinaryWrapper = let + f = { cc, sanitizers }: let + san = lib.concatMapStringsSep " " (s: "-fsanitize=${s}") sanitizers; + script = runCommand "make-binary-wrapper.sh" {} '' + substitute ${../build-support/setup-hooks/make-binary-wrapper.sh} $out \ + --replace " @CC@ " " ${cc}/bin/cc ${san} " + ''; + in + makeSetupHook { deps = [ dieHook ]; } script; + in + lib.makeOverridable f { + cc = stdenv.cc.cc; + sanitizers = [ "undefined" "address" ]; + }; + makeModulesClosure = { kernel, firmware, rootModules, allowMissing ? false }: callPackage ../build-support/kernel/modules-closure.nix { inherit kernel firmware rootModules allowMissing; |