diff --git a/docs/setup.md b/docs/setup.md index cbf9825..6093c31 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -280,6 +280,18 @@ scripts/systemd/manage-uce-service.sh restart scripts/systemd/manage-uce-service.sh logs 200 ``` +The server binary accepts only these process modes: + +```bash +bin/uce_fastcgi.linux.bin # start the server +bin/uce_fastcgi.linux.bin --precompile +bin/uce_fastcgi.linux.bin --help +``` + +Help and invalid arguments exit before configuration is loaded or any listener +is opened. Invalid or extra arguments return status 2. This ordering prevents a +diagnostic invocation from replacing or removing a configured Unix socket. + Managed restart builds and precompiles the complete candidate ABI generation before it switches the service. Precompile uses two low-priority processes by default; set `PRECOMPILE_JOBS` in `/etc/uce/settings.cfg` to tune the bounded diff --git a/scripts/run_cli_tests.sh b/scripts/run_cli_tests.sh index b47902a..a0fc53a 100755 --- a/scripts/run_cli_tests.sh +++ b/scripts/run_cli_tests.sh @@ -87,5 +87,6 @@ if [[ "$action" == "run" ]]; then timeout --signal=TERM --kill-after=5s 120s scripts/test_wasm_metadata_buffer.sh timeout --signal=TERM --kill-after=5s 150s scripts/test_wasm_metadata_deadline.sh scripts/test_wasm_source_locations.sh + scripts/test_server_arguments.sh scripts/test_socket_activation.sh fi diff --git a/scripts/test_server_arguments.sh b/scripts/test_server_arguments.sh new file mode 100755 index 0000000..c3b5214 --- /dev/null +++ b/scripts/test_server_arguments.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")/.." + +binary="$PWD/bin/uce_fastcgi.linux.bin" +root=$(mktemp -d /tmp/uce-server-arguments.XXXXXX) +listener_pid="" +cleanup() { + [[ -n "$listener_pid" ]] && kill "$listener_pid" 2>/dev/null || true + rm -rf "$root" +} +trap cleanup EXIT + +cfg="$root/settings.cfg" +socket_path="$root/sentinel.sock" +cp /etc/uce/settings.cfg "$cfg" +sed -E -i \ + -e "s|^[[:space:]]*FCGI_SOCKET_PATH[[:space:]]*=.*|FCGI_SOCKET_PATH=|" \ + -e "s|^[[:space:]]*FCGI_PORT[[:space:]]*=.*|FCGI_PORT=|" \ + -e "s|^[[:space:]]*CLI_SOCKET_PATH[[:space:]]*=.*|CLI_SOCKET_PATH=$socket_path|" \ + -e "s|^[[:space:]]*HTTP_PORT[[:space:]]*=.*|HTTP_PORT=|" \ + -e "s|^[[:space:]]*PROACTIVE_COMPILE_ENABLED[[:space:]]*=.*|PROACTIVE_COMPILE_ENABLED=false|" \ + -e "s|^[[:space:]]*WORKER_COUNT[[:space:]]*=.*|WORKER_COUNT=1|" \ + -e "s|^[[:space:]]*BIN_DIRECTORY[[:space:]]*=.*|BIN_DIRECTORY=$root/bin|" \ + -e "s|^[[:space:]]*TMP_UPLOAD_PATH[[:space:]]*=.*|TMP_UPLOAD_PATH=$root/upload|" \ + -e "s|^[[:space:]]*SESSION_PATH[[:space:]]*=.*|SESSION_PATH=$root/session|" \ + "$cfg" + +python3 -c 'import socket,sys,time; s=socket.socket(socket.AF_UNIX); s.bind(sys.argv[1]); s.listen(); time.sleep(30)' "$socket_path" & +listener_pid=$! +for _ in $(seq 1 50); do + [[ -S "$socket_path" ]] && break + sleep 0.02 +done +[[ -S "$socket_path" ]] +socket_inode=$(stat -c %i "$socket_path") + +invoke() { + local output="$1" + shift + timeout --signal=TERM --kill-after=1s 2s unshare --mount --fork \ + bash -c 'mount --bind "$1" /etc/uce/settings.cfg; exec "$2" "${@:3}"' \ + _ "$cfg" "$binary" "$@" >"$output.stdout" 2>"$output.stderr" +} + +for option in --help -h; do + invoke "$root/help" "$option" + grep -q '^Usage: uce_fastcgi' "$root/help.stdout" + grep -q -- '--precompile' "$root/help.stdout" + [[ ! -s "$root/help.stderr" ]] +done + +for arguments in '--unknown' '--precompile extra' '--help extra'; do + read -r -a argv <<<"$arguments" + set +e + invoke "$root/invalid" "${argv[@]}" + rc=$? + set -e + [[ $rc -eq 2 ]] + grep -q '^invalid arguments$' "$root/invalid.stderr" + grep -q '^Usage: uce_fastcgi' "$root/invalid.stderr" + [[ ! -s "$root/invalid.stdout" ]] +done + +[[ -S "$socket_path" ]] +[[ "$(stat -c %i "$socket_path")" == "$socket_inode" ]] +kill -0 "$listener_pid" + +echo 'server argument handling passed: help and invalid arguments exited before listener setup' diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index 6f50ccd..65f6837 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -1866,17 +1866,39 @@ int precompile_unit_generation() return(total.failed == 0 && !candidate_rejected ? 0 : 1); } +void print_fastcgi_usage(FILE* stream) +{ + fprintf(stream, + "Usage: uce_fastcgi [--precompile]\n" + " uce_fastcgi --help\n\n" + "Without options, start the FastCGI server.\n" + " --precompile Compile the current source generation without starting listeners.\n" + " -h, --help Show this help and exit.\n"); +} + int main(int argc, char** argv) { // systemd connects stdout to a pipe, which otherwise block-buffers child // diagnostics and assigns stale failures a misleading later journal time. setvbuf(stdout, 0, _IOLBF, 0); setvbuf(stderr, 0, _IONBF, 0); + if(argc == 2 && (String(argv[1]) == "--help" || String(argv[1]) == "-h")) + { + print_fastcgi_usage(stdout); + return(0); + } + bool precompile = argc == 2 && String(argv[1]) == "--precompile"; + if(argc != 1 && !precompile) + { + fprintf(stderr, "invalid arguments\n"); + print_fastcgi_usage(stderr); + return(2); + } // Warm up libgcc's backtrace state so the first in-handler backtrace() // after a fault does not allocate. backtrace(request_fault_frames, 4); process_start_directory(); - if(argc == 2 && String(argv[1]) == "--precompile") + if(precompile) return(precompile_unit_generation()); init_base_process();