nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1
2set -euo pipefail
3
4# Assert that FILE exists and is executable
5#
6# assertExecutable FILE
7assertExecutable() {
8 local file="$1"
9 [[ -f "$file" && -x "$file" ]] || \
10 die "Cannot wrap '$file' because it is not an executable file"
11}
12
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
16
17# 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# --resolve-argv0 : if argv0 doesn't include a / character, resolve it against PATH
23# --set VAR VAL : add VAR with value VAL to the executable's environment
24# --set-default VAR VAL : like --set, but only adds VAR if not already set in
25# the environment
26# --unset VAR : remove VAR from the environment
27# --chdir DIR : change working directory (use instead of --run "cd DIR")
28# --add-flag ARG : prepend the single argument ARG to the invocation of the executable
29# (that is, *before* any arguments passed on the command line)
30# --append-flag ARG : append the single argument ARG to the invocation of the executable
31# (that is, *after* any arguments passed on the command line)
32# --add-flags ARGS : prepend the whitespace-separated list of arguments ARGS to the invocation of the executable
33# --append-flags ARGS : append the whitespace-separated list of arguments ARGS to the invocation of the executable
34
35# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP
36# --suffix
37
38# To troubleshoot a binary wrapper after you compiled it,
39# use the `strings` command or open the binary file in a text editor.
40makeWrapper() { makeBinaryWrapper "$@"; }
41makeBinaryWrapper() {
42 local NIX_CFLAGS_COMPILE= NIX_CFLAGS_LINK=
43 local original="$1"
44 local wrapper="$2"
45 shift 2
46
47 assertExecutable "$original"
48
49 mkdir -p "$(dirname "$wrapper")"
50
51 makeDocumentedCWrapper "$original" "$@" | \
52 @cc@ \
53 -Wall -Werror -Wpedantic \
54 -Wno-overlength-strings \
55 -Os \
56 -x c \
57 -o "$wrapper" -
58}
59
60# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...>
61wrapProgram() { wrapProgramBinary "$@"; }
62wrapProgramBinary() {
63 local prog="$1"
64 local hidden
65
66 assertExecutable "$prog"
67
68 hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped
69 while [ -e "$hidden" ]; do
70 hidden="${hidden}_"
71 done
72 mv "$prog" "$hidden"
73 makeBinaryWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}"
74}
75
76# Generate source code for the wrapper in such a way that the wrapper inputs
77# will still be readable even after compilation
78# makeDocumentedCWrapper EXECUTABLE ARGS
79# ARGS: same as makeWrapper
80makeDocumentedCWrapper() {
81 local src docs
82 src=$(makeCWrapper "$@")
83 docs=$(docstring "$@")
84 printf '%s\n\n' "$src"
85 printf '%s\n' "$docs"
86}
87
88# makeCWrapper EXECUTABLE ARGS
89# ARGS: same as makeWrapper
90makeCWrapper() {
91 local argv0 inherit_argv0 n params cmd main flags executable length
92 local uses_sep_surround_check uses_prefix uses_suffix uses_assert uses_assert_success uses_stdio uses_asprintf
93 local flagsBefore=() flagsAfter=()
94 executable=$(escapeStringLiteral "$1")
95 params=("$@")
96 length=${#params[*]}
97 for ((n = 1; n < length; n += 1)); do
98 p="${params[n]}"
99 case $p in
100 --set)
101 cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}")
102 main="$main$cmd"$'\n'
103 n=$((n + 2))
104 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
105 ;;
106 --set-default)
107 cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}")
108 main="$main$cmd"$'\n'
109 uses_stdio=1
110 uses_assert_success=1
111 n=$((n + 2))
112 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n'
113 ;;
114 --unset)
115 cmd=$(unsetEnv "${params[n + 1]}")
116 main="$main$cmd"$'\n'
117 uses_stdio=1
118 uses_assert_success=1
119 n=$((n + 1))
120 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
121 ;;
122 --prefix)
123 cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
124 main="$main$cmd"$'\n'
125 uses_sep_surround_check=1
126 uses_prefix=1
127 uses_asprintf=1
128 uses_stdio=1
129 uses_string=1
130 uses_assert_success=1
131 uses_assert=1
132 n=$((n + 3))
133 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
134 ;;
135 --suffix)
136 cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}")
137 main="$main$cmd"$'\n'
138 uses_sep_surround_check=1
139 uses_suffix=1
140 uses_asprintf=1
141 uses_stdio=1
142 uses_string=1
143 uses_assert_success=1
144 uses_assert=1
145 n=$((n + 3))
146 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n'
147 ;;
148 --chdir)
149 cmd=$(changeDir "${params[n + 1]}")
150 main="$main$cmd"$'\n'
151 uses_stdio=1
152 uses_assert_success=1
153 n=$((n + 1))
154 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
155 ;;
156 --add-flag)
157 flagsBefore+=("${params[n + 1]}")
158 uses_assert=1
159 n=$((n + 1))
160 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
161 ;;
162 --append-flag)
163 flagsAfter+=("${params[n + 1]}")
164 uses_assert=1
165 n=$((n + 1))
166 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
167 ;;
168 --add-flags)
169 read -ra flags <<< "${params[n + 1]}"
170 flagsBefore+=("${flags[@]}")
171 uses_assert=1
172 n=$((n + 1))
173 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
174 ;;
175 --append-flags)
176 read -ra flags <<< "${params[n + 1]}"
177 flagsAfter+=("${flags[@]}")
178 uses_assert=1
179 n=$((n + 1))
180 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
181 ;;
182 --argv0)
183 argv0=$(escapeStringLiteral "${params[n + 1]}")
184 inherit_argv0=
185 n=$((n + 1))
186 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n'
187 ;;
188 --inherit-argv0)
189 # Whichever comes last of --argv0 and --inherit-argv0 wins
190 inherit_argv0=1
191 ;;
192 --resolve-argv0)
193 # this gets processed after other argv0 flags
194 uses_stdio=1
195 uses_string=1
196 resolve_argv0=1
197 ;;
198 *) # Using an error macro, we will make sure the compiler gives an understandable error message
199 main="$main#error makeCWrapper: Unknown argument ${p}"$'\n'
200 ;;
201 esac
202 done
203 (( ${#flagsBefore[@]} + ${#flagsAfter[@]} > 0 )) && main="$main"${main:+$'\n'}$(addFlags flagsBefore flagsAfter)$'\n'$'\n'
204 [ -z "$inherit_argv0" ] && main="${main}argv[0] = \"${argv0:-${executable}}\";"$'\n'
205 [ -z "$resolve_argv0" ] || main="${main}argv[0] = resolve_argv0(argv[0]);"$'\n'
206 main="${main}return execv(\"${executable}\", argv);"$'\n'
207
208 [ -z "$uses_asprintf" ] || printf '%s\n' "#define _GNU_SOURCE /* See feature_test_macros(7) */"
209 printf '%s\n' "#include <unistd.h>"
210 printf '%s\n' "#include <stdlib.h>"
211 [ -z "$uses_assert" ] || printf '%s\n' "#include <assert.h>"
212 [ -z "$uses_stdio" ] || printf '%s\n' "#include <stdio.h>"
213 [ -z "$uses_string" ] || printf '%s\n' "#include <string.h>"
214 [ -z "$uses_assert_success" ] || printf '\n%s\n' "#define assert_success(e) do { if ((e) < 0) { perror(#e); abort(); } } while (0)"
215 [ -z "$uses_sep_surround_check" ] || printf '\n%s\n' "$(setSepSurroundCheck)"
216 [ -z "$uses_prefix" ] || printf '\n%s\n' "$(setEnvPrefixFn)"
217 [ -z "$uses_suffix" ] || printf '\n%s\n' "$(setEnvSuffixFn)"
218 [ -z "$resolve_argv0" ] || printf '\n%s\n' "$(resolveArgv0Fn)"
219 printf '\n%s' "int main(int argc, char **argv) {"
220 printf '\n%s' "$(indent4 "$main")"
221 printf '\n%s\n' "}"
222}
223
224addFlags() {
225 local n flag var
226
227 local -n before=$1
228 local -n after=$2
229
230 var="argv_tmp"
231 printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));"
232 printf '%s\n' "assert($var != NULL);"
233 printf '%s\n' "${var}[0] = argv[0];"
234 for ((n = 0; n < ${#before[@]}; n += 1)); do
235 flag=$(escapeStringLiteral "${before[n]}")
236 printf '%s\n' "${var}[$((n + 1))] = \"$flag\";"
237 done
238 printf '%s\n' "for (int i = 1; i < argc; ++i) {"
239 printf '%s\n' " ${var}[${#before[@]} + i] = argv[i];"
240 printf '%s\n' "}"
241 for ((n = 0; n < ${#after[@]}; n += 1)); do
242 flag=$(escapeStringLiteral "${after[n]}")
243 printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";"
244 done
245 printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;"
246 printf '%s\n' "argv = $var;"
247}
248
249# chdir DIR
250changeDir() {
251 local dir
252 dir=$(escapeStringLiteral "$1")
253 printf '%s' "assert_success(chdir(\"$dir\"));"
254}
255
256# prefix ENV SEP VAL
257setEnvPrefix() {
258 local env sep val
259 env=$(escapeStringLiteral "$1")
260 sep=$(escapeStringLiteral "$2")
261 val=$(escapeStringLiteral "$3")
262 printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");"
263 assertValidEnvName "$1"
264}
265
266# suffix ENV SEP VAL
267setEnvSuffix() {
268 local env sep val
269 env=$(escapeStringLiteral "$1")
270 sep=$(escapeStringLiteral "$2")
271 val=$(escapeStringLiteral "$3")
272 printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");"
273 assertValidEnvName "$1"
274}
275
276# setEnv KEY VALUE
277setEnv() {
278 local key value
279 key=$(escapeStringLiteral "$1")
280 value=$(escapeStringLiteral "$2")
281 printf '%s' "putenv(\"$key=$value\");"
282 assertValidEnvName "$1"
283}
284
285# setDefaultEnv KEY VALUE
286setDefaultEnv() {
287 local key value
288 key=$(escapeStringLiteral "$1")
289 value=$(escapeStringLiteral "$2")
290 printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));"
291 assertValidEnvName "$1"
292}
293
294# unsetEnv KEY
295unsetEnv() {
296 local key
297 key=$(escapeStringLiteral "$1")
298 printf '%s' "assert_success(unsetenv(\"$key\"));"
299 assertValidEnvName "$1"
300}
301
302# Makes it safe to insert STRING within quotes in a C String Literal.
303# escapeStringLiteral STRING
304escapeStringLiteral() {
305 local result
306 result=${1//$'\\'/$'\\\\'}
307 result=${result//\"/'\"'}
308 result=${result//$'\n'/"\n"}
309 result=${result//$'\r'/"\r"}
310 printf '%s' "$result"
311}
312
313# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines
314# indent4 TEXT_BLOCK
315indent4() {
316 printf '%s' "$1" | awk '{ if ($0 != "") { print " "$0 } else { print $0 }}'
317}
318
319assertValidEnvName() {
320 case "$1" in
321 *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";;
322 "") printf '\n%s\n' "#error Environment variable name can't be empty.";;
323 esac
324}
325
326setSepSurroundCheck() {
327 printf '%s' "\
328int is_surrounded_by_sep(char *env, char *ptr, unsigned long len, char *sep) {
329 unsigned long sep_len = strlen(sep);
330
331 // Check left side (if not at start)
332 if (env != ptr) {
333 if (ptr - env < sep_len)
334 return 0;
335 if (strncmp(sep, ptr - sep_len, sep_len) != 0) {
336 return 0;
337 }
338 }
339 // Check right side (if not at end)
340 char *end_ptr = ptr + len;
341 if (*end_ptr != '\0') {
342 if (strncmp(sep, ptr + len, sep_len) != 0) {
343 return 0;
344 }
345 }
346
347 return 1;
348}
349"
350}
351
352setEnvPrefixFn() {
353 printf '%s' "\
354void set_env_prefix(char *env, char *sep, char *prefix) {
355 char *existing_env = getenv(env);
356 if (existing_env) {
357 char *val;
358
359 char *existing_prefix = strstr(existing_env, prefix);
360 unsigned long prefix_len = strlen(prefix);
361 // If the prefix already exists, remove the original
362 if (existing_prefix && is_surrounded_by_sep(existing_env, existing_prefix, prefix_len, sep)) {
363 if (existing_env == existing_prefix) {
364 return;
365 }
366 unsigned long sep_len = strlen(sep);
367 int n_before = existing_prefix - existing_env;
368 assert_success(asprintf(&val, \"%s%s%.*s%s\", prefix, sep,
369 n_before, existing_env,
370 existing_prefix + prefix_len + sep_len));
371 } else {
372 assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing_env));
373 }
374 assert_success(setenv(env, val, 1));
375 free(val);
376 } else {
377 assert_success(setenv(env, prefix, 1));
378 }
379}
380"
381}
382
383setEnvSuffixFn() {
384 printf '%s' "\
385void set_env_suffix(char *env, char *sep, char *suffix) {
386 char *existing_env = getenv(env);
387 if (existing_env) {
388 char *val;
389
390 char *existing_suffix = strstr(existing_env, suffix);
391 unsigned long suffix_len = strlen(suffix);
392 // If the suffix already exists, remove the original
393 if (existing_suffix && is_surrounded_by_sep(existing_env, existing_suffix, suffix_len, sep)) {
394 char *end_ptr = existing_suffix + suffix_len;
395 if (*end_ptr == '\0') {
396 return;
397 }
398 unsigned long sep_len = strlen(sep);
399 int n_before = existing_suffix - existing_env;
400 assert_success(asprintf(&val, \"%.*s%s%s%s\",
401 n_before, existing_env,
402 existing_suffix + suffix_len + sep_len,
403 sep, suffix));
404 } else {
405 assert_success(asprintf(&val, \"%s%s%s\", existing_env, sep, suffix));
406 }
407 assert_success(setenv(env, val, 1));
408 free(val);
409 } else {
410 assert_success(setenv(env, suffix, 1));
411 }
412}
413"
414}
415
416resolveArgv0Fn() {
417 printf '%s' "\
418char *resolve_argv0(char *argv0) {
419 if (strchr(argv0, '/') != NULL) {
420 return argv0;
421 }
422 char *path = getenv(\"PATH\");
423 if (path == NULL) {
424 return argv0;
425 }
426 char *path_copy = strdup(path);
427 if (path_copy == NULL) {
428 return argv0;
429 }
430 char *dir = strtok(path_copy, \":\");
431 while (dir != NULL) {
432 char *candidate = malloc(strlen(dir) + strlen(argv0) + 2);
433 if (candidate == NULL) {
434 free(path_copy);
435 return argv0;
436 }
437 sprintf(candidate, \"%s/%s\", dir, argv0);
438 if (access(candidate, X_OK) == 0) {
439 free(path_copy);
440 return candidate;
441 }
442 free(candidate);
443 dir = strtok(NULL, \":\");
444 }
445 free(path_copy);
446 return argv0;
447}
448"
449}
450
451# Embed a C string which shows up as readable text in the compiled binary wrapper,
452# giving instructions for recreating the wrapper.
453# Keep in sync with makeBinaryWrapper.extractCmd
454docstring() {
455 printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral "
456
457
458# ------------------------------------------------------------------------------------
459# The C-code for this binary wrapper has been generated using the following command:
460
461
462makeCWrapper $(formatArgs "$@")
463
464
465# (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell)
466# ------------------------------------------------------------------------------------
467
468
469")\";"
470}
471
472# formatArgs EXECUTABLE ARGS
473formatArgs() {
474 printf '%s' "${1@Q}"
475 shift
476 while [ $# -gt 0 ]; do
477 case "$1" in
478 --set)
479 formatArgsLine 2 "$@"
480 shift 2
481 ;;
482 --set-default)
483 formatArgsLine 2 "$@"
484 shift 2
485 ;;
486 --unset)
487 formatArgsLine 1 "$@"
488 shift 1
489 ;;
490 --prefix)
491 formatArgsLine 3 "$@"
492 shift 3
493 ;;
494 --suffix)
495 formatArgsLine 3 "$@"
496 shift 3
497 ;;
498 --chdir)
499 formatArgsLine 1 "$@"
500 shift 1
501 ;;
502 --add-flag)
503 formatArgsLine 1 "$@"
504 shift 1
505 ;;
506 --append-flag)
507 formatArgsLine 1 "$@"
508 shift 1
509 ;;
510 --add-flags)
511 formatArgsLine 1 "$@"
512 shift 1
513 ;;
514 --append-flags)
515 formatArgsLine 1 "$@"
516 shift 1
517 ;;
518 --argv0)
519 formatArgsLine 1 "$@"
520 shift 1
521 ;;
522 --inherit-argv0)
523 formatArgsLine 0 "$@"
524 ;;
525 esac
526 shift
527 done
528 printf '%s\n' ""
529}
530
531# formatArgsLine ARG_COUNT ARGS
532formatArgsLine() {
533 local ARG_COUNT LENGTH
534 ARG_COUNT=$1
535 LENGTH=$#
536 shift
537 printf '%s' $' \\\n '"$1"
538 shift
539 while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do
540 printf ' %s' "${1@Q}"
541 shift
542 done
543}