this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 993 lines 30 kB view raw
1#!/usr/bin/env bash 2# triage-syscalls.sh — Automated syscall triage for Darling + Nix 3# 4# This script runs various Nix operations inside a Darling prefix and 5# captures "Unimplemented syscall" messages, ENOSYS errors, and other 6# indicators of missing kernel functionality. The output is a table 7# suitable for pasting into plan/syscall-triage.md. 8# 9# Usage: 10# ./scripts/triage-syscalls.sh [OPTIONS] 11# 12# Options: 13# --prefix <path> Darling prefix (default: ~/.darling or $DPREFIX) 14# --output <file> Write results to file (default: stdout) 15# --strace Also run strace on darlingserver (requires root) 16# --xtrace Enable DARLING_XTRACE for detailed Darwin tracing 17# --operations <list> Comma-separated list of operations to test 18# (default: all). Available: version,eval,store, 19# touch,mv,curl,install,build,channel 20# --timeout <secs> Timeout per operation (default: 60) 21# --help Show this help 22# 23# Output: 24# A Markdown-formatted table of discovered syscalls, with columns: 25# Syscall # | Caller | Operation | Message | Count 26# 27# Prerequisites: 28# - Darling must be installed and `darling shell echo ok` must work 29# - For Nix-related tests, Nix must be installed in the prefix 30# (run scripts/install-nix-in-darling.sh first) 31# 32# See: plan/03-phase1-syscalls.md (Task 1.7) 33# plan/syscall-triage.md 34 35set -euo pipefail 36 37# ── Defaults ──────────────────────────────────────────────────────────────── 38 39DARLING_PREFIX="${DPREFIX:-$HOME/.darling}" 40OUTPUT_FILE="" 41USE_STRACE=0 42USE_XTRACE=0 43OPERATIONS="version,eval,store,touch,mv,curl,build" 44TIMEOUT=60 45SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 46REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" 47 48# Temp directory for logs 49TRIAGE_TMP="" 50 51# Known syscall number → name mapping (macOS/XNU BSD syscalls) 52# Source: src/external/xnu/bsd/kern/syscalls.master 53# This covers the most commonly-seen unimplemented syscalls. 54declare -A SYSCALL_NAMES=( 55 [1]="exit" 56 [2]="fork" 57 [3]="read" 58 [4]="write" 59 [5]="open" 60 [6]="close" 61 [7]="wait4" 62 [9]="link" 63 [10]="unlink" 64 [12]="chdir" 65 [15]="chmod" 66 [16]="chown" 67 [20]="getpid" 68 [23]="setuid" 69 [24]="getuid" 70 [25]="geteuid" 71 [27]="recvmsg" 72 [28]="sendmsg" 73 [29]="recvfrom" 74 [30]="accept" 75 [33]="access" 76 [36]="sync" 77 [37]="kill" 78 [39]="getppid" 79 [41]="dup" 80 [42]="pipe" 81 [43]="getegid" 82 [46]="sigaction" 83 [47]="getgid" 84 [48]="sigprocmask" 85 [49]="getlogin" 86 [50]="setlogin" 87 [51]="acct" 88 [53]="sigaltstack" 89 [54]="ioctl" 90 [56]="revoke" 91 [57]="symlink" 92 [58]="readlink" 93 [59]="execve" 94 [60]="umask" 95 [61]="chroot" 96 [65]="msync" 97 [66]="vfork" 98 [73]="munmap" 99 [74]="mprotect" 100 [75]="madvise" 101 [78]="mincore" 102 [79]="getgroups" 103 [80]="setgroups" 104 [82]="setpgid" 105 [83]="setitimer" 106 [85]="swapon" 107 [86]="getitimer" 108 [89]="getdtablesize" 109 [90]="dup2" 110 [92]="fcntl" 111 [93]="select" 112 [95]="fsync" 113 [96]="setpriority" 114 [97]="socket" 115 [98]="connect" 116 [100]="getpriority" 117 [104]="bind" 118 [105]="setsockopt" 119 [106]="listen" 120 [111]="sigsuspend" 121 [116]="gettimeofday" 122 [117]="getrusage" 123 [118]="getsockopt" 124 [120]="readv" 125 [121]="writev" 126 [122]="settimeofday" 127 [123]="fchown" 128 [124]="fchmod" 129 [128]="rename" 130 [131]="flock" 131 [132]="mkfifo" 132 [133]="sendto" 133 [134]="shutdown" 134 [135]="socketpair" 135 [136]="mkdir" 136 [137]="rmdir" 137 [138]="utimes" 138 [139]="futimes" 139 [140]="adjtime" 140 [142]="gethostuuid" 141 [147]="setsid" 142 [151]="getpgid" 143 [152]="setprivexec" 144 [153]="pread" 145 [154]="pwrite" 146 [157]="statfs" 147 [158]="fstatfs" 148 [159]="unmount" 149 [165]="mount" 150 [167]="csops" 151 [169]="csops_audittoken" 152 [170]="fdatasync" 153 [173]="waitid" 154 [180]="kdebug_trace64" 155 [181]="kdebug_trace" 156 [182]="kdebug_typefilter" 157 [183]="setgid" 158 [184]="setegid" 159 [185]="seteuid" 160 [187]="stat" 161 [188]="fstat" 162 [189]="lstat" 163 [190]="pathconf" 164 [191]="fpathconf" 165 [194]="getrlimit" 166 [195]="setrlimit" 167 [196]="getdirentries" 168 [197]="mmap" 169 [199]="lseek" 170 [200]="truncate" 171 [201]="ftruncate" 172 [202]="sysctl" 173 [203]="mlock" 174 [204]="munlock" 175 [205]="undelete" 176 [216]="mkcomplex" 177 [220]="getattrlist" 178 [221]="setattrlist" 179 [222]="getdirentriesattr" 180 [223]="exchangedata" 181 [225]="searchfs" 182 [226]="delete" 183 [227]="copyfile" 184 [228]="fgetattrlist" 185 [229]="fsetattrlist" 186 [230]="poll" 187 [233]="getxattr" 188 [234]="fgetxattr" 189 [235]="setxattr" 190 [236]="fsetxattr" 191 [237]="removexattr" 192 [238]="fremovexattr" 193 [239]="listxattr" 194 [240]="flistxattr" 195 [241]="fsctl" 196 [242]="initgroups" 197 [243]="posix_spawn" 198 [244]="ffsctl" 199 [247]="nfsclnt" 200 [248]="fhopen" 201 [250]="minherit" 202 [266]="shm_open" 203 [267]="shm_unlink" 204 [268]="sem_open" 205 [269]="sem_close" 206 [270]="sem_unlink" 207 [271]="sem_wait" 208 [272]="sem_trywait" 209 [273]="sem_post" 210 [274]="sysctlbyname" 211 [277]="open_extended" 212 [278]="umask_extended" 213 [279]="stat_extended" 214 [280]="lstat_extended" 215 [281]="fstat_extended" 216 [282]="chmod_extended" 217 [283]="fchmod_extended" 218 [284]="access_extended" 219 [285]="settid" 220 [286]="gettid" 221 [288]="kqueue" 222 [289]="kevent" 223 [296]="mlockall" 224 [297]="munlockall" 225 [301]="issetugid" 226 [302]="__pthread_kill" 227 [303]="__pthread_sigmask" 228 [305]="__disable_threadsignal" 229 [310]="__semwait_signal" 230 [311]="proc_info" 231 [322]="getsid" 232 [324]="pread_nocancel" 233 [325]="pwrite_nocancel" 234 [327]="aio_suspend" 235 [336]="proc_rlimit_control" 236 [338]="iopolicysys" 237 [339]="process_policy" 238 [340]="mlockall" 239 [341]="munlockall" 240 [343]="issetugid" 241 [344]="__pthread_chdir" 242 [345]="__pthread_fchdir" 243 [346]="audit" 244 [347]="auditon" 245 [350]="getaudit_addr" 246 [351]="setaudit_addr" 247 [357]="getentropy" 248 [360]="getattrlistbulk" 249 [361]="clonefileat" 250 [362]="openat" 251 [363]="openat_nocancel" 252 [364]="renameat" 253 [366]="faccessat" 254 [367]="fchmodat" 255 [368]="fchownat" 256 [369]="fstatat" 257 [370]="fstatat64" 258 [371]="linkat" 259 [372]="unlinkat" 260 [373]="readlinkat" 261 [374]="symlinkat" 262 [375]="mkdirat" 263 [376]="getattrlistat" 264 [377]="proc_trace_log" 265 [378]="bsdthread_ctl" 266 [380]="openbyid_np" 267 [381]="recvmsg_x" 268 [382]="sendmsg_x" 269 [384]="guarded_open_np" 270 [385]="guarded_close_np" 271 [386]="guarded_kqueue_np" 272 [387]="change_fdguard_np" 273 [388]="usrctl" 274 [389]="proc_rlimit_control" 275 [394]="coalition" 276 [395]="coalition_info" 277 [396]="necp_match_policy" 278 [397]="getattrlistbulk" 279 [398]="clonefileat" 280 [399]="openat" 281 [400]="openat_nocancel" 282 [401]="renameat" 283 [403]="faccessat" 284 [404]="fchmodat" 285 [405]="fchownat" 286 [406]="fstatat" 287 [407]="fstatat64" 288 [408]="linkat" 289 [409]="unlinkat" 290 [410]="readlinkat" 291 [411]="symlinkat" 292 [412]="mkdirat" 293 [413]="getattrlistat" 294 [414]="proc_trace_log" 295 [415]="bsdthread_ctl" 296 [417]="openbyid_np" 297 [418]="recvmsg_x" 298 [419]="sendmsg_x" 299 [420]="thread_selfusage" 300 [421]="csrctl" 301 [422]="guarded_open_dprotected_np" 302 [423]="guarded_write_np" 303 [424]="guarded_pwrite_np" 304 [425]="guarded_writev_np" 305 [426]="renameatx_np" 306 [427]="mremap_encrypted" 307 [428]="netagent_trigger" 308 [429]="stack_snapshot_with_config" 309 [430]="microstackshot" 310 [431]="grab_pgo_data" 311 [432]="persona" 312 [438]="fs_snapshot" 313 [441]="terminate_with_payload" 314 [442]="abort_with_payload" 315 [443]="necp_session_open" 316 [444]="necp_session_action" 317 [449]="fclonefileat" 318 [450]="fs_snapshot" 319 [452]="terminate_with_payload" 320 [453]="abort_with_payload" 321 [462]="clonefile" 322 [463]="close_nocancel" 323 [464]="accept_nocancel" 324 [468]="msync_nocancel" 325 [469]="fcntl_nocancel" 326 [470]="select_nocancel" 327 [471]="fsgetpath" 328 [473]="pselect" 329 [474]="pselect_nocancel" 330 [475]="read_nocancel" 331 [476]="write_nocancel" 332 [477]="open_dprotected_np" 333 [480]="kevent_qos" 334 [481]="kevent_id" 335 [482]="__mac_execve" 336 [483]="__mac_syscall" 337 [484]="__mac_get_file" 338 [485]="__mac_set_file" 339 [486]="__mac_get_link" 340 [487]="__mac_set_link" 341 [488]="renameatx_np" 342 [489]="setxattr" 343 [500]="getentropy" 344 [515]="ulock_wait" 345 [516]="ulock_wake" 346 [517]="fclonefileat" 347 [518]="fs_snapshot" 348 [519]="terminate_with_payload" 349 [520]="abort_with_payload" 350) 351 352# ── Colors ────────────────────────────────────────────────────────────────── 353 354if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then 355 RED='\033[0;31m' 356 GREEN='\033[0;32m' 357 YELLOW='\033[0;33m' 358 BLUE='\033[0;34m' 359 BOLD='\033[1m' 360 DIM='\033[2m' 361 RESET='\033[0m' 362else 363 RED='' GREEN='' YELLOW='' BLUE='' BOLD='' DIM='' RESET='' 364fi 365 366# ── Helpers ───────────────────────────────────────────────────────────────── 367 368log() { echo -e "${GREEN}[triage]${RESET} $*" >&2; } 369warn() { echo -e "${YELLOW}[triage] WARNING:${RESET} $*" >&2; } 370err() { echo -e "${RED}[triage] ERROR:${RESET} $*" >&2; } 371debug() { echo -e "${DIM}[triage] $*${RESET}" >&2; } 372fatal() { err "$@"; exit 1; } 373 374usage() { 375 sed -n '/^# Usage:/,/^# See:/p' "$0" | sed 's/^# \?//' 376 exit 0 377} 378 379# Run a command inside the Darling prefix, capturing all output 380dsh() { 381 local logfile="$1" 382 shift 383 timeout "$TIMEOUT" darling shell "$@" >"$logfile" 2>&1 || true 384} 385 386# Run a command inside the Darling prefix with bash -lc 387dsh_bash() { 388 local logfile="$1" 389 shift 390 timeout "$TIMEOUT" darling shell bash -lc "$*" >"$logfile" 2>&1 || true 391} 392 393# Run with DARLING_XTRACE if requested 394dsh_traced() { 395 local logfile="$1" 396 shift 397 local env_args=() 398 if [ "$USE_XTRACE" -eq 1 ]; then 399 env_args=(env DARLING_XTRACE=1) 400 fi 401 timeout "$TIMEOUT" "${env_args[@]}" darling shell "$@" >"$logfile" 2>&1 || true 402} 403 404dsh_bash_traced() { 405 local logfile="$1" 406 shift 407 local env_args=() 408 if [ "$USE_XTRACE" -eq 1 ]; then 409 env_args=(env DARLING_XTRACE=1) 410 fi 411 timeout "$TIMEOUT" "${env_args[@]}" darling shell bash -lc "$*" >"$logfile" 2>&1 || true 412} 413 414# Resolve a syscall number to its name 415syscall_name() { 416 local num="$1" 417 if [[ -v "SYSCALL_NAMES[$num]" ]]; then 418 echo "${SYSCALL_NAMES[$num]}" 419 else 420 echo "unknown_$num" 421 fi 422} 423 424# ── Argument Parsing ──────────────────────────────────────────────────────── 425 426while [ $# -gt 0 ]; do 427 case "$1" in 428 --prefix) 429 DARLING_PREFIX="$2" 430 shift 2 431 ;; 432 --output) 433 OUTPUT_FILE="$2" 434 shift 2 435 ;; 436 --strace) 437 USE_STRACE=1 438 shift 439 ;; 440 --xtrace) 441 USE_XTRACE=1 442 shift 443 ;; 444 --operations) 445 OPERATIONS="$2" 446 shift 2 447 ;; 448 --timeout) 449 TIMEOUT="$2" 450 shift 2 451 ;; 452 --help|-h) 453 usage 454 ;; 455 *) 456 fatal "Unknown option: $1 (try --help)" 457 ;; 458 esac 459done 460 461# ── Setup ─────────────────────────────────────────────────────────────────── 462 463TRIAGE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/darling-triage.XXXXXX") 464cleanup() { 465 if [ -n "$TRIAGE_TMP" ] && [ -d "$TRIAGE_TMP" ]; then 466 rm -rf "$TRIAGE_TMP" 467 fi 468} 469trap cleanup EXIT 470 471log "${BOLD}Darling Syscall Triage${RESET}" 472log "Prefix: $DARLING_PREFIX" 473log "Operations: $OPERATIONS" 474log "Timeout: ${TIMEOUT}s per operation" 475log "XTrace: $([ "$USE_XTRACE" -eq 1 ] && echo "enabled" || echo "disabled")" 476log "Strace: $([ "$USE_STRACE" -eq 1 ] && echo "enabled" || echo "disabled")" 477log "Temp dir: $TRIAGE_TMP" 478echo "" >&2 479 480# ── Preflight ─────────────────────────────────────────────────────────────── 481 482if ! command -v darling &>/dev/null; then 483 fatal "darling not found in PATH. Build it first with: nix build .#darling" 484fi 485 486log "Checking darling shell..." 487if ! timeout 30 darling shell echo "ok" &>/dev/null; then 488 fatal "darling shell is not functional. Try: darling shell echo ok" 489fi 490log " darling shell: ${GREEN}OK${RESET}" 491 492# Check if Nix is available 493HAS_NIX=0 494if timeout 15 darling shell bash -lc 'command -v nix' &>/dev/null; then 495 HAS_NIX=1 496 log " Nix in prefix: ${GREEN}found${RESET}" 497else 498 warn "Nix not found in prefix. Nix-specific operations will be skipped." 499 warn "Run scripts/install-nix-in-darling.sh first for full triage." 500fi 501 502echo "" >&2 503 504# ── Strace setup ──────────────────────────────────────────────────────────── 505 506STRACE_PID="" 507STRACE_LOG="" 508 509start_strace() { 510 if [ "$USE_STRACE" -eq 0 ]; then 511 return 512 fi 513 514 local server_pid 515 server_pid=$(pidof darlingserver 2>/dev/null || true) 516 if [ -z "$server_pid" ]; then 517 warn "darlingserver not running; cannot attach strace" 518 return 519 fi 520 521 STRACE_LOG="$TRIAGE_TMP/strace.log" 522 strace -f -p "$server_pid" -e trace=all -o "$STRACE_LOG" & 523 STRACE_PID=$! 524 sleep 1 525 debug "strace attached to darlingserver (pid=$server_pid)" 526} 527 528stop_strace() { 529 if [ -n "$STRACE_PID" ]; then 530 kill "$STRACE_PID" 2>/dev/null || true 531 wait "$STRACE_PID" 2>/dev/null || true 532 STRACE_PID="" 533 fi 534} 535 536# ── Operations ────────────────────────────────────────────────────────────── 537 538# Each operation function takes a log directory and produces a log file 539# named <operation>.log inside that directory. 540 541op_version() { 542 local logdir="$1" 543 544 log " Testing: ${BLUE}darling shell echo ok${RESET}" 545 dsh_traced "$logdir/echo.log" echo "hello from darling" 546 547 log " Testing: ${BLUE}sw_vers${RESET}" 548 dsh_traced "$logdir/sw_vers.log" sw_vers 549 550 log " Testing: ${BLUE}uname -a${RESET}" 551 dsh_traced "$logdir/uname.log" uname -a 552 553 if [ "$HAS_NIX" -eq 1 ]; then 554 log " Testing: ${BLUE}nix --version${RESET}" 555 dsh_bash_traced "$logdir/nix_version.log" "nix --version" 556 557 log " Testing: ${BLUE}nix-env --version${RESET}" 558 dsh_bash_traced "$logdir/nix_env_version.log" "nix-env --version" 559 560 log " Testing: ${BLUE}nix-store --version${RESET}" 561 dsh_bash_traced "$logdir/nix_store_version.log" "nix-store --version" 562 fi 563} 564 565op_eval() { 566 local logdir="$1" 567 568 if [ "$HAS_NIX" -eq 0 ]; then 569 warn " Skipping eval tests (Nix not installed)" 570 return 571 fi 572 573 log " Testing: ${BLUE}nix-instantiate --eval -E '1 + 1'${RESET}" 574 dsh_bash_traced "$logdir/eval_simple.log" "nix-instantiate --eval -E '1 + 1'" 575 576 log " Testing: ${BLUE}nix eval --expr '1 + 1'${RESET}" 577 dsh_bash_traced "$logdir/eval_nix3.log" "nix eval --expr '1 + 1'" 578 579 log " Testing: ${BLUE}builtins.currentSystem${RESET}" 580 dsh_bash_traced "$logdir/eval_system.log" "nix eval --expr 'builtins.currentSystem'" 581 582 log " Testing: ${BLUE}nix eval (complex expression)${RESET}" 583 dsh_bash_traced "$logdir/eval_complex.log" \ 584 "nix eval --expr 'let f = x: if x <= 1 then 1 else x * f (x - 1); in f 10'" 585 586 log " Testing: ${BLUE}nix eval (import)${RESET}" 587 dsh_bash_traced "$logdir/eval_import.log" \ 588 "nix eval --expr 'builtins.length (builtins.attrNames builtins)'" 589} 590 591op_store() { 592 local logdir="$1" 593 594 if [ "$HAS_NIX" -eq 0 ]; then 595 warn " Skipping store tests (Nix not installed)" 596 return 597 fi 598 599 log " Testing: ${BLUE}nix-store --verify${RESET}" 600 dsh_bash_traced "$logdir/store_verify.log" "nix-store --verify --no-build 2>&1 || nix-store --verify" 601 602 log " Testing: ${BLUE}nix-store --dump-db${RESET}" 603 dsh_bash_traced "$logdir/store_dump_db.log" "nix-store --dump-db | head -50" 604 605 log " Testing: ${BLUE}nix-store --gc --print-dead${RESET}" 606 dsh_bash_traced "$logdir/store_gc_dead.log" "nix-store --gc --print-dead 2>&1 | head -20" 607} 608 609op_touch() { 610 local logdir="$1" 611 612 log " Testing: ${BLUE}touch /tmp/triage_test${RESET} (built-in)" 613 dsh_traced "$logdir/touch_builtin.log" /usr/bin/touch /tmp/triage_test_builtin 614 615 log " Testing: ${BLUE}touch -t (timestamp)${RESET}" 616 dsh_traced "$logdir/touch_timestamp.log" /usr/bin/touch -t 202301011200 /tmp/triage_test_ts 617 618 if [ "$HAS_NIX" -eq 1 ]; then 619 log " Testing: ${BLUE}Nix coreutils touch${RESET}" 620 dsh_bash_traced "$logdir/touch_nix.log" \ 621 "if type -P touch >/dev/null; then touch /tmp/triage_test_nix; else echo 'touch not on PATH'; fi" 622 fi 623 624 # Clean up 625 dsh "$TRIAGE_TMP/touch_cleanup.log" rm -f /tmp/triage_test_builtin /tmp/triage_test_ts /tmp/triage_test_nix 626} 627 628op_mv() { 629 local logdir="$1" 630 631 log " Testing: ${BLUE}mv (built-in)${RESET}" 632 dsh_traced "$logdir/mv_builtin_setup.log" /usr/bin/touch /tmp/triage_mv_src 633 dsh_traced "$logdir/mv_builtin.log" /bin/mv /tmp/triage_mv_src /tmp/triage_mv_dst 634 635 if [ "$HAS_NIX" -eq 1 ]; then 636 log " Testing: ${BLUE}Nix coreutils mv${RESET}" 637 dsh_bash_traced "$logdir/mv_nix_setup.log" "touch /tmp/triage_mv_nix_src" 638 dsh_bash_traced "$logdir/mv_nix.log" \ 639 "if type -P mv >/dev/null; then mv /tmp/triage_mv_nix_src /tmp/triage_mv_nix_dst; else echo 'mv not on PATH'; fi" 640 fi 641 642 # Clean up 643 dsh "$TRIAGE_TMP/mv_cleanup.log" rm -f /tmp/triage_mv_src /tmp/triage_mv_dst /tmp/triage_mv_nix_src /tmp/triage_mv_nix_dst 644} 645 646op_curl() { 647 local logdir="$1" 648 649 log " Testing: ${BLUE}curl --version${RESET}" 650 dsh_traced "$logdir/curl_version.log" curl --version 651 652 log " Testing: ${BLUE}curl https://cache.nixos.org/nix-cache-info${RESET}" 653 dsh_traced "$logdir/curl_https.log" curl -sfI https://cache.nixos.org/nix-cache-info 654 655 log " Testing: ${BLUE}curl http (plain)${RESET}" 656 dsh_traced "$logdir/curl_http.log" curl -sfI http://example.com/ 657} 658 659op_build() { 660 local logdir="$1" 661 662 if [ "$HAS_NIX" -eq 0 ]; then 663 warn " Skipping build tests (Nix not installed)" 664 return 665 fi 666 667 log " Testing: ${BLUE}trivial derivation build${RESET}" 668 dsh_bash_traced "$logdir/build_trivial.log" \ 669 "nix-build --no-out-link --expr 'derivation { name = \"triage-test\"; builder = \"/bin/bash\"; args = [\"-c\" \"echo ok > \\\$out\"]; system = \"x86_64-darwin\"; }' 2>&1" 670 671 log " Testing: ${BLUE}sandbox-exec passthrough${RESET}" 672 dsh_traced "$logdir/sandbox_exec.log" \ 673 /usr/bin/sandbox-exec -f /dev/null -D _GLOBAL_TMP_DIR=/tmp /bin/echo "sandbox-exec passthrough ok" 674} 675 676op_channel() { 677 local logdir="$1" 678 679 if [ "$HAS_NIX" -eq 0 ]; then 680 warn " Skipping channel tests (Nix not installed)" 681 return 682 fi 683 684 log " Testing: ${BLUE}nix-channel --list${RESET}" 685 dsh_bash_traced "$logdir/channel_list.log" "nix-channel --list" 686} 687 688op_install() { 689 local logdir="$1" 690 691 if [ "$HAS_NIX" -eq 0 ]; then 692 warn " Skipping install tests (Nix not installed)" 693 return 694 fi 695 696 log " Testing: ${BLUE}nix-env --query --installed${RESET}" 697 dsh_bash_traced "$logdir/env_query.log" "nix-env --query --installed 2>&1 || true" 698} 699 700# ── Run Operations ────────────────────────────────────────────────────────── 701 702IFS=',' read -ra OPS <<< "$OPERATIONS" 703 704LOGDIR="$TRIAGE_TMP/logs" 705mkdir -p "$LOGDIR" 706 707start_strace 708 709for op in "${OPS[@]}"; do 710 op=$(echo "$op" | tr -d '[:space:]') 711 opdir="$LOGDIR/$op" 712 mkdir -p "$opdir" 713 714 log "${BOLD}Running operation: $op${RESET}" 715 716 case "$op" in 717 version) op_version "$opdir" ;; 718 eval) op_eval "$opdir" ;; 719 store) op_store "$opdir" ;; 720 touch) op_touch "$opdir" ;; 721 mv) op_mv "$opdir" ;; 722 curl) op_curl "$opdir" ;; 723 build) op_build "$opdir" ;; 724 channel) op_channel "$opdir" ;; 725 install) op_install "$opdir" ;; 726 *) 727 warn "Unknown operation: $op (skipping)" 728 ;; 729 esac 730 731 echo "" >&2 732done 733 734stop_strace 735 736# ── Analysis ──────────────────────────────────────────────────────────────── 737 738log "${BOLD}Analyzing results...${RESET}" 739 740# Patterns to search for in the logs 741PATTERNS=( 742 'Unimplemented syscall' 743 'unimplemented syscall' 744 'ENOSYS' 745 'not.implemented' 746 'STUB' 747 'Function not implemented' 748 'Segmentation fault' 749 'Bus error' 750 'Abort trap' 751 'Illegal instruction' 752 'Bad system call' 753 'Bad file descriptor' 754) 755 756PATTERN_REGEX=$(IFS='|'; echo "${PATTERNS[*]}") 757 758# Collect all findings into a single file 759FINDINGS="$TRIAGE_TMP/findings.txt" 760: > "$FINDINGS" 761 762# Process each log file 763while IFS= read -r -d '' logfile; do 764 relpath="${logfile#"$LOGDIR"/}" 765 operation="${relpath%%/*}" 766 testname="${relpath#*/}" 767 testname="${testname%.log}" 768 769 if grep -qiE "$PATTERN_REGEX" "$logfile" 2>/dev/null; then 770 while IFS= read -r line; do 771 echo "$operation|$testname|$line" >> "$FINDINGS" 772 done < <(grep -iE "$PATTERN_REGEX" "$logfile" 2>/dev/null || true) 773 fi 774done < <(find "$LOGDIR" -name '*.log' -print0 2>/dev/null) 775 776# Also check strace log if available 777if [ -n "$STRACE_LOG" ] && [ -f "$STRACE_LOG" ]; then 778 while IFS= read -r line; do 779 echo "strace|darlingserver|$line" >> "$FINDINGS" 780 done < <(grep -iE 'ENOSYS|ENOTSUP' "$STRACE_LOG" 2>/dev/null | head -100 || true) 781fi 782 783# ── Extract syscall numbers ───────────────────────────────────────────────── 784 785SYSCALLS_FILE="$TRIAGE_TMP/syscalls.txt" 786: > "$SYSCALLS_FILE" 787 788# Pattern: "Unimplemented syscall (NNN)" or "Unimplemented syscall NNN" 789while IFS= read -r line; do 790 if [[ "$line" =~ [Uu]nimplemented[[:space:]]syscall[[:space:]]*\(?([0-9]+)\)? ]]; then 791 num="${BASH_REMATCH[1]}" 792 name=$(syscall_name "$num") 793 op="${line%%|*}" 794 echo "$num|$name|$op|$line" >> "$SYSCALLS_FILE" 795 fi 796done < "$FINDINGS" 797 798# ── Other issues (non-syscall) ────────────────────────────────────────────── 799 800OTHER_FILE="$TRIAGE_TMP/other_issues.txt" 801: > "$OTHER_FILE" 802 803while IFS= read -r line; do 804 if [[ ! "$line" =~ [Uu]nimplemented[[:space:]]syscall ]]; then 805 echo "$line" >> "$OTHER_FILE" 806 fi 807done < "$FINDINGS" 808 809# ── Generate Report ───────────────────────────────────────────────────────── 810 811generate_report() { 812 local timestamp 813 timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 814 815 cat <<EOF 816# Syscall Triage Report 817 818Generated: $timestamp 819Darling prefix: $DARLING_PREFIX 820Operations tested: $OPERATIONS 821XTrace: $([ "$USE_XTRACE" -eq 1 ] && echo "enabled" || echo "disabled") 822Strace: $([ "$USE_STRACE" -eq 1 ] && echo "enabled" || echo "disabled") 823Nix available: $([ "$HAS_NIX" -eq 1 ] && echo "yes" || echo "no") 824 825--- 826 827## Unimplemented Syscalls 828 829EOF 830 831 if [ -s "$SYSCALLS_FILE" ]; then 832 echo "| Syscall # | Name | Operation | Count | Sample Message |" 833 echo "|-----------|------|-----------|-------|----------------|" 834 835 # Deduplicate and count occurrences 836 sort "$SYSCALLS_FILE" | while IFS='|' read -r num name op full_line; do 837 echo "$num|$name|$op" 838 done | sort | uniq -c | sort -rn | while read -r count entry; do 839 IFS='|' read -r num name op <<< "$entry" 840 # Get the first sample message for this syscall 841 sample=$(grep "^${num}|" "$SYSCALLS_FILE" | head -1 | cut -d'|' -f4-) 842 # Truncate long messages 843 if [ ${#sample} -gt 80 ]; then 844 sample="${sample:0:77}..." 845 fi 846 # Escape pipe chars in the message for markdown 847 sample="${sample//|/\\|}" 848 echo "| $num | \`$name\` | $op | $count | $sample |" 849 done 850 851 echo "" 852 else 853 echo "*No unimplemented syscalls detected.* :tada:" 854 echo "" 855 echo "This either means:" 856 echo "1. All tested operations use fully-implemented syscalls, or" 857 echo "2. The operations didn't exercise enough codepaths (try --xtrace or more operations)" 858 echo "" 859 fi 860 861 cat <<EOF 862## Other Issues 863 864EOF 865 866 if [ -s "$OTHER_FILE" ]; then 867 echo "| Category | Operation | Test | Message |" 868 echo "|----------|-----------|------|---------|" 869 870 while IFS='|' read -r op test msg; do 871 # Categorize the issue 872 local category="Unknown" 873 case "$msg" in 874 *[Ss]egmentation*fault*|*SIGSEGV*) category="**SEGFAULT**" ;; 875 *[Bb]us*error*|*SIGBUS*) category="**BUS ERROR**" ;; 876 *[Aa]bort*|*SIGABRT*) category="**ABORT**" ;; 877 *[Ii]llegal*instruction*|*SIGILL*) category="**SIGILL**" ;; 878 *ENOSYS*) category="ENOSYS" ;; 879 *STUB*|*[Ss]tub*) category="Stub" ;; 880 *[Nn]ot.implemented*) category="Not impl" ;; 881 *[Bb]ad*file*descriptor*) category="Bad FD" ;; 882 *) category="Other" ;; 883 esac 884 885 # Truncate long messages 886 local short_msg="$msg" 887 if [ ${#short_msg} -gt 80 ]; then 888 short_msg="${short_msg:0:77}..." 889 fi 890 short_msg="${short_msg//|/\\|}" 891 892 echo "| $category | $op | $test | $short_msg |" 893 done < "$OTHER_FILE" 894 895 echo "" 896 else 897 echo "*No other issues detected.*" 898 echo "" 899 fi 900 901 cat <<EOF 902## Summary 903 904- **Total log files analyzed**: $(find "$LOGDIR" -name '*.log' 2>/dev/null | wc -l) 905- **Files with findings**: $([ -s "$FINDINGS" ] && wc -l < "$FINDINGS" || echo 0) lines 906- **Unique unimplemented syscalls**: $([ -s "$SYSCALLS_FILE" ] && cut -d'|' -f1 "$SYSCALLS_FILE" | sort -u | wc -l || echo 0) 907- **Other issues**: $([ -s "$OTHER_FILE" ] && wc -l < "$OTHER_FILE" || echo 0) 908 909## Recommended Actions 910 911EOF 912 913 if [ -s "$SYSCALLS_FILE" ]; then 914 echo "### Must Fix (causes crashes / blocks Nix operations)" 915 echo "" 916 917 # List unique syscalls sorted by number 918 local seen_nums="" 919 while IFS='|' read -r num name op _; do 920 if [[ ! " $seen_nums " =~ " $num " ]]; then 921 seen_nums="$seen_nums $num" 922 echo "- **Syscall $num** (\`$name\`): Add to syscall triage table in \`plan/syscall-triage.md\`" 923 fi 924 done < <(sort -t'|' -k1,1n "$SYSCALLS_FILE" | sort -t'|' -k1,1n -u) 925 926 echo "" 927 fi 928 929 cat <<EOF 930### Next Steps 931 9321. Add any new syscalls to \`plan/syscall-triage.md\` 9332. For each "Must fix" syscall, determine the best implementation strategy: 934 - Full translation to Linux equivalent 935 - Stub returning ENOTSUP (if caller handles gracefully) 936 - Stub returning 0 (if call is informational/optional) 9373. Re-run this triage after implementing fixes to verify they work 9384. Run with \`--xtrace\` for more detailed tracing if needed 939 940## Raw Logs 941 942Log files are saved in: \`$TRIAGE_TMP/logs/\` 943 944EOF 945 946 # List all log files and whether they had issues 947 echo "| Log File | Status | Size |" 948 echo "|----------|--------|------|" 949 while IFS= read -r -d '' logfile; do 950 local relpath="${logfile#"$LOGDIR"/}" 951 local size 952 size=$(wc -c < "$logfile") 953 local status="${GREEN}clean${RESET}" 954 if grep -qiE "$PATTERN_REGEX" "$logfile" 2>/dev/null; then 955 status="${RED}issues found${RESET}" 956 elif [ "$size" -eq 0 ]; then 957 status="${YELLOW}empty${RESET}" 958 fi 959 echo "| \`$relpath\` | $status | ${size}B |" 960 done < <(find "$LOGDIR" -name '*.log' -print0 2>/dev/null | sort -z) 961 962 echo "" 963 echo "---" 964 echo "*Generated by \`scripts/triage-syscalls.sh\` — see [plan/syscall-triage.md](../plan/syscall-triage.md)*" 965} 966 967# ── Output ────────────────────────────────────────────────────────────────── 968 969REPORT="$TRIAGE_TMP/report.md" 970generate_report > "$REPORT" 971 972if [ -n "$OUTPUT_FILE" ]; then 973 cp "$REPORT" "$OUTPUT_FILE" 974 log "Report saved to: ${BOLD}$OUTPUT_FILE${RESET}" 975else 976 echo "" >&2 977 log "${BOLD}═══ Triage Report ═══${RESET}" 978 echo "" >&2 979 cat "$REPORT" 980fi 981 982# Print a short summary to stderr regardless 983echo "" >&2 984UNIQUE_SYSCALLS=$([ -s "$SYSCALLS_FILE" ] && cut -d'|' -f1 "$SYSCALLS_FILE" | sort -u | wc -l || echo 0) 985OTHER_COUNT=$([ -s "$OTHER_FILE" ] && wc -l < "$OTHER_FILE" || echo 0) 986 987if [ "$UNIQUE_SYSCALLS" -eq 0 ] && [ "$OTHER_COUNT" -eq 0 ]; then 988 log "${GREEN}${BOLD}No issues found!${RESET} All tested operations passed cleanly." 989else 990 log "Found ${RED}${BOLD}$UNIQUE_SYSCALLS${RESET} unimplemented syscall(s) and ${YELLOW}${BOLD}$OTHER_COUNT${RESET} other issue(s)." 991fi 992log "Full logs: $TRIAGE_TMP/logs/" 993[ -n "$OUTPUT_FILE" ] && log "Report: $OUTPUT_FILE"