| 1 | #include "internal.h" |
| 2 | |
| 3 | #include <asm.h> |
| 4 | #include <console/panic.h> |
| 5 | #include <console/printf.h> |
| 6 | #include <global.h> |
| 7 | #include <mem/alloc.h> |
| 8 | #include <mem/pmm.h> |
| 9 | #include <ndjson.h> |
| 10 | #include <nightmare/perturb.h> |
| 11 | #include <nightmare/record.h> |
| 12 | #include <smp/smp.h> |
| 13 | #include <string.h> |
| 14 | #include <thread/thread.h> |
| 15 | #include <time/time.h> |
| 16 | |
| 17 | #ifndef CHARMOS_COMMIT |
| 18 | #define CHARMOS_COMMIT "unknown" |
| 19 | #endif |
| 20 | |
| 21 | #define NIGHTMARE_MIB (1024ULL * 1024ULL) |
| 22 | #define NIGHTMARE_DEFAULT_DURATION_MS 1000 |
| 23 | #define NIGHTMARE_DEFAULT_DRAIN_MS 20000 |
| 24 | #define NIGHTMARE_DEFAULT_STAT_MS 5000 |
| 25 | |
| 26 | struct nightmare_runtime nightmare_runtime; |
| 27 | |
| 28 | static void nightmare_exit(uint8_t code, const char *reason) { |
| 29 | ndjson_bye(code, reason); |
| 30 | qemu_exit(code); |
| 31 | while (true) |
| 32 | cpu_relax(); |
| 33 | } |
| 34 | |
| 35 | const char *nightmare_result_string(enum nightmare_result result) { |
| 36 | switch (result) { |
| 37 | case NIGHTMARE_RESULT_OK: return "ok" ; |
| 38 | case NIGHTMARE_RESULT_FINDING: return "finding" ; |
| 39 | case NIGHTMARE_RESULT_FAIL: return "fail" ; |
| 40 | case NIGHTMARE_RESULT_STALL: return "stall" ; |
| 41 | case NIGHTMARE_RESULT_SKIP: return "skip" ; |
| 42 | default: return "unknown" ; |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | const char *nightmare_skip_string(enum nightmare_skip_reason reason) { |
| 47 | switch (reason) { |
| 48 | case NIGHTMARE_SKIP_NONE: return "none" ; |
| 49 | case NIGHTMARE_SKIP_NOT_COMPILED: return "not_compiled" ; |
| 50 | case NIGHTMARE_SKIP_NO_SUCH_NIGHTMARE: return "no_such_nightmare" ; |
| 51 | case NIGHTMARE_SKIP_NEEDS_SMP: return "needs_smp" ; |
| 52 | case NIGHTMARE_SKIP_NEEDS_PREEMPT: return "needs_preempt" ; |
| 53 | case NIGHTMARE_SKIP_NEEDS_ASAN: return "needs_asan" ; |
| 54 | case NIGHTMARE_SKIP_NEEDS_INJECT: return "needs_inject" ; |
| 55 | case NIGHTMARE_SKIP_RAM_LOW: return "ram_low" ; |
| 56 | case NIGHTMARE_SKIP_SEED_UNUSED: return "seed_unused" ; |
| 57 | case NIGHTMARE_SKIP_SEED_MISSING: return "seed_missing" ; |
| 58 | case NIGHTMARE_SKIP_SERVICE_MISSING: return "service_missing" ; |
| 59 | case NIGHTMARE_SKIP_PREPARE_REFUSED: return "prepare_refused" ; |
| 60 | default: return "unknown" ; |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | const char *nightmare_seed_policy_string(enum nightmare_seed_policy policy) { |
| 65 | switch (policy) { |
| 66 | case NIGHTMARE_SEED_IGNORED: return "ignored" ; |
| 67 | case NIGHTMARE_SEED_OPTIONAL: return "optional" ; |
| 68 | case NIGHTMARE_SEED_REQUIRED: return "required" ; |
| 69 | default: return "unknown" ; |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | const char *nightmare_seed_mode_string(enum nightmare_seed_mode mode) { |
| 74 | switch (mode) { |
| 75 | case NIGHTMARE_SEED_SPLIT: return "split" ; |
| 76 | case NIGHTMARE_SEED_SEEDFUL: return "seedful" ; |
| 77 | case NIGHTMARE_SEED_SEEDLESS: return "seedless" ; |
| 78 | default: return "unknown" ; |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | static uint8_t nightmare_exit_for_result(enum nightmare_result result) { |
| 83 | switch (result) { |
| 84 | case NIGHTMARE_RESULT_OK: return NIGHTMARE_EXIT_OK; |
| 85 | case NIGHTMARE_RESULT_FINDING: return NIGHTMARE_EXIT_FINDING; |
| 86 | case NIGHTMARE_RESULT_FAIL: return NIGHTMARE_EXIT_FAIL; |
| 87 | case NIGHTMARE_RESULT_STALL: return NIGHTMARE_EXIT_STALL; |
| 88 | case NIGHTMARE_RESULT_SKIP: return NIGHTMARE_EXIT_SKIP; |
| 89 | default: return NIGHTMARE_EXIT_FAIL; |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | static uint64_t nightmare_progress_snapshot(void) { |
| 94 | #ifdef TEST_NIGHTMARE_ENABLED |
| 95 | return nightmare_progress_sum_irq(); |
| 96 | #else |
| 97 | return 0; |
| 98 | #endif |
| 99 | } |
| 100 | |
| 101 | static void append_cap(char *caps, size_t cap, const char *value) { |
| 102 | size_t used = strlen(str: caps); |
| 103 | if (used >= cap - 1) |
| 104 | return; |
| 105 | snprintf(buffer: caps + used, buffer_len: (int) (cap - used), format: "%s%s" , used ? "," : "" , value); |
| 106 | } |
| 107 | |
| 108 | static void nightmare_build_caps(void) { |
| 109 | nightmare_runtime.caps[0] = '\0'; |
| 110 | append_cap(caps: nightmare_runtime.caps, cap: sizeof(nightmare_runtime.caps), |
| 111 | value: "preempt" ); |
| 112 | #ifdef DEBUG_ASAN |
| 113 | append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps), "asan" ); |
| 114 | #endif |
| 115 | #ifdef DEBUG_LOCK_CHK |
| 116 | append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps), |
| 117 | "lock_chk" ); |
| 118 | #endif |
| 119 | #ifdef INJECT_ENABLED |
| 120 | append_cap(nightmare_runtime.caps, sizeof(nightmare_runtime.caps), |
| 121 | "inject" ); |
| 122 | #endif |
| 123 | } |
| 124 | |
| 125 | #ifdef TEST_NIGHTMARE_ENABLED |
| 126 | static const struct nightmare *nightmare_resolve(const char *name) { |
| 127 | const struct nightmare *match = NULL; |
| 128 | for (struct nightmare *nm = __skernel_nightmares; nm < __ekernel_nightmares; |
| 129 | nm++) { |
| 130 | if (!nm->name) |
| 131 | nightmare_panic("nightmare registry contains an unnamed entry" ); |
| 132 | if (strcmp(str1: nm->name, str2: name) != 0) |
| 133 | continue; |
| 134 | if (match) |
| 135 | nightmare_panic("duplicate nightmare name '%s' in %s and %s" , name, |
| 136 | match->fname, nm->fname); |
| 137 | match = nm; |
| 138 | } |
| 139 | return match; |
| 140 | } |
| 141 | #endif |
| 142 | |
| 143 | static void |
| 144 | nightmare_record_resolved_boot(const struct nightmare_cmdline_config *config, |
| 145 | const char *requested) { |
| 146 | const struct nightmare *nm = nightmare_runtime.ctx.nm; |
| 147 | nightmare_record_boot(record: &(struct nightmare_boot_record){ |
| 148 | .test = nm ? nm->name : requested, |
| 149 | .seed = config->seed, |
| 150 | .seed_policy = |
| 151 | nm ? nightmare_seed_policy_string(policy: nm->seed_policy) : "unknown" , |
| 152 | .seed_mode = nightmare_seed_mode_string(mode: config->seed_mode), |
| 153 | .intensity = nightmare_runtime.ctx.intensity, |
| 154 | .intensity_val = nightmare_runtime.ctx.intensity_val, |
| 155 | .workers = nightmare_runtime.ctx.worker_count, |
| 156 | .smp = global.core_count, |
| 157 | .mem_mib = pmm_get_usable_ram() / NIGHTMARE_MIB, |
| 158 | .caps = nightmare_runtime.caps, |
| 159 | .campaign_id = config->campaign_id ? config->campaign_id : "" , |
| 160 | .boot_index = config->boot_index, |
| 161 | .commit = CHARMOS_COMMIT, |
| 162 | }); |
| 163 | } |
| 164 | |
| 165 | static void nightmare_emit_verdict(struct nightmare_verdict verdict, |
| 166 | const char *fallback_reason) { |
| 167 | size_t findings = atomic_load_explicit(&nightmare_runtime.finding_count, |
| 168 | memory_order_acquire); |
| 169 | enum nightmare_result result = |
| 170 | nightmare_result_with_findings(result: verdict.result, findings); |
| 171 | |
| 172 | const char *reason = verdict.reason; |
| 173 | if (result == NIGHTMARE_RESULT_SKIP) |
| 174 | reason = nightmare_skip_string(reason: verdict.skip_reason); |
| 175 | if (!reason) |
| 176 | reason = fallback_reason; |
| 177 | |
| 178 | time_ms_t now = time_get_ms(); |
| 179 | uint64_t elapsed = |
| 180 | nightmare_runtime.started_ms ? now - nightmare_runtime.started_ms : 0; |
| 181 | nightmare_record_verdict(record: &(struct nightmare_verdict_record){ |
| 182 | .result = nightmare_result_string(result), |
| 183 | .reason = reason ? reason : "unknown" , |
| 184 | .duration_ms = elapsed, |
| 185 | .progress = nightmare_progress_snapshot(), |
| 186 | .findings = findings, |
| 187 | .msg = verdict.msg ? verdict.msg : "" , |
| 188 | }); |
| 189 | nightmare_exit(code: nightmare_exit_for_result(result), |
| 190 | reason: reason ? reason : nightmare_result_string(result)); |
| 191 | } |
| 192 | |
| 193 | enum nightmare_result |
| 194 | nightmare_result_with_findings(enum nightmare_result result, size_t findings) { |
| 195 | if (result == NIGHTMARE_RESULT_OK && findings) |
| 196 | return NIGHTMARE_RESULT_FINDING; |
| 197 | return result; |
| 198 | } |
| 199 | |
| 200 | #ifdef TEST_NIGHTMARE_ENABLED |
| 201 | static enum nightmare_skip_reason |
| 202 | nightmare_seed_refusal(const struct nightmare *nm, |
| 203 | const struct nightmare_cmdline_config *config) { |
| 204 | if (config->seed_mode != NIGHTMARE_SEED_SEEDLESS && !config->seed_present) |
| 205 | return NIGHTMARE_SKIP_SEED_MISSING; |
| 206 | if (nm->seed_policy == NIGHTMARE_SEED_IGNORED && |
| 207 | config->seed_mode == NIGHTMARE_SEED_SEEDFUL) |
| 208 | return NIGHTMARE_SKIP_SEED_UNUSED; |
| 209 | if (nm->seed_policy == NIGHTMARE_SEED_REQUIRED && |
| 210 | config->seed_mode != NIGHTMARE_SEED_SEEDFUL) |
| 211 | return NIGHTMARE_SKIP_SEED_MISSING; |
| 212 | return NIGHTMARE_SKIP_NONE; |
| 213 | } |
| 214 | |
| 215 | static bool |
| 216 | nightmare_has_missing_service(const struct nightmare *nm, |
| 217 | const struct nightmare_cmdline_config *config) { |
| 218 | if (nm->perturb) { |
| 219 | for (const char *const *name = nm->perturb; *name; name++) { |
| 220 | if (!nightmare_perturb_lookup(name: *name)) |
| 221 | return true; |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | if (!config->perturb_present) |
| 226 | return false; |
| 227 | for (size_t i = 0; i < config->perturb.count; i++) { |
| 228 | const char *name = NULL; |
| 229 | if (cmdline_extract_const_string(val: &config->perturb.items[i], out: &name) != |
| 230 | ERR_OK || |
| 231 | !nightmare_perturb_lookup(name)) |
| 232 | return true; |
| 233 | } |
| 234 | return false; |
| 235 | } |
| 236 | |
| 237 | static enum nightmare_skip_reason |
| 238 | nightmare_preflight(const struct nightmare *nm, |
| 239 | const struct nightmare_cmdline_config *config) { |
| 240 | if ((nm->requires & NIGHTMARE_REQ_SMP) && global.core_count < 2) |
| 241 | return NIGHTMARE_SKIP_NEEDS_SMP; |
| 242 | /* Today, we always have preemption enabled, however TODO: someday |
| 243 | * we can implement support for a non-preemptive kernel*/ |
| 244 | if ((nm->requires & NIGHTMARE_REQ_PREEMPT) == 0) { |
| 245 | /* No-op */ |
| 246 | } |
| 247 | #ifndef DEBUG_ASAN |
| 248 | if (nm->requires & NIGHTMARE_REQ_ASAN) |
| 249 | return NIGHTMARE_SKIP_NEEDS_ASAN; |
| 250 | #endif |
| 251 | #ifndef INJECT_ENABLED |
| 252 | if (nm->requires & NIGHTMARE_REQ_INJECT) |
| 253 | return NIGHTMARE_SKIP_NEEDS_INJECT; |
| 254 | #endif |
| 255 | if (pmm_get_usable_ram() / NIGHTMARE_MIB < nm->min_mem_mib) |
| 256 | return NIGHTMARE_SKIP_RAM_LOW; |
| 257 | |
| 258 | enum nightmare_skip_reason seed = nightmare_seed_refusal(nm, config); |
| 259 | if (seed != NIGHTMARE_SKIP_NONE) |
| 260 | return seed; |
| 261 | if (nightmare_has_missing_service(nm, config)) |
| 262 | return NIGHTMARE_SKIP_SERVICE_MISSING; |
| 263 | return NIGHTMARE_SKIP_NONE; |
| 264 | } |
| 265 | |
| 266 | static void nightmare_soft_deadline(struct timer *timer) { |
| 267 | (void) timer; |
| 268 | nightmare_publish_stop(reason: NM_STOP_BUDGET); |
| 269 | } |
| 270 | |
| 271 | static void nightmare_hard_deadline(struct timer *timer) { |
| 272 | (void) timer; |
| 273 | bool expected = false; |
| 274 | if (!atomic_compare_exchange_strong_explicit( |
| 275 | &nightmare_runtime.terminal, &expected, true, memory_order_acq_rel, |
| 276 | memory_order_acquire)) |
| 277 | return; |
| 278 | |
| 279 | nightmare_publish_stop(reason: NM_STOP_STALL); |
| 280 | ndjson_enter_panic(); |
| 281 | nightmare_record_verdict(record: &(struct nightmare_verdict_record){ |
| 282 | .result = "stall" , |
| 283 | .reason = "drain_timeout" , |
| 284 | .duration_ms = time_get_ms() - nightmare_runtime.started_ms, |
| 285 | .progress = nightmare_progress_sum_irq(), |
| 286 | .findings = atomic_load_explicit(&nightmare_runtime.finding_count, |
| 287 | memory_order_relaxed), |
| 288 | .msg = "hard deadline expired before teardown completed" , |
| 289 | }); |
| 290 | nightmare_exit(code: NIGHTMARE_EXIT_STALL, reason: "hard deadline" ); |
| 291 | } |
| 292 | |
| 293 | static void nightmare_arm_deadlines(time_ms_t duration_ms, |
| 294 | time_ms_t drain_grace_ms) { |
| 295 | time_us_t now = time_get_us(); |
| 296 | timer_init(timer: &nightmare_runtime.soft_timer, func: nightmare_soft_deadline, NULL); |
| 297 | nightmare_runtime.soft_timer.flags = TIMER_FLAG_IRQ; |
| 298 | nightmare_runtime.soft_timer.expiration_us = now + MS_TO_US(duration_ms); |
| 299 | timer_add_global(timer: &nightmare_runtime.soft_timer); |
| 300 | |
| 301 | timer_init(timer: &nightmare_runtime.hard_timer, func: nightmare_hard_deadline, NULL); |
| 302 | nightmare_runtime.hard_timer.flags = TIMER_FLAG_IRQ; |
| 303 | nightmare_runtime.hard_timer.expiration_us = |
| 304 | now + MS_TO_US(duration_ms + drain_grace_ms); |
| 305 | timer_add_global(timer: &nightmare_runtime.hard_timer); |
| 306 | } |
| 307 | |
| 308 | static uint64_t nightmare_worker_seed(const struct nightmare_ctx *ctx, |
| 309 | size_t index) { |
| 310 | bool seeded = |
| 311 | ctx->seed_mode == NIGHTMARE_SEED_SEEDFUL || |
| 312 | (ctx->seed_mode == NIGHTMARE_SEED_SPLIT && index >= ctx->worker_count); |
| 313 | uint64_t base = seeded ? ctx->seed : time_get_ns(); |
| 314 | base ^= PRNG_SPLITMIX64_GAMMA * (index + 1); |
| 315 | struct nightmare_rng rng = {.state = base}; |
| 316 | return nightmare_rand(rng: &rng); |
| 317 | } |
| 318 | |
| 319 | static bool nightmare_spawn_threads(void) { |
| 320 | size_t created = 0; |
| 321 | for (size_t i = 0; i < nightmare_runtime.total_worker_count; i++) { |
| 322 | struct nightmare_worker *worker = &nightmare_runtime.workers[i]; |
| 323 | worker->index = i; |
| 324 | if (i < nightmare_runtime.ctx.worker_count) { |
| 325 | worker->role = "worker" ; |
| 326 | } else { |
| 327 | size_t pidx = i - nightmare_runtime.ctx.worker_count; |
| 328 | worker->role = nightmare_runtime.perturbers[pidx]->name; |
| 329 | } |
| 330 | worker->rng.state = |
| 331 | nightmare_worker_seed(ctx: &nightmare_runtime.ctx, index: worker->index); |
| 332 | struct thread *thread = thread_spawn_joinable( |
| 333 | name: "nightmare_%s_%zu" , entry: nightmare_thread_main, arg: worker, worker->role, i); |
| 334 | if (!thread) |
| 335 | goto fail; |
| 336 | atomic_store_explicit(&worker->th, thread, memory_order_release); |
| 337 | created++; |
| 338 | } |
| 339 | |
| 340 | nightmare_runtime.heartbeat = thread_spawn_joinable( |
| 341 | name: "nightmare_heartbeat" , entry: nightmare_heartbeat_main, NULL); |
| 342 | if (!nightmare_runtime.heartbeat) |
| 343 | goto fail; |
| 344 | return true; |
| 345 | |
| 346 | fail: |
| 347 | nightmare_publish_stop(reason: NM_STOP_FAIL); |
| 348 | complete_all(c: &nightmare_runtime.start); |
| 349 | for (size_t i = created; i > 0; i--) { |
| 350 | struct thread *th = atomic_load_explicit( |
| 351 | &nightmare_runtime.workers[i - 1].th, memory_order_acquire); |
| 352 | if (th) |
| 353 | thread_join(t: th); |
| 354 | } |
| 355 | return false; |
| 356 | } |
| 357 | |
| 358 | static void nightmare_join_threads(void) { |
| 359 | for (size_t i = nightmare_runtime.ctx.worker_count; |
| 360 | i < nightmare_runtime.total_worker_count; i++) { |
| 361 | struct thread *thread = atomic_load_explicit( |
| 362 | &nightmare_runtime.workers[i].th, memory_order_acquire); |
| 363 | if (thread) |
| 364 | thread_join(t: thread); |
| 365 | } |
| 366 | for (size_t i = 0; i < nightmare_runtime.ctx.worker_count; i++) { |
| 367 | struct thread *thread = atomic_load_explicit( |
| 368 | &nightmare_runtime.workers[i].th, memory_order_acquire); |
| 369 | if (thread) |
| 370 | thread_join(t: thread); |
| 371 | } |
| 372 | if (nightmare_runtime.heartbeat) |
| 373 | thread_join(t: nightmare_runtime.heartbeat); |
| 374 | } |
| 375 | |
| 376 | void nightmare_publish_perturb_verdict(struct nightmare_verdict verdict) { |
| 377 | if (verdict.result == NIGHTMARE_RESULT_OK || |
| 378 | atomic_load_explicit(&nightmare_runtime.perturb_verdict_ready, |
| 379 | memory_order_acquire)) |
| 380 | return; |
| 381 | |
| 382 | if (verdict.reason) { |
| 383 | snprintf(buffer: nightmare_runtime.perturb_reason, |
| 384 | buffer_len: sizeof(nightmare_runtime.perturb_reason), format: "%s" , |
| 385 | verdict.reason); |
| 386 | verdict.reason = nightmare_runtime.perturb_reason; |
| 387 | } |
| 388 | if (verdict.msg) { |
| 389 | snprintf(buffer: nightmare_runtime.perturb_msg, |
| 390 | buffer_len: sizeof(nightmare_runtime.perturb_msg), format: "%s" , verdict.msg); |
| 391 | verdict.msg = nightmare_runtime.perturb_msg; |
| 392 | } |
| 393 | nightmare_runtime.perturb_verdict = verdict; |
| 394 | atomic_store_explicit(&nightmare_runtime.perturb_verdict_ready, true, |
| 395 | memory_order_release); |
| 396 | } |
| 397 | |
| 398 | bool nightmare_load_perturb_verdict(struct nightmare_verdict *out) { |
| 399 | if (!atomic_load_explicit(&nightmare_runtime.perturb_verdict_ready, |
| 400 | memory_order_acquire)) |
| 401 | return false; |
| 402 | *out = nightmare_runtime.perturb_verdict; |
| 403 | return true; |
| 404 | } |
| 405 | static void nightmare_add_perturber(const struct nightmare_perturb_desc *desc) { |
| 406 | if (!desc || nightmare_runtime.perturber_count >= NIGHTMARE_MAX_PERTURBERS) |
| 407 | return; |
| 408 | for (size_t i = 0; i < nightmare_runtime.perturber_count; i++) { |
| 409 | if (nightmare_runtime.perturbers[i] == desc) |
| 410 | return; |
| 411 | } |
| 412 | nightmare_runtime.perturbers[nightmare_runtime.perturber_count++] = desc; |
| 413 | } |
| 414 | |
| 415 | static void |
| 416 | nightmare_collect_perturbers(const struct nightmare *nm, |
| 417 | const struct nightmare_cmdline_config *config) { |
| 418 | nightmare_runtime.perturber_count = 0; |
| 419 | if (nm->perturb) { |
| 420 | for (const char *const *name = nm->perturb; *name; name++) |
| 421 | nightmare_add_perturber(desc: nightmare_perturb_lookup(name: *name)); |
| 422 | } |
| 423 | if (config->perturb_present) { |
| 424 | for (size_t i = 0; i < config->perturb.count; i++) { |
| 425 | const char *name = NULL; |
| 426 | if (cmdline_extract_const_string(val: &config->perturb.items[i], |
| 427 | out: &name) == ERR_OK && |
| 428 | name) |
| 429 | nightmare_add_perturber(desc: nightmare_perturb_lookup(name)); |
| 430 | } |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | static struct nightmare_verdict |
| 435 | nightmare_finalize_verdict(const struct nightmare *nm) { |
| 436 | struct nightmare_verdict final = NIGHTMARE_OK; |
| 437 | struct nightmare_verdict perturb_verdict; |
| 438 | bool has_perturb_verdict = nightmare_load_perturb_verdict(out: &perturb_verdict); |
| 439 | if (nm->ops && nm->ops->quiesce_check) { |
| 440 | final = nm->ops->quiesce_check(&nightmare_runtime.ctx); |
| 441 | nightmare_record_quiesce(record: &(struct nightmare_quiesce_record){ |
| 442 | .result = nightmare_result_string(result: final.result), |
| 443 | .checks = 1, |
| 444 | }); |
| 445 | } |
| 446 | if (has_perturb_verdict) |
| 447 | final = perturb_verdict; |
| 448 | else if (final.result == NIGHTMARE_RESULT_OK && nm->ops && nm->ops->finish) |
| 449 | final = nm->ops->finish(&nightmare_runtime.ctx); |
| 450 | |
| 451 | enum nightmare_stop stop = |
| 452 | atomic_load_explicit(&nightmare_runtime.stop, memory_order_acquire); |
| 453 | return nightmare_verdict_for_stop(verdict: final, stop); |
| 454 | } |
| 455 | |
| 456 | struct nightmare_verdict |
| 457 | nightmare_verdict_for_stop(struct nightmare_verdict final, |
| 458 | enum nightmare_stop stop) { |
| 459 | if (stop == NM_STOP_STALL) |
| 460 | final = (struct nightmare_verdict){ |
| 461 | .result = NIGHTMARE_RESULT_STALL, |
| 462 | .reason = "liveness" , |
| 463 | }; |
| 464 | else if (stop == NM_STOP_FAIL && final.result == NIGHTMARE_RESULT_OK) |
| 465 | final = NIGHTMARE_FAIL("harness" , "harness requested termination" ); |
| 466 | |
| 467 | return final; |
| 468 | } |
| 469 | #endif |
| 470 | |
| 471 | void nightmare_run(void) { |
| 472 | struct nightmare_cmdline_config config; |
| 473 | nightmare_cmdline_get(config: &config); |
| 474 | if (!config.selector) |
| 475 | return; |
| 476 | |
| 477 | nightmare_runtime = (struct nightmare_runtime){ |
| 478 | .ctx = {.seed = config.seed, |
| 479 | .seed_present = config.seed_present, |
| 480 | .seed_mode = config.seed_mode}, |
| 481 | .active = ATOMIC_VAR_INIT(false), |
| 482 | .stop = ATOMIC_VAR_INIT(NM_RUN), |
| 483 | .quiesce_requested = ATOMIC_VAR_INIT(false), |
| 484 | .parked_count = ATOMIC_VAR_INIT(0), |
| 485 | .finding_count = ATOMIC_VAR_INIT(0), |
| 486 | .terminal = ATOMIC_VAR_INIT(false), |
| 487 | .perturb_verdict_ready = ATOMIC_VAR_INIT(false), |
| 488 | .stat_interval_ms = config.stat_interval_ms ? config.stat_interval_ms |
| 489 | : NIGHTMARE_DEFAULT_STAT_MS, |
| 490 | .campaign_id = config.campaign_id, |
| 491 | .boot_index = config.boot_index, |
| 492 | }; |
| 493 | completion_init(c: &nightmare_runtime.start, COMPLETION_INIT_NORMAL); |
| 494 | nightmare_runtime.started_ms = time_get_ms(); |
| 495 | nightmare_build_caps(); |
| 496 | |
| 497 | #ifndef TEST_NIGHTMARE_ENABLED |
| 498 | nightmare_record_resolved_boot(&config, config.selector); |
| 499 | nightmare_emit_verdict(NIGHTMARE_SKIP(NIGHTMARE_SKIP_NOT_COMPILED), |
| 500 | "not_compiled" ); |
| 501 | #else |
| 502 | const struct nightmare *nm = nightmare_resolve(name: config.selector); |
| 503 | nightmare_runtime.ctx.nm = nm; |
| 504 | if (!nm) { |
| 505 | nightmare_record_resolved_boot(config: &config, requested: config.selector); |
| 506 | nightmare_emit_verdict(NIGHTMARE_SKIP(NIGHTMARE_SKIP_NO_SUCH_NIGHTMARE), |
| 507 | fallback_reason: "no_such_nightmare" ); |
| 508 | } |
| 509 | |
| 510 | nightmare_runtime.ctx.intensity = |
| 511 | config.intensity != NIGHTMARE_INTENSITY_SENTINEL |
| 512 | ? config.intensity |
| 513 | : (nm->intensity != NIGHTMARE_INTENSITY_SENTINEL |
| 514 | ? nm->intensity |
| 515 | : NIGHTMARE_INTENSITY_DEFAULT); |
| 516 | nightmare_runtime.ctx.intensity_val = |
| 517 | scaled_param_eval(desc: &nm->intensity_desc, value: nightmare_runtime.ctx.intensity); |
| 518 | nightmare_runtime.ctx.worker_count = |
| 519 | nightmare_runtime.ctx.intensity_val |
| 520 | ? nightmare_runtime.ctx.intensity_val |
| 521 | : 1; |
| 522 | |
| 523 | time_ms_t duration_ms = |
| 524 | config.duration_ms |
| 525 | ? config.duration_ms |
| 526 | : (nm->default_duration_ms ? nm->default_duration_ms |
| 527 | : NIGHTMARE_DEFAULT_DURATION_MS); |
| 528 | time_ms_t drain_ms = config.drain_grace_ms ? config.drain_grace_ms |
| 529 | : NIGHTMARE_DEFAULT_DRAIN_MS; |
| 530 | nightmare_runtime.ctx.soft_deadline_ms = |
| 531 | nightmare_runtime.started_ms + duration_ms; |
| 532 | nightmare_runtime.ctx.hard_deadline_ms = |
| 533 | nightmare_runtime.ctx.soft_deadline_ms + drain_ms; |
| 534 | |
| 535 | nightmare_record_resolved_boot(config: &config, requested: config.selector); |
| 536 | |
| 537 | enum nightmare_skip_reason refusal = nightmare_preflight(nm, config: &config); |
| 538 | if (refusal != NIGHTMARE_SKIP_NONE) |
| 539 | nightmare_emit_verdict(NIGHTMARE_SKIP(refusal), |
| 540 | fallback_reason: nightmare_skip_string(reason: refusal)); |
| 541 | |
| 542 | atomic_store_explicit(&nightmare_runtime.active, true, |
| 543 | memory_order_release); |
| 544 | nightmare_arm_deadlines(duration_ms, drain_grace_ms: drain_ms); |
| 545 | |
| 546 | struct nightmare_verdict prepared = NIGHTMARE_OK; |
| 547 | if (nm->ops && nm->ops->prepare) |
| 548 | prepared = nm->ops->prepare(&nightmare_runtime.ctx); |
| 549 | if (prepared.result != NIGHTMARE_RESULT_OK) { |
| 550 | timer_shutdown_sync(timer: &nightmare_runtime.soft_timer); |
| 551 | timer_shutdown_sync(timer: &nightmare_runtime.hard_timer); |
| 552 | if (prepared.result == NIGHTMARE_RESULT_SKIP) |
| 553 | prepared = NIGHTMARE_SKIP(NIGHTMARE_SKIP_PREPARE_REFUSED); |
| 554 | nightmare_emit_verdict(verdict: prepared, fallback_reason: "prepare" ); |
| 555 | } |
| 556 | |
| 557 | nightmare_collect_perturbers(nm, config: &config); |
| 558 | |
| 559 | nightmare_runtime.total_worker_count = |
| 560 | nightmare_runtime.ctx.worker_count + nightmare_runtime.perturber_count; |
| 561 | |
| 562 | nightmare_runtime.workers = kmalloc(nightmare_runtime.total_worker_count * |
| 563 | sizeof(*nightmare_runtime.workers), |
| 564 | ALLOC_FLAGS_ZERO); |
| 565 | if (!nightmare_runtime.workers) { |
| 566 | timer_shutdown_sync(timer: &nightmare_runtime.soft_timer); |
| 567 | timer_shutdown_sync(timer: &nightmare_runtime.hard_timer); |
| 568 | nightmare_emit_verdict( |
| 569 | NIGHTMARE_FAIL("worker_alloc" , "could not allocate worker state" ), |
| 570 | fallback_reason: "worker_alloc" ); |
| 571 | } |
| 572 | |
| 573 | if (!nightmare_spawn_threads()) { |
| 574 | timer_shutdown_sync(timer: &nightmare_runtime.soft_timer); |
| 575 | timer_shutdown_sync(timer: &nightmare_runtime.hard_timer); |
| 576 | nightmare_emit_verdict( |
| 577 | NIGHTMARE_FAIL("thread_create" , "could not create all threads" ), |
| 578 | fallback_reason: "thread_create" ); |
| 579 | } |
| 580 | |
| 581 | if (!nightmare_liveness_start(threshold_ms: config.stall_threshold_ms, policy: config.on_stall)) |
| 582 | nightmare_panic("could not start liveness detector" ); |
| 583 | complete_all(c: &nightmare_runtime.start); |
| 584 | nightmare_join_threads(); |
| 585 | nightmare_liveness_stop(); |
| 586 | nightmare_publish_stop(reason: NM_STOP_BUDGET); |
| 587 | |
| 588 | timer_shutdown_sync(timer: &nightmare_runtime.soft_timer); |
| 589 | timer_shutdown_sync(timer: &nightmare_runtime.hard_timer); |
| 590 | |
| 591 | struct nightmare_verdict final = nightmare_finalize_verdict(nm); |
| 592 | atomic_store_explicit(&nightmare_runtime.active, false, |
| 593 | memory_order_release); |
| 594 | |
| 595 | kfree(nightmare_runtime.workers); |
| 596 | nightmare_runtime.workers = NULL; |
| 597 | if (nightmare_runtime.ctx.private) { |
| 598 | kfree(nightmare_runtime.ctx.private); |
| 599 | nightmare_runtime.ctx.private = NULL; |
| 600 | } |
| 601 | |
| 602 | bool expected = false; |
| 603 | if (atomic_compare_exchange_strong_explicit( |
| 604 | &nightmare_runtime.terminal, &expected, true, memory_order_acq_rel, |
| 605 | memory_order_acquire)) |
| 606 | nightmare_emit_verdict(verdict: final, fallback_reason: "completed" ); |
| 607 | #endif |
| 608 | } |
| 609 | |