356 lines
11 KiB
Bash
Executable File
356 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
repo_root=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
|
|
guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile}
|
|
guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install}
|
|
shepherd_prefix=${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install}
|
|
shepherd_bin=$shepherd_prefix/bin/shepherd
|
|
herd_bin=$shepherd_prefix/bin/herd
|
|
metadata_target=${METADATA_OUT:-}
|
|
|
|
if [ ! -x "$guile_bin" ]; then
|
|
echo "Guile binary is not executable: $guile_bin" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ensure_built() {
|
|
if [ ! -d "$guile_extra_prefix/share/guile/site" ] || \
|
|
! GUILE_LOAD_PATH="$guile_extra_prefix/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \
|
|
GUILE_LOAD_COMPILED_PATH="$guile_extra_prefix/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \
|
|
GUILE_EXTENSIONS_PATH="$guile_extra_prefix/lib/guile/3.0/extensions${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \
|
|
LD_LIBRARY_PATH="$guile_extra_prefix/lib:/tmp/guile-freebsd-validate-install/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
|
"$guile_bin" -c '(catch #t (lambda () (use-modules (fibers)) (display "ok") (newline)) (lambda _ (display "missing") (newline)))' | grep -qx ok; then
|
|
METADATA_OUT= ENV_OUT= "$repo_root/tests/shepherd/build-local-guile-fibers.sh"
|
|
fi
|
|
|
|
if [ ! -x "$shepherd_bin" ] || [ ! -x "$herd_bin" ]; then
|
|
METADATA_OUT= ENV_OUT= GUILE_EXTRA_PREFIX="$guile_extra_prefix" "$repo_root/tests/shepherd/build-local-shepherd.sh"
|
|
fi
|
|
}
|
|
|
|
ensure_built
|
|
|
|
guile_bindir=$(CDPATH= cd -- "$(dirname "$guile_bin")" && pwd)
|
|
guile_prefix=$(CDPATH= cd -- "$guile_bindir/.." && pwd)
|
|
guile_lib_dir=$guile_prefix/lib
|
|
guile_version=$(LD_LIBRARY_PATH="$guile_lib_dir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))')
|
|
extra_site_dir=$guile_extra_prefix/share/guile/site/$guile_version
|
|
extra_site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache
|
|
extra_extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions
|
|
|
|
run_root_env() {
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
env \
|
|
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib" \
|
|
GUILE_LOAD_PATH="$extra_site_dir" \
|
|
GUILE_LOAD_COMPILED_PATH="$extra_site_ccache_dir" \
|
|
GUILE_EXTENSIONS_PATH="$extra_extensions_dir" \
|
|
"$@"
|
|
else
|
|
sudo env \
|
|
LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_lib_dir:/usr/local/lib" \
|
|
GUILE_LOAD_PATH="$extra_site_dir" \
|
|
GUILE_LOAD_COMPILED_PATH="$extra_site_ccache_dir" \
|
|
GUILE_EXTENSIONS_PATH="$extra_extensions_dir" \
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
cleanup=0
|
|
if [ -n "${WORKDIR:-}" ]; then
|
|
workdir=$WORKDIR
|
|
mkdir -p "$workdir"
|
|
else
|
|
workdir=$(mktemp -d /tmp/freebsd-shepherd-service.XXXXXX)
|
|
cleanup=1
|
|
fi
|
|
if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then
|
|
cleanup=0
|
|
fi
|
|
|
|
port=$((19000 + ($$ % 1000)))
|
|
state_dir=$workdir/state
|
|
config_file=$workdir/service-config.scm
|
|
socket_file=$workdir/shepherd.sock
|
|
pid_file=$workdir/shepherd.pid
|
|
shepherd_log=$workdir/shepherd.log
|
|
shepherd_stdout=$workdir/shepherd.out
|
|
metadata_file=$workdir/freebsd-shepherd-service-metadata.txt
|
|
logger_status=$workdir/logger.status
|
|
web_status=$workdir/web.status
|
|
monitor_status=$workdir/file-monitor.status
|
|
crashy_status=$workdir/crashy.status
|
|
service_command_log=$state_dir/service-events.log
|
|
http_response_file=$workdir/http-response.txt
|
|
|
|
cleanup_workdir() {
|
|
run_root_env "$herd_bin" -s "$socket_file" stop root >/dev/null 2>&1 || true
|
|
if [ -f "$pid_file" ]; then
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
kill "$(cat "$pid_file")" >/dev/null 2>&1 || true
|
|
else
|
|
sudo sh -c 'kill "$(cat "$1")" >/dev/null 2>&1 || true' sh "$pid_file"
|
|
fi
|
|
fi
|
|
if [ "$cleanup" -eq 1 ]; then
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
rm -rf "$workdir"
|
|
else
|
|
sudo rm -rf "$workdir"
|
|
fi
|
|
fi
|
|
}
|
|
trap cleanup_workdir EXIT INT TERM
|
|
|
|
mkdir -p "$state_dir"
|
|
chmod 0755 "$workdir"
|
|
chmod 0777 "$state_dir"
|
|
|
|
cat >"$workdir/logger.sh" <<'EOF'
|
|
#!/bin/sh
|
|
log=$1
|
|
ready=$2
|
|
uidfile=$3
|
|
printf 'logger-start uid=%s gid=%s\n' "$(id -u)" "$(id -g)" >> "$log"
|
|
id -u > "$uidfile"
|
|
touch "$ready"
|
|
trap 'echo logger-stop >> "$log"; exit 0' TERM INT
|
|
while :; do
|
|
echo heartbeat >> "$log"
|
|
sleep 1
|
|
done
|
|
EOF
|
|
chmod +x "$workdir/logger.sh"
|
|
|
|
cat >"$workdir/web.sh" <<'EOF'
|
|
#!/bin/sh
|
|
port=$1
|
|
ready=$2
|
|
log=$3
|
|
echo web-start >> "$log"
|
|
touch "$ready"
|
|
trap 'echo web-stop >> "$log"; exit 0' TERM INT
|
|
while :; do
|
|
printf 'HTTP/1.0 200 OK\r\nContent-Length: 20\r\n\r\nshepherd-freebsd-ok\n' | nc -l 127.0.0.1 "$port"
|
|
done
|
|
EOF
|
|
chmod +x "$workdir/web.sh"
|
|
|
|
cat >"$workdir/monitor.sh" <<'EOF'
|
|
#!/bin/sh
|
|
watch=$1
|
|
event=$2
|
|
ready=$3
|
|
log=$4
|
|
echo monitor-start >> "$log"
|
|
touch "$ready"
|
|
trap 'echo monitor-stop >> "$log"; exit 0' TERM INT
|
|
while :; do
|
|
if [ -f "$watch" ] && [ ! -f "$event" ]; then
|
|
echo detected > "$event"
|
|
echo monitor-detected >> "$log"
|
|
fi
|
|
sleep 1
|
|
done
|
|
EOF
|
|
chmod +x "$workdir/monitor.sh"
|
|
|
|
cat >"$workdir/crashy.sh" <<'EOF'
|
|
#!/bin/sh
|
|
counter=$1
|
|
ready=$2
|
|
log=$3
|
|
n=0
|
|
if [ -f "$counter" ]; then
|
|
n=$(cat "$counter")
|
|
fi
|
|
n=$((n + 1))
|
|
echo "$n" > "$counter"
|
|
echo "crashy-attempt-$n" >> "$log"
|
|
if [ "$n" -eq 1 ]; then
|
|
exit 1
|
|
fi
|
|
touch "$ready"
|
|
trap 'echo crashy-stop >> "$log"; exit 0' TERM INT
|
|
while :; do
|
|
sleep 10
|
|
done
|
|
EOF
|
|
chmod +x "$workdir/crashy.sh"
|
|
|
|
cat >"$config_file" <<EOF
|
|
(use-modules (shepherd service)
|
|
(shepherd support))
|
|
(register-services
|
|
(list
|
|
(service '(logger)
|
|
#:documentation "Write a heartbeat log as an unprivileged service."
|
|
#:start (make-forkexec-constructor (list "$workdir/logger.sh" "$service_command_log" "$state_dir/logger.ready" "$state_dir/logger.uid")
|
|
#:user "nobody" #:group "nobody"
|
|
#:directory "$state_dir"
|
|
#:log-file "$state_dir/logger.stdout")
|
|
#:stop (make-kill-destructor)
|
|
#:respawn? #f)
|
|
(service '(web)
|
|
#:documentation "Serve a tiny HTTP response over loopback."
|
|
#:requirement '(logger)
|
|
#:start (make-forkexec-constructor (list "$workdir/web.sh" "$port" "$state_dir/web.ready" "$service_command_log")
|
|
#:user "nobody" #:group "nobody"
|
|
#:directory "$state_dir"
|
|
#:log-file "$state_dir/web.stdout")
|
|
#:stop (make-kill-destructor)
|
|
#:respawn? #f)
|
|
(service '(file-monitor)
|
|
#:documentation "Watch for a flag file and record detection."
|
|
#:requirement '(web)
|
|
#:start (make-forkexec-constructor (list "$workdir/monitor.sh" "$state_dir/watch.flag" "$state_dir/event.flag" "$state_dir/monitor.ready" "$service_command_log")
|
|
#:user "nobody" #:group "nobody"
|
|
#:directory "$state_dir"
|
|
#:log-file "$state_dir/monitor.stdout")
|
|
#:stop (make-kill-destructor)
|
|
#:respawn? #f)
|
|
(service '(crashy)
|
|
#:documentation "Fail once and then stay running to validate respawn."
|
|
#:start (make-forkexec-constructor (list "$workdir/crashy.sh" "$state_dir/crashy.counter" "$state_dir/crashy.ready" "$service_command_log")
|
|
#:directory "$state_dir"
|
|
#:log-file "$state_dir/crashy.stdout")
|
|
#:stop (make-kill-destructor)
|
|
#:respawn? #t)))
|
|
EOF
|
|
|
|
run_root_env "$shepherd_bin" -I -s "$socket_file" -c "$config_file" --pid="$pid_file" -l "$shepherd_log" >"$shepherd_stdout" 2>&1 &
|
|
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
if [ -f "$pid_file" ] && [ -S "$socket_file" ]; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if [ ! -f "$pid_file" ] || [ ! -S "$socket_file" ]; then
|
|
echo "Shepherd did not become ready" >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_root_env "$herd_bin" -s "$socket_file" start file-monitor >/dev/null
|
|
for ready in "$state_dir/logger.ready" "$state_dir/web.ready" "$state_dir/monitor.ready"; do
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
[ -f "$ready" ] && break
|
|
sleep 1
|
|
done
|
|
[ -f "$ready" ] || {
|
|
echo "Expected readiness file missing: $ready" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
run_root_env "$herd_bin" -s "$socket_file" status logger >"$logger_status"
|
|
run_root_env "$herd_bin" -s "$socket_file" status web >"$web_status"
|
|
run_root_env "$herd_bin" -s "$socket_file" status file-monitor >"$monitor_status"
|
|
|
|
fetch -T 5 -qo "$http_response_file" "http://127.0.0.1:$port/"
|
|
http_response=$(tr -d '\r' <"$http_response_file")
|
|
if [ "$http_response" != "shepherd-freebsd-ok" ]; then
|
|
echo "Unexpected HTTP response: $http_response" >&2
|
|
exit 1
|
|
fi
|
|
|
|
touch "$state_dir/watch.flag"
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
[ -f "$state_dir/event.flag" ] && break
|
|
sleep 1
|
|
done
|
|
[ -f "$state_dir/event.flag" ] || {
|
|
echo "Monitor did not detect watched file" >&2
|
|
exit 1
|
|
}
|
|
|
|
run_root_env "$herd_bin" -s "$socket_file" start crashy >/dev/null || true
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
[ -f "$state_dir/crashy.ready" ] && break
|
|
sleep 1
|
|
done
|
|
[ -f "$state_dir/crashy.ready" ] || {
|
|
echo "Respawn test service did not reach steady state" >&2
|
|
exit 1
|
|
}
|
|
run_root_env "$herd_bin" -s "$socket_file" status crashy >"$crashy_status"
|
|
|
|
logger_uid=$(cat "$state_dir/logger.uid")
|
|
crashy_counter=$(cat "$state_dir/crashy.counter")
|
|
if [ "$logger_uid" != "65534" ]; then
|
|
echo "Expected logger to run as nobody (65534), got $logger_uid" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$crashy_counter" -lt 2 ]; then
|
|
echo "Expected respawn counter >= 2, got $crashy_counter" >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_root_env "$herd_bin" -s "$socket_file" stop root >/dev/null || true
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
[ ! -f "$pid_file" ] && break
|
|
sleep 1
|
|
done
|
|
|
|
case $(cat "$logger_status") in
|
|
*"It is running"*) logger_running=yes ;;
|
|
*) logger_running=no ;;
|
|
esac
|
|
case $(cat "$web_status") in
|
|
*"It is running"*) web_running=yes ;;
|
|
*) web_running=no ;;
|
|
esac
|
|
case $(cat "$monitor_status") in
|
|
*"It is running"*) monitor_running=yes ;;
|
|
*) monitor_running=no ;;
|
|
esac
|
|
case $(cat "$crashy_status") in
|
|
*"It is running"*) crashy_running=yes ;;
|
|
*) crashy_running=no ;;
|
|
esac
|
|
case $(cat "$shepherd_stdout") in
|
|
*"System lacks support for 'signalfd'; using fallback mechanism."*) signalfd_fallback=yes ;;
|
|
*) signalfd_fallback=no ;;
|
|
esac
|
|
|
|
cat >"$metadata_file" <<EOF
|
|
workdir=$workdir
|
|
shepherd_prefix=$shepherd_prefix
|
|
guile_bin=$guile_bin
|
|
guile_extra_prefix=$guile_extra_prefix
|
|
port=$port
|
|
socket_file=$socket_file
|
|
pid_file=$pid_file
|
|
shepherd_log=$shepherd_log
|
|
shepherd_stdout=$shepherd_stdout
|
|
service_command_log=$service_command_log
|
|
logger_status_file=$logger_status
|
|
web_status_file=$web_status
|
|
monitor_status_file=$monitor_status
|
|
crashy_status_file=$crashy_status
|
|
logger_running=$logger_running
|
|
web_running=$web_running
|
|
monitor_running=$monitor_running
|
|
crashy_running=$crashy_running
|
|
logger_uid=$logger_uid
|
|
http_response=$http_response
|
|
monitor_detected=$(cat "$state_dir/event.flag")
|
|
crashy_counter=$crashy_counter
|
|
signalfd_fallback=$signalfd_fallback
|
|
EOF
|
|
|
|
if [ -n "$metadata_target" ]; then
|
|
mkdir -p "$(dirname "$metadata_target")"
|
|
cp "$metadata_file" "$metadata_target"
|
|
fi
|
|
|
|
printf 'PASS freebsd-shepherd-service-prototype\n'
|
|
printf 'Work directory: %s\n' "$workdir"
|
|
printf 'Metadata file: %s\n' "$metadata_file"
|
|
if [ -n "$metadata_target" ]; then
|
|
printf 'Copied metadata to: %s\n' "$metadata_target"
|
|
fi
|
|
printf '%s\n' '--- metadata ---'
|
|
cat "$metadata_file"
|