4 # Assert that FILE exists and is executable
6 # assertExecutable FILE
9 [[ -f "$file" && -x "$file" ]] || \
10 die
"Cannot wrap '$file' because it is not an executable file"
13 # Generate a binary executable wrapper for wrapping an executable.
14 # The binary is compiled from generated C-code using gcc.
15 # makeWrapper EXECUTABLE OUT_PATH ARGS
18 # --argv0 NAME : set the name of the executed process to NAME
19 # (if unset or empty, defaults to EXECUTABLE)
20 # --inherit-argv0 : the executable inherits argv0 from the wrapper.
21 # (use instead of --argv0 '$0')
22 # --set VAR VAL : add VAR with value VAL to the executable's environment
23 # --set-default VAR VAL : like --set, but only adds VAR if not already set in
25 # --unset VAR : remove VAR from the environment
26 # --chdir DIR : change working directory (use instead of --run "cd DIR")
27 # --add-flags ARGS : prepend ARGS to the invocation of the executable
28 # (that is, *before* any arguments passed on the command line)
29 # --append-flags ARGS : append ARGS to the invocation of the executable
30 # (that is, *after* any arguments passed on the command line)
32 # --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP
35 # To troubleshoot a binary wrapper after you compiled it,
36 # use the `strings` command or open the binary file in a text editor.
37 makeWrapper
() { makeBinaryWrapper
"$@"; }
39 local NIX_CFLAGS_COMPILE
= NIX_CFLAGS_LINK
=
44 assertExecutable
"$original"
46 mkdir
-p "$(dirname "$wrapper")"
48 makeDocumentedCWrapper
"$original" "$@" | \
50 -Wall -Werror -Wpedantic \
51 -Wno-overlength-strings \
57 # Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
58 wrapProgram
() { wrapProgramBinary
"$@"; }
63 assertExecutable
"$prog"
65 hidden
="$(dirname "$prog")/.$(basename "$prog")"-wrapped
66 while [ -e "$hidden" ]; do
70 makeBinaryWrapper
"$hidden" "$prog" --inherit-argv0 "${@:2}"
73 # Generate source code for the wrapper in such a way that the wrapper inputs
74 # will still be readable even after compilation
75 # makeDocumentedCWrapper EXECUTABLE ARGS
76 # ARGS: same as makeWrapper
77 makeDocumentedCWrapper
() {
79 src
=$
(makeCWrapper
"$@")
80 docs
=$
(docstring
"$@")
81 printf '%s\n\n' "$src"
85 # makeCWrapper EXECUTABLE ARGS
86 # ARGS: same as makeWrapper
88 local argv0 inherit_argv0 n params cmd main flagsBefore flagsAfter flags executable length
89 local uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
90 executable
=$
(escapeStringLiteral
"$1")
93 for ((n
= 1; n
< length
; n
+= 1)); do
97 cmd
=$
(setEnv
"${params[n + 1]}" "${params[n + 2]}")
100 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 2 arguments"$
'\n'
103 cmd
=$
(setDefaultEnv
"${params[n + 1]}" "${params[n + 2]}")
104 main
="$main$cmd"$
'\n'
106 uses_assert_success
=1
108 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 2 arguments"$
'\n'
111 cmd
=$
(unsetEnv
"${params[n + 1]}")
112 main
="$main$cmd"$
'\n'
114 uses_assert_success
=1
116 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 1 argument"$
'\n'
119 cmd
=$
(setEnvPrefix
"${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
120 main
="$main$cmd"$
'\n'
124 uses_assert_success
=1
127 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 3 arguments"$
'\n'
130 cmd
=$
(setEnvSuffix
"${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
131 main
="$main$cmd"$
'\n'
135 uses_assert_success
=1
138 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 3 arguments"$
'\n'
141 cmd
=$
(changeDir
"${params[n + 1]}")
142 main
="$main$cmd"$
'\n'
144 uses_assert_success
=1
146 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 1 argument"$
'\n'
149 flags
="${params[n + 1]}"
150 flagsBefore
="$flagsBefore $flags"
153 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 1 argument"$
'\n'
156 flags
="${params[n + 1]}"
157 flagsAfter
="$flagsAfter $flags"
160 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 1 argument"$
'\n'
163 argv0
=$
(escapeStringLiteral
"${params[n + 1]}")
166 [ $n -ge "$length" ] && main
="$main#error makeCWrapper: $p takes 1 argument"$
'\n'
169 # Whichever comes last of --argv0 and --inherit-argv0 wins
172 *) # Using an error macro, we will make sure the compiler gives an understandable error message
173 main
="$main#error makeCWrapper: Unknown argument ${p}"$
'\n'
177 [[ -z "$flagsBefore" && -z "$flagsAfter" ]] || main
="$main"${main:+$'\n'}$
(addFlags
"$flagsBefore" "$flagsAfter")$
'\n'$
'\n'
178 [ -z "$inherit_argv0" ] && main
="${main}argv[0] = \"${argv0:-${executable}}\";"$
'\n'
179 main
="${main}return execv(\"${executable}\", argv);"$
'\n'
181 [ -z "$uses_asprintf" ] ||
printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */"
182 printf '%s\n' "#include <unistd.h>"
183 printf '%s\n' "#include <stdlib.h>"
184 [ -z "$uses_assert" ] ||
printf '%s\n' "#include <assert.h>"
185 [ -z "$uses_stdio" ] ||
printf '%s\n' "#include <stdio.h>"
186 [ -z "$uses_assert_success" ] ||
printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)"
187 [ -z "$uses_prefix" ] ||
printf '\n%s\n' "$(setEnvPrefixFn)"
188 [ -z "$uses_suffix" ] ||
printf '\n%s\n' "$(setEnvSuffixFn)"
189 printf '\n%s' "int main(int argc, char **argv) {"
190 printf '\n%s' "$(indent4 "$main")"
195 local n flag before after var
197 # Disable file globbing, since bash will otherwise try to find
198 # filenames matching the the value to be prefixed/suffixed if
199 # it contains characters considered wildcards, such as `?` and
200 # `*`. We want the value as is, except we also want to split
201 # it on on the separator; hence we can't quote it.
203 if [[ ! -o noglob
]]; then
207 # shellcheck disable=SC2086
208 before
=($1) after
=($2)
209 if (( reenableGlob
)); then
214 printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));"
215 printf '%s\n' "assert($var != NULL);"
216 printf '%s\n' "${var}[0] = argv[0];"
217 for ((n
= 0; n
< ${#before[@]}; n
+= 1)); do
218 flag
=$
(escapeStringLiteral
"${before[n]}")
219 printf '%s\n' "${var}[$((n + 1))] = \"$flag\";"
221 printf '%s\n' "for (int i = 1; i < argc; ++i) {"
222 printf '%s\n' " ${var}[${#before[@]} + i] = argv[i];"
224 for ((n
= 0; n
< ${#after[@]}; n
+= 1)); do
225 flag
=$
(escapeStringLiteral
"${after[n]}")
226 printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";"
228 printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;"
229 printf '%s\n' "argv = $var;"
235 dir
=$
(escapeStringLiteral
"$1")
236 printf '%s' "assert_success(chdir(\"$dir\"));"
242 env
=$
(escapeStringLiteral
"$1")
243 sep
=$
(escapeStringLiteral
"$2")
244 val
=$
(escapeStringLiteral
"$3")
245 printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");"
246 assertValidEnvName
"$1"
252 env
=$
(escapeStringLiteral
"$1")
253 sep
=$
(escapeStringLiteral
"$2")
254 val
=$
(escapeStringLiteral
"$3")
255 printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");"
256 assertValidEnvName
"$1"
262 key
=$
(escapeStringLiteral
"$1")
263 value
=$
(escapeStringLiteral
"$2")
264 printf '%s' "putenv(\"$key=$value\");"
265 assertValidEnvName
"$1"
268 # setDefaultEnv KEY VALUE
271 key
=$
(escapeStringLiteral
"$1")
272 value
=$
(escapeStringLiteral
"$2")
273 printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));"
274 assertValidEnvName
"$1"
280 key
=$
(escapeStringLiteral
"$1")
281 printf '%s' "assert_success(unsetenv(\"$key\"));"
282 assertValidEnvName
"$1"
285 # Makes it safe to insert STRING within quotes in a C String Literal.
286 # escapeStringLiteral STRING
287 escapeStringLiteral
() {
289 result
=${1//$'\\'/$'\\\\'}
290 result
=${result//\"/'\"'}
291 result
=${result//$'\n'/"\n"}
292 result
=${result//$'\r'/"\r"}
293 printf '%s' "$result"
296 # Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines
299 printf '%s' "$1" |
awk '{ if ($0 != "") { print " "$0 } else { print $0 }}'
302 assertValidEnvName
() {
304 *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";;
305 "") printf '\n%s\n' "#error Environment variable name can't be empty.";;
311 void set_env_prefix(char *env, char *sep, char *prefix) {
312 char *existing = getenv(env);
315 assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing));
316 assert_success(setenv(env, val, 1));
319 assert_success(setenv(env, prefix, 1));
327 void set_env_suffix(char *env, char *sep, char *suffix) {
328 char *existing = getenv(env);
331 assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix));
332 assert_success(setenv(env, val, 1));
335 assert_success(setenv(env, suffix, 1));
341 # Embed a C string which shows up as readable text in the compiled binary wrapper,
342 # giving instructions for recreating the wrapper.
343 # Keep in sync with makeBinaryWrapper.extractCmd
345 printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral "
348 # ------------------------------------------------------------------------------------
349 # The C-code for this binary wrapper has been generated using the following command:
352 makeCWrapper $
(formatArgs
"$@")
355 # (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell)
356 # ------------------------------------------------------------------------------------
362 # formatArgs EXECUTABLE ARGS
366 while [ $# -gt 0 ]; do
369 formatArgsLine
2 "$@"
373 formatArgsLine
2 "$@"
377 formatArgsLine
1 "$@"
381 formatArgsLine
3 "$@"
385 formatArgsLine
3 "$@"
389 formatArgsLine
1 "$@"
393 formatArgsLine
1 "$@"
397 formatArgsLine
1 "$@"
401 formatArgsLine
1 "$@"
405 formatArgsLine
0 "$@"
413 # formatArgsLine ARG_COUNT ARGS
415 local ARG_COUNT LENGTH
419 printf '%s' $
' \\\n '"$1"
421 while [ "$ARG_COUNT" -gt $
((LENGTH
- $# - 2)) ]; do
422 printf ' %s' "${1@Q}"