nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at python-updates 543 lines 18 kB view raw
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}