diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md index c108fffd1b0..6d72bd0deb4 100644 --- a/doc/stdenv/stdenv.chapter.md +++ b/doc/stdenv/stdenv.chapter.md @@ -796,7 +796,7 @@ The standard environment provides a number of useful functions. ### `makeWrapper` \ \ \ {#fun-makeWrapper} -Constructs a wrapper for a program with various possible arguments. For example: +Constructs a wrapper for a program with various possible arguments. It is defined as part of 2 setup-hooks named `makeWrapper` and `makeBinaryWrapper` that implement the same bash functions. Hence, to use it you have to add `makeWrapper` to your `nativeBuildInputs`. Here's an example usage: ```bash # adds `FOOBAR=baz` to `$out/bin/foo`’s environment @@ -808,9 +808,11 @@ makeWrapper $out/bin/foo $wrapperfile --set FOOBAR baz makeWrapper $out/bin/foo $wrapperfile --prefix PATH : ${lib.makeBinPath [ hello git ]} ``` -There’s many more kinds of arguments, they are documented in `nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh`. +There’s many more kinds of arguments, they are documented in `nixpkgs/pkgs/build-support/setup-hooks/make-wrapper.sh` for the `makeWrapper` implementation and in `nixpkgs/pkgs/build-support/setup-hooks/make-binary-wrapper.sh` for the `makeBinaryWrapper` implementation. -`wrapProgram` is a convenience function you probably want to use most of the time. +`wrapProgram` is a convenience function you probably want to use most of the time, implemented by both `makeWrapper` and `makeBinaryWrapper`. + +Using the `makeBinaryWrapper` implementation is usually preferred, as it creates a tiny _compiled_ wrapper executable, that can be used as a shebang interpreter. This is needed mostly on Darwin, where shebangs cannot point to scripts, [due to a limitation with the `execve`-syscall](https://stackoverflow.com/questions/67100831/macos-shebang-with-absolute-path-not-working). Compiled wrappers generated by `makeBinaryWrapper` can be inspected with `less ` - by scrolling past the binary data you should be able to see the shell command that generated the executable and there see the environment variables that were injected into the wrapper. ### `substitute` \ \ \ {#fun-substitute} @@ -885,9 +887,9 @@ someVar=$(stripHash $name) ### `wrapProgram` \ \ {#fun-wrapProgram} -Convenience function for `makeWrapper` that automatically creates a sane wrapper file. It takes all the same arguments as `makeWrapper`, except for `--argv0`. +Convenience function for `makeWrapper` that replaces `<\executable\>` with a wrapper that executes the original program. It takes all the same arguments as `makeWrapper`, except for `--inherit-argv0` (used by the `makeBinaryWrapper` implementation) and `--argv0` (used by both `makeWrapper` and `makeBinaryWrapper` wrapper implementations). -It cannot be applied multiple times, since it will overwrite the wrapper file. +If you will apply it multiple times, it will overwrite the wrapper file and you will end up with double wrapping, which should be avoided. ## Package setup hooks {#ssec-setup-hooks} diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index a90651883e7..c547274ee9d 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -1414,6 +1414,12 @@ githubId = 251106; name = "Daniel Bergey"; }; + bergkvist = { + email = "tobias@bergkv.ist"; + github = "bergkvist"; + githubId = 410028; + name = "Tobias Bergkvist"; + }; betaboon = { email = "betaboon@0x80.ninja"; github = "betaboon"; 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 00000000000..abc929cb89d --- /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 +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 " + printf '%s\n' "#include " + [ -z "$uses_assert" ] || printf '%s\n' "#include " + [ -z "$uses_stdio" ] || printf '%s\n' "#include " + [ -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 e3ef7839c4b..b73617daa89 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 00000000000..7ce682c6be6 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/add-flags.c @@ -0,0 +1,21 @@ +#include +#include +#include + +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 00000000000..f840c772e34 --- /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 00000000000..9b8d1fb9f6a --- /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 00000000000..70c36889dc8 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/argv0.c @@ -0,0 +1,7 @@ +#include +#include + +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 00000000000..1cadce8312a --- /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 00000000000..04c13d32ee6 --- /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 00000000000..1c126618137 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/basic.c @@ -0,0 +1,7 @@ +#include +#include + +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 00000000000..e69de29bb2d diff --git a/pkgs/test/make-binary-wrapper/basic.env b/pkgs/test/make-binary-wrapper/basic.env new file mode 100644 index 00000000000..b0da3195944 --- /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 00000000000..c67c695b1c3 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/chdir.c @@ -0,0 +1,11 @@ +#include +#include +#include + +#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 00000000000..15235f20621 --- /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 00000000000..db129d68af7 --- /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 00000000000..e9ce5f1d724 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/combination.c @@ -0,0 +1,53 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include +#include +#include +#include + +#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 00000000000..fb3861235c8 --- /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 00000000000..886420c01d1 --- /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 00000000000..c5bb6970aac --- /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 00000000000..7e0422dee3b --- /dev/null +++ b/pkgs/test/make-binary-wrapper/env.c @@ -0,0 +1,14 @@ +#include +#include +#include + +#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 00000000000..3c89f33e2dc --- /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 00000000000..c7661e165e0 --- /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 00000000000..848fbdaa80f --- /dev/null +++ b/pkgs/test/make-binary-wrapper/envcheck.c @@ -0,0 +1,22 @@ +#include +#include +#include + +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 00000000000..e1c2bc926aa --- /dev/null +++ b/pkgs/test/make-binary-wrapper/inherit-argv0.c @@ -0,0 +1,6 @@ +#include +#include + +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 00000000000..08807679983 --- /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 00000000000..c46ca95eefb --- /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 00000000000..4dfd36fb68a --- /dev/null +++ b/pkgs/test/make-binary-wrapper/invalid-env.c @@ -0,0 +1,14 @@ +#include +#include +#include + +#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 00000000000..a03b001e754 --- /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 00000000000..ea8fbdc64a8 --- /dev/null +++ b/pkgs/test/make-binary-wrapper/prefix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include +#include +#include +#include + +#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 00000000000..99cebf9503f --- /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 00000000000..033676457c5 --- /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 00000000000..d33f86c070c --- /dev/null +++ b/pkgs/test/make-binary-wrapper/suffix.c @@ -0,0 +1,26 @@ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include +#include +#include +#include + +#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 00000000000..95d291f3c16 --- /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 00000000000..3ce4cc54de4 --- /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 337b830a348..92ab74ab288 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;