mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-02-16 17:14:00 +00:00
Merge pull request #124556 from bergkvist/bergkvist/make-c-wrapper
Generate tiny compiled binary for wrapping executables
This commit is contained in:
commit
9fb7d91888
@ -796,7 +796,7 @@ The standard environment provides a number of useful functions.
|
||||
|
||||
### `makeWrapper` \<executable\> \<wrapperfile\> \<args\> {#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 <path-to-wrapper>` - 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` \<infile\> \<outfile\> \<subs\> {#fun-substitute}
|
||||
|
||||
@ -885,9 +887,9 @@ someVar=$(stripHash $name)
|
||||
|
||||
### `wrapProgram` \<executable\> \<makeWrapperArgs\> {#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}
|
||||
|
||||
|
@ -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";
|
||||
|
384
pkgs/build-support/setup-hooks/make-binary-wrapper.sh
Normal file
384
pkgs/build-support/setup-hooks/make-binary-wrapper.sh
Normal file
@ -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
|
||||
}
|
@ -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 {});
|
||||
|
21
pkgs/test/make-binary-wrapper/add-flags.c
Normal file
21
pkgs/test/make-binary-wrapper/add-flags.c
Normal file
@ -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);
|
||||
}
|
2
pkgs/test/make-binary-wrapper/add-flags.cmdline
Normal file
2
pkgs/test/make-binary-wrapper/add-flags.cmdline
Normal file
@ -0,0 +1,2 @@
|
||||
--add-flags "-x -y -z" \
|
||||
--add-flags -abc
|
6
pkgs/test/make-binary-wrapper/add-flags.env
Normal file
6
pkgs/test/make-binary-wrapper/add-flags.env
Normal file
@ -0,0 +1,6 @@
|
||||
CWD=SUBST_CWD
|
||||
SUBST_ARGV0
|
||||
-x
|
||||
-y
|
||||
-z
|
||||
-abc
|
7
pkgs/test/make-binary-wrapper/argv0.c
Normal file
7
pkgs/test/make-binary-wrapper/argv0.c
Normal file
@ -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);
|
||||
}
|
1
pkgs/test/make-binary-wrapper/argv0.cmdline
Normal file
1
pkgs/test/make-binary-wrapper/argv0.cmdline
Normal file
@ -0,0 +1 @@
|
||||
--argv0 alternative-name
|
2
pkgs/test/make-binary-wrapper/argv0.env
Normal file
2
pkgs/test/make-binary-wrapper/argv0.env
Normal file
@ -0,0 +1,2 @@
|
||||
CWD=SUBST_CWD
|
||||
alternative-name
|
7
pkgs/test/make-binary-wrapper/basic.c
Normal file
7
pkgs/test/make-binary-wrapper/basic.c
Normal file
@ -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);
|
||||
}
|
0
pkgs/test/make-binary-wrapper/basic.cmdline
Normal file
0
pkgs/test/make-binary-wrapper/basic.cmdline
Normal file
2
pkgs/test/make-binary-wrapper/basic.env
Normal file
2
pkgs/test/make-binary-wrapper/basic.env
Normal file
@ -0,0 +1,2 @@
|
||||
CWD=SUBST_CWD
|
||||
SUBST_ARGV0
|
11
pkgs/test/make-binary-wrapper/chdir.c
Normal file
11
pkgs/test/make-binary-wrapper/chdir.c
Normal file
@ -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);
|
||||
}
|
1
pkgs/test/make-binary-wrapper/chdir.cmdline
Normal file
1
pkgs/test/make-binary-wrapper/chdir.cmdline
Normal file
@ -0,0 +1 @@
|
||||
--chdir /tmp/foo
|
2
pkgs/test/make-binary-wrapper/chdir.env
Normal file
2
pkgs/test/make-binary-wrapper/chdir.env
Normal file
@ -0,0 +1,2 @@
|
||||
CWD=/tmp/foo
|
||||
SUBST_ARGV0
|
53
pkgs/test/make-binary-wrapper/combination.c
Normal file
53
pkgs/test/make-binary-wrapper/combination.c
Normal file
@ -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);
|
||||
}
|
6
pkgs/test/make-binary-wrapper/combination.cmdline
Normal file
6
pkgs/test/make-binary-wrapper/combination.cmdline
Normal file
@ -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
|
8
pkgs/test/make-binary-wrapper/combination.env
Normal file
8
pkgs/test/make-binary-wrapper/combination.env
Normal file
@ -0,0 +1,8 @@
|
||||
MESSAGE=HELLO
|
||||
PATH=/usr/bin/:/usr/local/bin/
|
||||
MESSAGE2=WORLD
|
||||
CWD=SUBST_CWD
|
||||
my-wrapper
|
||||
-x
|
||||
-y
|
||||
-z
|
54
pkgs/test/make-binary-wrapper/default.nix
Normal file
54
pkgs/test/make-binary-wrapper/default.nix
Normal file
@ -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
|
14
pkgs/test/make-binary-wrapper/env.c
Normal file
14
pkgs/test/make-binary-wrapper/env.c
Normal file
@ -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);
|
||||
}
|
4
pkgs/test/make-binary-wrapper/env.cmdline
Normal file
4
pkgs/test/make-binary-wrapper/env.cmdline
Normal file
@ -0,0 +1,4 @@
|
||||
--set PART1 HELLO \
|
||||
--set-default PART2 WORLD \
|
||||
--unset SOME_OTHER_VARIABLE \
|
||||
--set PART3 $'"!!\n"'
|
6
pkgs/test/make-binary-wrapper/env.env
Normal file
6
pkgs/test/make-binary-wrapper/env.env
Normal file
@ -0,0 +1,6 @@
|
||||
PART1=HELLO
|
||||
PART2=WORLD
|
||||
PART3="!!
|
||||
"
|
||||
CWD=SUBST_CWD
|
||||
SUBST_ARGV0
|
22
pkgs/test/make-binary-wrapper/envcheck.c
Normal file
22
pkgs/test/make-binary-wrapper/envcheck.c
Normal file
@ -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;
|
||||
}
|
6
pkgs/test/make-binary-wrapper/inherit-argv0.c
Normal file
6
pkgs/test/make-binary-wrapper/inherit-argv0.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
return execv("/send/me/flags", argv);
|
||||
}
|
1
pkgs/test/make-binary-wrapper/inherit-argv0.cmdline
Normal file
1
pkgs/test/make-binary-wrapper/inherit-argv0.cmdline
Normal file
@ -0,0 +1 @@
|
||||
--inherit-argv0
|
2
pkgs/test/make-binary-wrapper/inherit-argv0.env
Normal file
2
pkgs/test/make-binary-wrapper/inherit-argv0.env
Normal file
@ -0,0 +1,2 @@
|
||||
CWD=SUBST_CWD
|
||||
./wrapped
|
14
pkgs/test/make-binary-wrapper/invalid-env.c
Normal file
14
pkgs/test/make-binary-wrapper/invalid-env.c
Normal file
@ -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);
|
||||
}
|
2
pkgs/test/make-binary-wrapper/invalid-env.cmdline
Normal file
2
pkgs/test/make-binary-wrapper/invalid-env.cmdline
Normal file
@ -0,0 +1,2 @@
|
||||
--set "=" "TEST1" \
|
||||
--set-default "" "TEST2"
|
26
pkgs/test/make-binary-wrapper/prefix.c
Normal file
26
pkgs/test/make-binary-wrapper/prefix.c
Normal file
@ -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);
|
||||
}
|
2
pkgs/test/make-binary-wrapper/prefix.cmdline
Normal file
2
pkgs/test/make-binary-wrapper/prefix.cmdline
Normal file
@ -0,0 +1,2 @@
|
||||
--prefix PATH : /usr/bin/ \
|
||||
--prefix PATH : /usr/local/bin/
|
3
pkgs/test/make-binary-wrapper/prefix.env
Normal file
3
pkgs/test/make-binary-wrapper/prefix.env
Normal file
@ -0,0 +1,3 @@
|
||||
PATH=/usr/local/bin/:/usr/bin/
|
||||
CWD=SUBST_CWD
|
||||
SUBST_ARGV0
|
26
pkgs/test/make-binary-wrapper/suffix.c
Normal file
26
pkgs/test/make-binary-wrapper/suffix.c
Normal file
@ -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);
|
||||
}
|
2
pkgs/test/make-binary-wrapper/suffix.cmdline
Normal file
2
pkgs/test/make-binary-wrapper/suffix.cmdline
Normal file
@ -0,0 +1,2 @@
|
||||
--suffix PATH : /usr/bin/ \
|
||||
--suffix PATH : /usr/local/bin/
|
3
pkgs/test/make-binary-wrapper/suffix.env
Normal file
3
pkgs/test/make-binary-wrapper/suffix.env
Normal file
@ -0,0 +1,3 @@
|
||||
PATH=/usr/bin/:/usr/local/bin/
|
||||
CWD=SUBST_CWD
|
||||
SUBST_ARGV0
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user