nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1#! /usr/bin/env bash
2
3set -eu -o pipefail
4
5# A shell implementation for pgrep as we don't want to depend on procps
6pgrep(){
7 PATTERN="$1"
8 for pid_dir in /proc/[0-9]*; do
9 pid="${pid_dir##*/}"
10
11 # Attempt to open /proc/<PID>/cmdline for reading on a new file descriptor
12 # If we can't read it (no permission or doesn't exist), skip
13 exec {fd}< "$pid_dir/cmdline" 2>/dev/null || continue
14
15 cmdline=""
16 # Read each null-delimited token from /proc/<PID>/cmdline
17 # and join them with a space for easier pattern matching
18 while IFS= read -r -d $'\0' arg <&$fd; do
19 if [[ -z "$cmdline" ]]; then
20 cmdline="$arg"
21 else
22 cmdline="$cmdline $arg"
23 fi
24 done
25
26 # Close the file descriptor
27 exec {fd}>&-
28
29 # If cmdline is non-empty and matches the pattern, print the PID
30 if [[ -n "$cmdline" && "$cmdline" =~ $PATTERN ]]; then
31 echo "$pid"
32 fi
33 done
34}
35
36# helper to extract variables from the build env
37getVar(){
38 while IFS= read -r -d $'\0' line; do
39 case "$line" in
40 *"$1="* )
41 echo "$line"
42 ;;
43 esac
44 done < /proc/$pid/environ \
45 | cut -d "=" -f 2
46}
47
48id="$1"
49pids="$(pgrep "sleep $id" || :)"
50if [ -z "$pids" ]; then
51 echo "Error: No process found for 'sleep $id'. The build must still be running in order to attach. Also make sure it's not on a remote builder." >&2
52 exit 1s
53elif [ "$(echo "$pids" | wc -l)" -ne 1 ]; then
54 echo "Error: Multiple processes found matching 'sleep $id'" >&2
55 exit 1
56fi
57pid="$(echo "$pids" | head -n1)"
58
59
60# get the build top level directory inside the sandbox (eg. /build)
61buildDir=$(getVar NIX_BUILD_TOP)
62
63# bash is needed to load the env vars, as we do not know the syntax of the debug shell.
64# bashInteractive is used instead of bash, as we depend on it anyways, due to it being
65# the default debug shell
66bashInteractive="$(getVar bashInteractive)"
67# the debug shell will be started as interactive shell after loading the env vars
68debugShell="$(getVar debugShell)"
69# to drop the user into the working directory at the point of failure
70pwd="$(readlink /proc/$pid/cwd)"
71
72# enter the namespace of the failed build
73# bash needs to be executed with --init-file /build/env-vars to include the bash native
74# variables like ones declared via `declare -a`.
75# If another shell is chosen via `debugShell`, it will only have simple env vars avaialable.
76exec nsenter --mount --ipc --uts --pid --user --setuid follow --setgid follow --net --target "$pid" "$bashInteractive" -c "
77 set -eu -o pipefail
78 source \"$buildDir/env-vars\"
79 cd \"$pwd\"
80 if [ -n \"$debugShell\" ]; then
81 exec \"$debugShell\"
82 else
83 exec \"$bashInteractive\" --init-file \"$buildDir/env-vars\"
84 fi
85"