Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at github-to-sqlite-beautifulsoup4 410 lines 13 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# --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 24# the environment 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) 31 32# --prefix ENV SEP VAL : suffix/prefix ENV with VAL, separated by SEP 33# --suffix 34 35# To troubleshoot a binary wrapper after you compiled it, 36# use the `strings` command or open the binary file in a text editor. 37makeWrapper() { makeBinaryWrapper "$@"; } 38makeBinaryWrapper() { 39 local NIX_CFLAGS_COMPILE= NIX_CFLAGS_LINK= 40 local original="$1" 41 local wrapper="$2" 42 shift 2 43 44 assertExecutable "$original" 45 46 mkdir -p "$(dirname "$wrapper")" 47 48 makeDocumentedCWrapper "$original" "$@" | \ 49 @cc@ \ 50 -Wall -Werror -Wpedantic \ 51 -Wno-overlength-strings \ 52 -Os \ 53 -x c \ 54 -o "$wrapper" - 55} 56 57# Syntax: wrapProgram <PROGRAM> <MAKE-WRAPPER FLAGS...> 58wrapProgram() { wrapProgramBinary "$@"; } 59wrapProgramBinary() { 60 local prog="$1" 61 local hidden 62 63 assertExecutable "$prog" 64 65 hidden="$(dirname "$prog")/.$(basename "$prog")"-wrapped 66 while [ -e "$hidden" ]; do 67 hidden="${hidden}_" 68 done 69 mv "$prog" "$hidden" 70 makeBinaryWrapper "$hidden" "$prog" --inherit-argv0 "${@:2}" 71} 72 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 77makeDocumentedCWrapper() { 78 local src docs 79 src=$(makeCWrapper "$@") 80 docs=$(docstring "$@") 81 printf '%s\n\n' "$src" 82 printf '%s\n' "$docs" 83} 84 85# makeCWrapper EXECUTABLE ARGS 86# ARGS: same as makeWrapper 87makeCWrapper() { 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") 91 params=("$@") 92 length=${#params[*]} 93 for ((n = 1; n < length; n += 1)); do 94 p="${params[n]}" 95 case $p in 96 --set) 97 cmd=$(setEnv "${params[n + 1]}" "${params[n + 2]}") 98 main="$main$cmd"$'\n' 99 n=$((n + 2)) 100 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' 101 ;; 102 --set-default) 103 cmd=$(setDefaultEnv "${params[n + 1]}" "${params[n + 2]}") 104 main="$main$cmd"$'\n' 105 uses_stdio=1 106 uses_assert_success=1 107 n=$((n + 2)) 108 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 2 arguments"$'\n' 109 ;; 110 --unset) 111 cmd=$(unsetEnv "${params[n + 1]}") 112 main="$main$cmd"$'\n' 113 uses_stdio=1 114 uses_assert_success=1 115 n=$((n + 1)) 116 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' 117 ;; 118 --prefix) 119 cmd=$(setEnvPrefix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") 120 main="$main$cmd"$'\n' 121 uses_prefix=1 122 uses_asprintf=1 123 uses_stdio=1 124 uses_assert_success=1 125 uses_assert=1 126 n=$((n + 3)) 127 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' 128 ;; 129 --suffix) 130 cmd=$(setEnvSuffix "${params[n + 1]}" "${params[n + 2]}" "${params[n + 3]}") 131 main="$main$cmd"$'\n' 132 uses_suffix=1 133 uses_asprintf=1 134 uses_stdio=1 135 uses_assert_success=1 136 uses_assert=1 137 n=$((n + 3)) 138 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 3 arguments"$'\n' 139 ;; 140 --chdir) 141 cmd=$(changeDir "${params[n + 1]}") 142 main="$main$cmd"$'\n' 143 uses_stdio=1 144 uses_assert_success=1 145 n=$((n + 1)) 146 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' 147 ;; 148 --add-flags) 149 flags="${params[n + 1]}" 150 flagsBefore="$flagsBefore $flags" 151 uses_assert=1 152 n=$((n + 1)) 153 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' 154 ;; 155 --append-flags) 156 flags="${params[n + 1]}" 157 flagsAfter="$flagsAfter $flags" 158 uses_assert=1 159 n=$((n + 1)) 160 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' 161 ;; 162 --argv0) 163 argv0=$(escapeStringLiteral "${params[n + 1]}") 164 inherit_argv0= 165 n=$((n + 1)) 166 [ $n -ge "$length" ] && main="$main#error makeCWrapper: $p takes 1 argument"$'\n' 167 ;; 168 --inherit-argv0) 169 # Whichever comes last of --argv0 and --inherit-argv0 wins 170 inherit_argv0=1 171 ;; 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' 174 ;; 175 esac 176 done 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' 180 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")" 191 printf '\n%s\n' "}" 192} 193 194addFlags() { 195 local n flag before after var 196 # shellcheck disable=SC2086 197 before=($1) after=($2) 198 var="argv_tmp" 199 printf '%s\n' "char **$var = calloc(${#before[@]} + argc + ${#after[@]} + 1, sizeof(*$var));" 200 printf '%s\n' "assert($var != NULL);" 201 printf '%s\n' "${var}[0] = argv[0];" 202 for ((n = 0; n < ${#before[@]}; n += 1)); do 203 flag=$(escapeStringLiteral "${before[n]}") 204 printf '%s\n' "${var}[$((n + 1))] = \"$flag\";" 205 done 206 printf '%s\n' "for (int i = 1; i < argc; ++i) {" 207 printf '%s\n' " ${var}[${#before[@]} + i] = argv[i];" 208 printf '%s\n' "}" 209 for ((n = 0; n < ${#after[@]}; n += 1)); do 210 flag=$(escapeStringLiteral "${after[n]}") 211 printf '%s\n' "${var}[${#before[@]} + argc + $n] = \"$flag\";" 212 done 213 printf '%s\n' "${var}[${#before[@]} + argc + ${#after[@]}] = NULL;" 214 printf '%s\n' "argv = $var;" 215} 216 217# chdir DIR 218changeDir() { 219 local dir 220 dir=$(escapeStringLiteral "$1") 221 printf '%s' "assert_success(chdir(\"$dir\"));" 222} 223 224# prefix ENV SEP VAL 225setEnvPrefix() { 226 local env sep val 227 env=$(escapeStringLiteral "$1") 228 sep=$(escapeStringLiteral "$2") 229 val=$(escapeStringLiteral "$3") 230 printf '%s' "set_env_prefix(\"$env\", \"$sep\", \"$val\");" 231 assertValidEnvName "$1" 232} 233 234# suffix ENV SEP VAL 235setEnvSuffix() { 236 local env sep val 237 env=$(escapeStringLiteral "$1") 238 sep=$(escapeStringLiteral "$2") 239 val=$(escapeStringLiteral "$3") 240 printf '%s' "set_env_suffix(\"$env\", \"$sep\", \"$val\");" 241 assertValidEnvName "$1" 242} 243 244# setEnv KEY VALUE 245setEnv() { 246 local key value 247 key=$(escapeStringLiteral "$1") 248 value=$(escapeStringLiteral "$2") 249 printf '%s' "putenv(\"$key=$value\");" 250 assertValidEnvName "$1" 251} 252 253# setDefaultEnv KEY VALUE 254setDefaultEnv() { 255 local key value 256 key=$(escapeStringLiteral "$1") 257 value=$(escapeStringLiteral "$2") 258 printf '%s' "assert_success(setenv(\"$key\", \"$value\", 0));" 259 assertValidEnvName "$1" 260} 261 262# unsetEnv KEY 263unsetEnv() { 264 local key 265 key=$(escapeStringLiteral "$1") 266 printf '%s' "assert_success(unsetenv(\"$key\"));" 267 assertValidEnvName "$1" 268} 269 270# Makes it safe to insert STRING within quotes in a C String Literal. 271# escapeStringLiteral STRING 272escapeStringLiteral() { 273 local result 274 result=${1//$'\\'/$'\\\\'} 275 result=${result//\"/'\"'} 276 result=${result//$'\n'/"\n"} 277 result=${result//$'\r'/"\r"} 278 printf '%s' "$result" 279} 280 281# Indents every non-empty line by 4 spaces. To avoid trailing whitespace, we don't indent empty lines 282# indent4 TEXT_BLOCK 283indent4() { 284 printf '%s' "$1" | awk '{ if ($0 != "") { print " "$0 } else { print $0 }}' 285} 286 287assertValidEnvName() { 288 case "$1" in 289 *=*) printf '\n%s\n' "#error Illegal environment variable name \`$1\` (cannot contain \`=\`)";; 290 "") printf '\n%s\n' "#error Environment variable name can't be empty.";; 291 esac 292} 293 294setEnvPrefixFn() { 295 printf '%s' "\ 296void set_env_prefix(char *env, char *sep, char *prefix) { 297 char *existing = getenv(env); 298 if (existing) { 299 char *val; 300 assert_success(asprintf(&val, \"%s%s%s\", prefix, sep, existing)); 301 assert_success(setenv(env, val, 1)); 302 free(val); 303 } else { 304 assert_success(setenv(env, prefix, 1)); 305 } 306} 307" 308} 309 310setEnvSuffixFn() { 311 printf '%s' "\ 312void set_env_suffix(char *env, char *sep, char *suffix) { 313 char *existing = getenv(env); 314 if (existing) { 315 char *val; 316 assert_success(asprintf(&val, \"%s%s%s\", existing, sep, suffix)); 317 assert_success(setenv(env, val, 1)); 318 free(val); 319 } else { 320 assert_success(setenv(env, suffix, 1)); 321 } 322} 323" 324} 325 326# Embed a C string which shows up as readable text in the compiled binary wrapper, 327# giving instructions for recreating the wrapper. 328# Keep in sync with makeBinaryWrapper.extractCmd 329docstring() { 330 printf '%s' "const char * DOCSTRING = \"$(escapeStringLiteral " 331 332 333# ------------------------------------------------------------------------------------ 334# The C-code for this binary wrapper has been generated using the following command: 335 336 337makeCWrapper $(formatArgs "$@") 338 339 340# (Use \`nix-shell -p makeBinaryWrapper\` to get access to makeCWrapper in your shell) 341# ------------------------------------------------------------------------------------ 342 343 344")\";" 345} 346 347# formatArgs EXECUTABLE ARGS 348formatArgs() { 349 printf '%s' "${1@Q}" 350 shift 351 while [ $# -gt 0 ]; do 352 case "$1" in 353 --set) 354 formatArgsLine 2 "$@" 355 shift 2 356 ;; 357 --set-default) 358 formatArgsLine 2 "$@" 359 shift 2 360 ;; 361 --unset) 362 formatArgsLine 1 "$@" 363 shift 1 364 ;; 365 --prefix) 366 formatArgsLine 3 "$@" 367 shift 3 368 ;; 369 --suffix) 370 formatArgsLine 3 "$@" 371 shift 3 372 ;; 373 --chdir) 374 formatArgsLine 1 "$@" 375 shift 1 376 ;; 377 --add-flags) 378 formatArgsLine 1 "$@" 379 shift 1 380 ;; 381 --append-flags) 382 formatArgsLine 1 "$@" 383 shift 1 384 ;; 385 --argv0) 386 formatArgsLine 1 "$@" 387 shift 1 388 ;; 389 --inherit-argv0) 390 formatArgsLine 0 "$@" 391 ;; 392 esac 393 shift 394 done 395 printf '%s\n' "" 396} 397 398# formatArgsLine ARG_COUNT ARGS 399formatArgsLine() { 400 local ARG_COUNT LENGTH 401 ARG_COUNT=$1 402 LENGTH=$# 403 shift 404 printf '%s' $' \\\n '"$1" 405 shift 406 while [ "$ARG_COUNT" -gt $((LENGTH - $# - 2)) ]; do 407 printf ' %s' "${1@Q}" 408 shift 409 done 410}