Skip to main content

test_pid/
main.rs

1#![no_std]
2#![no_main]
3
4use core::{
5    arch::asm,
6    panic::PanicInfo,
7    sync::atomic::{AtomicUsize, Ordering},
8};
9use strat9_syscall::{call, error::Error, number};
10
11static mut COW_SENTINEL: u64 = 0x1122_3344_5566_7788;
12static mut COW_MULTI: [u8; 4096 * 4] = [0; 4096 * 4];
13static mut THREAD_STACK_A: [u8; 4096 * 2] = [0; 4096 * 2];
14static mut THREAD_STACK_B: [u8; 4096 * 2] = [0; 4096 * 2];
15static THREAD_STARTED: AtomicUsize = AtomicUsize::new(0);
16static THREAD_TID_SLOT_0: AtomicUsize = AtomicUsize::new(0);
17static THREAD_TID_SLOT_1: AtomicUsize = AtomicUsize::new(0);
18
19#[repr(C)]
20#[derive(Clone, Copy)]
21struct ThreadCase {
22    slot: usize,
23    expected_exit: i32,
24    spin_loops: usize,
25}
26
27static THREAD_CASE_A: ThreadCase = ThreadCase {
28    slot: 0,
29    expected_exit: 33,
30    spin_loops: 64,
31};
32
33static THREAD_CASE_B: ThreadCase = ThreadCase {
34    slot: 1,
35    expected_exit: 44,
36    spin_loops: 128,
37};
38
39/// Writes fd.
40fn write_fd(fd: usize, msg: &str) {
41    let _ = call::write(fd, msg.as_bytes());
42}
43
44/// Implements log.
45fn log(msg: &str) {
46    write_fd(1, msg);
47}
48
49/// Implements log err.
50fn log_err(msg: &str) {
51    write_fd(2, msg);
52}
53
54/// Implements log nl.
55fn log_nl() {
56    log("\n");
57}
58
59/// Implements log sep star.
60fn log_sep_star() {
61    log("************************************************************\n");
62}
63
64/// Implements log sep eq.
65fn log_sep_eq() {
66    log("============================================================\n");
67}
68
69/// Implements log section.
70fn log_section(title: &str) {
71    log_sep_star();
72    log("[init-test] ");
73    log(title);
74    log_nl();
75    log_sep_eq();
76}
77
78/// Implements log u64.
79fn log_u64(mut value: u64) {
80    let mut buf = [0u8; 21];
81    if value == 0 {
82        write_fd(1, "0");
83        return;
84    }
85
86    let mut i = buf.len();
87    while value > 0 {
88        i -= 1;
89        buf[i] = b'0' + (value % 10) as u8;
90        value /= 10;
91    }
92
93    let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
94    write_fd(1, s);
95}
96
97/// Implements log i64.
98fn log_i64(value: i64) {
99    if value < 0 {
100        write_fd(1, "-");
101        log_u64(value.unsigned_abs());
102    } else {
103        log_u64(value as u64);
104    }
105}
106
107/// Implements log hex u64.
108fn log_hex_u64(mut value: u64) {
109    let mut buf = [0u8; 16];
110    for i in (0..16).rev() {
111        let nibble = (value & 0xF) as u8;
112        buf[i] = if nibble < 10 {
113            b'0' + nibble
114        } else {
115            b'a' + (nibble - 10)
116        };
117        value >>= 4;
118    }
119    write_fd(1, "0x");
120    let s = unsafe { core::str::from_utf8_unchecked(&buf) };
121    write_fd(1, s);
122}
123
124/// Implements log result.
125fn log_result(label: &str, res: core::result::Result<usize, Error>) -> Option<usize> {
126    log("[init-test] ");
127    log(label);
128    log(" => ");
129    match res {
130        Ok(v) => {
131            log("OK value=");
132            log_u64(v as u64);
133            log(" hex=");
134            log_hex_u64(v as u64);
135            log_nl();
136            Some(v)
137        }
138        Err(e) => {
139            log("ERR ");
140            log(e.name());
141            log(" (errno=");
142            log_u64(e.to_errno() as u64);
143            log(")");
144            log_nl();
145            None
146        }
147    }
148}
149
150/// Implements decode wait status.
151fn decode_wait_status(status: i32) {
152    let exit_code = ((status >> 8) & 0xff) as u8;
153    let signal = (status & 0x7f) as u8;
154    log("[init-test] wait status decode: raw=");
155    log_i64(status as i64);
156    log(" hex=");
157    log_hex_u64(status as u32 as u64);
158    log(" exit_code=");
159    log_u64(exit_code as u64);
160    log(" signal=");
161    log_u64(signal as u64);
162    log_nl();
163}
164
165/// Implements raw syscall.
166unsafe fn raw_syscall(nr: usize, a1: usize, a2: usize, a3: usize) -> usize {
167    let mut ret = nr;
168    unsafe {
169        asm!(
170            "syscall",
171            inout("rax") ret,
172            in("rdi") a1,
173            in("rsi") a2,
174            in("rdx") a3,
175            out("rcx") _,
176            out("r11") _,
177            options(nostack),
178        );
179    }
180    ret
181}
182
183/// Implements log raw ret.
184fn log_raw_ret(label: &str, ret: usize) {
185    log("[init-test] RAW ");
186    log(label);
187    log(" => dec=");
188    log_u64(ret as u64);
189    log(" hex=");
190    log_hex_u64(ret as u64);
191    if (ret as isize) < 0 {
192        log(" signed=");
193        log_i64(ret as isize as i64);
194        if let Err(e) = Error::demux(ret) {
195            log(" err=");
196            log(e.name());
197        }
198    }
199    log_nl();
200}
201
202/// Implements cow addr.
203fn cow_addr() -> u64 {
204    core::ptr::addr_of!(COW_SENTINEL) as u64
205}
206
207/// Implements cow read.
208fn cow_read() -> u64 {
209    unsafe { core::ptr::read_volatile(core::ptr::addr_of!(COW_SENTINEL)) }
210}
211
212/// Implements cow write.
213fn cow_write(value: u64) {
214    unsafe {
215        core::ptr::write_volatile(core::ptr::addr_of_mut!(COW_SENTINEL), value);
216    }
217}
218
219/// Implements cow multi addr.
220fn cow_multi_addr() -> u64 {
221    core::ptr::addr_of!(COW_MULTI) as u64
222}
223
224/// Implements cow multi read.
225fn cow_multi_read(offset: usize) -> u8 {
226    unsafe { core::ptr::read_volatile((core::ptr::addr_of!(COW_MULTI) as *const u8).add(offset)) }
227}
228
229/// Implements cow multi write.
230fn cow_multi_write(offset: usize, value: u8) {
231    unsafe {
232        core::ptr::write_volatile(
233            (core::ptr::addr_of_mut!(COW_MULTI) as *mut u8).add(offset),
234            value,
235        );
236    }
237}
238
239/// Implements log cow multi page snapshot.
240fn log_cow_multi_page_snapshot(prefix: &str, page: usize) {
241    let base = page * 4096;
242    let a = cow_multi_read(base);
243    let b = cow_multi_read(base + 17);
244    let c = cow_multi_read(base + 4095);
245    log(prefix);
246    log(" page=");
247    log_u64(page as u64);
248    log(" first=");
249    log_hex_u64(a as u64);
250    log(" mid=");
251    log_hex_u64(b as u64);
252    log(" last=");
253    log_hex_u64(c as u64);
254    log_nl();
255}
256
257/// Implements exit process.
258fn exit_process(code: usize) -> ! {
259    call::exit(code)
260}
261
262/// Implements stack top for.
263fn stack_top_for(buf: *mut u8, len: usize) -> usize {
264    (buf as usize + len) & !0xFusize
265}
266
267/// Implements userspace thread entry.
268extern "C" fn userspace_thread_entry(arg0: usize) -> ! {
269    let case = unsafe { &*(arg0 as *const ThreadCase) };
270
271    let tid = call::gettid().unwrap_or(0);
272    if case.slot == 0 {
273        THREAD_TID_SLOT_0.store(tid, Ordering::SeqCst);
274    } else {
275        THREAD_TID_SLOT_1.store(tid, Ordering::SeqCst);
276    }
277    THREAD_STARTED.fetch_add(1, Ordering::SeqCst);
278
279    for _ in 0..case.spin_loops {
280        let _ = call::sched_yield();
281    }
282
283    call::thread_exit(case.expected_exit)
284}
285
286#[panic_handler]
287/// Implements panic.
288fn panic(_info: &PanicInfo) -> ! {
289    log_err("[init-test] PANIC detected, exiting with code 222\n");
290    exit_process(222)
291}
292
293#[no_mangle]
294/// Implements start.
295pub extern "C" fn _start() -> ! {
296    log_nl();
297    log_sep_eq();
298    log("[init-test] Strat9 first userspace init/test binary starting\n");
299    log("[init-test] Goal: maximal verbosity for PID/FORK/WAIT/COW debugging\n");
300    log_sep_eq();
301
302    log_section("STEP 1/11: reading identifiers via high-level wrappers");
303    let pid = log_result("getpid()", call::getpid()).unwrap_or(0);
304    let ppid = log_result("getppid()", call::getppid()).unwrap_or(0);
305    let tid = log_result("gettid()", call::gettid()).unwrap_or(0);
306    log("[init-test] summary ids: pid=");
307    log_u64(pid as u64);
308    log(" ppid=");
309    log_u64(ppid as u64);
310    log(" tid=");
311    log_u64(tid as u64);
312    log_nl();
313
314    log_section("STEP 2/11: reading identifiers via raw syscalls for cross-check");
315    log_raw_ret("SYS_GETPID", unsafe {
316        raw_syscall(number::SYS_GETPID, 0, 0, 0)
317    });
318    log_raw_ret("SYS_GETPPID", unsafe {
319        raw_syscall(number::SYS_GETPPID, 0, 0, 0)
320    });
321    log_raw_ret("SYS_GETTID", unsafe {
322        raw_syscall(number::SYS_GETTID, 0, 0, 0)
323    });
324
325    log_section("STEP 3/11: waitpid(-1, WNOHANG) before any fork (expect no child)");
326    let mut status_nochild: i32 = -9999;
327    log_result(
328        "waitpid(-1, &status, WNOHANG)",
329        call::waitpid(-1, Some(&mut status_nochild), call::WNOHANG),
330    );
331    log("[init-test] status buffer after nochild waitpid = ");
332    log_i64(status_nochild as i64);
333    log_nl();
334    let raw_nochild = unsafe {
335        raw_syscall(
336            number::SYS_PROC_WAITPID,
337            (-1isize) as usize,
338            (&mut status_nochild as *mut i32) as usize,
339            call::WNOHANG,
340        )
341    };
342    log_raw_ret("SYS_PROC_WAITPID(-1, WNOHANG)", raw_nochild);
343
344    log_section("STEP 4/11: forking first child (child should exit 42)");
345    let fork_ret = call::fork();
346    let child_pid = match fork_ret {
347        Ok(v) => v,
348        Err(e) => {
349            log("[init-test] fork failed errno=");
350            log_u64(e.to_errno() as u64);
351            log_nl();
352            exit_process(10);
353        }
354    };
355
356    if child_pid == 0 {
357        log("[init-test:child1] entered child branch\n");
358        log_result("[child1] getpid()", call::getpid());
359        log_result("[child1] getppid()", call::getppid());
360        log_result("[child1] gettid()", call::gettid());
361        log("[init-test:child1] doing sched_yield x2 to exercise scheduler\n");
362        let _ = call::sched_yield();
363        let _ = call::sched_yield();
364        log("[init-test:child1] exiting with code 42\n");
365        exit_process(42);
366    }
367
368    log("[init-test:parent] fork returned child_pid=");
369    log_u64(child_pid as u64);
370    log_nl();
371
372    log_section("STEP 5/11: parent waits child1 (poll WNOHANG then blocking wait)");
373    let mut child1_status: i32 = -1234;
374    for i in 0..5usize {
375        log("[init-test:parent] poll iteration ");
376        log_u64(i as u64);
377        log(": ");
378        match call::waitpid(child_pid as isize, Some(&mut child1_status), call::WNOHANG) {
379            Ok(0) => {
380                log("no exit yet\n");
381                let _ = call::sched_yield();
382            }
383            Ok(pid_done) => {
384                log("reaped immediately pid=");
385                log_u64(pid_done as u64);
386                log_nl();
387                decode_wait_status(child1_status);
388                break;
389            }
390            Err(e) => {
391                log("poll waitpid error errno=");
392                log_u64(e.to_errno() as u64);
393                log_nl();
394                break;
395            }
396        }
397    }
398    let waited = call::waitpid_blocking(child_pid as isize, &mut child1_status);
399    if let Some(done) = log_result("waitpid(child1, blocking)", waited) {
400        log("[init-test:parent] blocking wait returned pid=");
401        log_u64(done as u64);
402        log_nl();
403    }
404    decode_wait_status(child1_status);
405
406    log_section("STEP 6/11: process group/session syscalls (diagnostic only)");
407    let _ = log_result("getpgrp()", call::getpgrp());
408    let _ = log_result("getpgid(0)", call::getpgid(0));
409    let _ = log_result("setpgid(0,0)", call::setpgid(0, 0));
410    let _ = log_result("getsid(0)", call::getsid(0));
411    let _ = log_result("setsid()", call::setsid());
412    let _ = log_result("getsid(0) after setsid", call::getsid(0));
413
414    log_section("STEP 7/12: userspace thread_create/thread_join validation");
415    THREAD_STARTED.store(0, Ordering::SeqCst);
416    THREAD_TID_SLOT_0.store(0, Ordering::SeqCst);
417    THREAD_TID_SLOT_1.store(0, Ordering::SeqCst);
418
419    let thread_entry_ptr = userspace_thread_entry as *const () as usize;
420    let invalid_create = call::thread_create(thread_entry_ptr, 0x12345, 0, 0);
421    log_result("thread_create(invalid stack align)", invalid_create);
422    if !matches!(invalid_create, Err(Error::InvalidArgument)) {
423        log("[init-test:thread] ERROR: expected EINVAL for misaligned stack\n");
424        exit_process(19);
425    }
426
427    let stack_a_top = stack_top_for(
428        core::ptr::addr_of_mut!(THREAD_STACK_A) as *mut u8,
429        core::mem::size_of::<[u8; 4096 * 2]>(),
430    );
431    let stack_b_top = stack_top_for(
432        core::ptr::addr_of_mut!(THREAD_STACK_B) as *mut u8,
433        core::mem::size_of::<[u8; 4096 * 2]>(),
434    );
435
436    let arg_a = core::ptr::addr_of!(THREAD_CASE_A) as usize;
437    let arg_b = core::ptr::addr_of!(THREAD_CASE_B) as usize;
438
439    let tid_a = if let Some(tid_created) = log_result(
440        "thread_create(thread A)",
441        call::thread_create(thread_entry_ptr, stack_a_top, arg_a, 0),
442    ) {
443        tid_created
444    } else {
445        exit_process(20);
446    };
447
448    let tid_b = if let Some(tid_created) = log_result(
449        "thread_create(thread B)",
450        call::thread_create(thread_entry_ptr, stack_b_top, arg_b, 0),
451    ) {
452        tid_created
453    } else {
454        exit_process(21);
455    };
456
457    let mut join_status_a: i32 = -1;
458    let joined_a = if let Some(joined_tid) = log_result(
459        "thread_join(thread A)",
460        call::thread_join(tid_a, Some(&mut join_status_a)),
461    ) {
462        joined_tid
463    } else {
464        exit_process(22);
465    };
466    if joined_a != tid_a {
467        log("[init-test:thread] ERROR: joined tid mismatch for thread A\n");
468        exit_process(23);
469    }
470    log("[init-test:thread] join status for A=");
471    log_i64(join_status_a as i64);
472    log_nl();
473    if join_status_a != THREAD_CASE_A.expected_exit {
474        log("[init-test:thread] ERROR: unexpected join status for thread A\n");
475        exit_process(24);
476    }
477
478    let mut join_status_b: i32 = -1;
479    let joined_b = if let Some(joined_tid) = log_result(
480        "thread_join(thread B)",
481        call::thread_join(tid_b, Some(&mut join_status_b)),
482    ) {
483        joined_tid
484    } else {
485        exit_process(25);
486    };
487    if joined_b != tid_b {
488        log("[init-test:thread] ERROR: joined tid mismatch for thread B\n");
489        exit_process(26);
490    }
491    log("[init-test:thread] join status for B=");
492    log_i64(join_status_b as i64);
493    log_nl();
494    if join_status_b != THREAD_CASE_B.expected_exit {
495        log("[init-test:thread] ERROR: unexpected join status for thread B\n");
496        exit_process(27);
497    }
498
499    let started_count = THREAD_STARTED.load(Ordering::SeqCst);
500    log("[init-test:thread] started_count=");
501    log_u64(started_count as u64);
502    log(" slot0_tid=");
503    log_u64(THREAD_TID_SLOT_0.load(Ordering::SeqCst) as u64);
504    log(" slot1_tid=");
505    log_u64(THREAD_TID_SLOT_1.load(Ordering::SeqCst) as u64);
506    log_nl();
507    if started_count != 2
508        || THREAD_TID_SLOT_0.load(Ordering::SeqCst) == 0
509        || THREAD_TID_SLOT_1.load(Ordering::SeqCst) == 0
510    {
511        log("[init-test:thread] ERROR: not all thread start markers are set\n");
512        exit_process(28);
513    }
514
515    let self_tid = call::gettid().unwrap_or(0);
516    let join_self = call::thread_join(self_tid, None);
517    log_result("thread_join(self)", join_self);
518    if !matches!(join_self, Err(Error::InvalidArgument)) {
519        log("[init-test:thread] ERROR: expected EINVAL for join(self)\n");
520        exit_process(29);
521    }
522
523    let join_missing = call::thread_join(u32::MAX as usize, None);
524    log_result("thread_join(nonexistent tid)", join_missing);
525    if !matches!(join_missing, Err(Error::NotFound)) {
526        log("[init-test:thread] ERROR: expected ENOENT for missing tid\n");
527        exit_process(30);
528    }
529
530    let join_twice = call::thread_join(tid_a, None);
531    log_result("thread_join(already joined thread A)", join_twice);
532    if !matches!(join_twice, Err(Error::NotFound)) {
533        log("[init-test:thread] ERROR: expected ENOENT for second join\n");
534        exit_process(31);
535    }
536    log("[init-test:thread] SUCCESS: thread lifecycle + error paths validated\n");
537
538    log_section("STEP 8/12: second fork/wait any-child path (child exits 7)");
539    let second = call::fork();
540    let child2_pid = match second {
541        Ok(v) => v,
542        Err(e) => {
543            log("[init-test] second fork failed errno=");
544            log_u64(e.to_errno() as u64);
545            log_nl();
546            exit_process(11);
547        }
548    };
549    if child2_pid == 0 {
550        log("[init-test:child2] exiting with code 7 immediately\n");
551        exit_process(7);
552    }
553    log("[init-test:parent] second child pid=");
554    log_u64(child2_pid as u64);
555    log_nl();
556    let mut child2_status: i32 = 0;
557    let _ = log_result(
558        "waitpid(-1, blocking)",
559        call::waitpid_blocking(-1, &mut child2_status),
560    );
561    decode_wait_status(child2_status);
562
563    log_section("STEP 9/12: targeted CoW test (single 64-bit sentinel)");
564    let cow_initial = 0x1122_3344_5566_7788u64;
565    let cow_child_value = 0xdead_beef_cafe_babeu64;
566    let cow_parent_value = 0xa1a2_a3a4_a5a6_a7a8u64;
567    cow_write(cow_initial);
568    log("[init-test:cow] sentinel address=");
569    log_hex_u64(cow_addr());
570    log(" initial=");
571    log_hex_u64(cow_read());
572    log_nl();
573    log("[init-test:cow] expected child write value=");
574    log_hex_u64(cow_child_value);
575    log(" expected parent write value=");
576    log_hex_u64(cow_parent_value);
577    log_nl();
578
579    let cow_fork = call::fork();
580    let cow_child_pid = match cow_fork {
581        Ok(v) => v,
582        Err(e) => {
583            log("[init-test:cow] fork failed errno=");
584            log_u64(e.to_errno() as u64);
585            log_nl();
586            exit_process(12);
587        }
588    };
589
590    if cow_child_pid == 0 {
591        log("[init-test:cow:child] entered child branch\n");
592        let child_seen_before = cow_read();
593        log("[init-test:cow:child] sentinel before write=");
594        log_hex_u64(child_seen_before);
595        log_nl();
596        if child_seen_before != cow_initial {
597            log("[init-test:cow:child] ERROR: unexpected initial sentinel in child\n");
598            exit_process(90);
599        }
600
601        log("[init-test:cow:child] writing sentinel to child-specific value\n");
602        cow_write(cow_child_value);
603        let child_seen_after = cow_read();
604        log("[init-test:cow:child] sentinel after write=");
605        log_hex_u64(child_seen_after);
606        log_nl();
607        if child_seen_after != cow_child_value {
608            log("[init-test:cow:child] ERROR: child write did not stick\n");
609            exit_process(91);
610        }
611
612        let _ = call::sched_yield();
613        let _ = call::sched_yield();
614        log("[init-test:cow:child] CoW child path done, exiting code 77\n");
615        exit_process(77);
616    }
617
618    log("[init-test:cow:parent] child pid=");
619    log_u64(cow_child_pid as u64);
620    log(" parent sees sentinel pre-wait=");
621    log_hex_u64(cow_read());
622    log_nl();
623
624    let mut cow_status: i32 = -1;
625    let waited_cow = call::waitpid_blocking(cow_child_pid as isize, &mut cow_status);
626    let waited_cow_pid =
627        if let Some(done) = log_result("[cow-parent] waitpid(cow-child, blocking)", waited_cow) {
628            done
629        } else {
630            exit_process(13);
631        };
632    log("[init-test:cow:parent] wait returned pid=");
633    log_u64(waited_cow_pid as u64);
634    log_nl();
635    decode_wait_status(cow_status);
636
637    let cow_after_child_exit = cow_read();
638    log("[init-test:cow:parent] sentinel post-child-exit=");
639    log_hex_u64(cow_after_child_exit);
640    log_nl();
641    if cow_after_child_exit != cow_initial {
642        log("[init-test:cow:parent] ERROR: parent observed child write (CoW broken)\n");
643        exit_process(14);
644    }
645
646    log("[init-test:cow:parent] writing parent-specific value\n");
647    cow_write(cow_parent_value);
648    let cow_after_parent_write = cow_read();
649    log("[init-test:cow:parent] sentinel after parent write=");
650    log_hex_u64(cow_after_parent_write);
651    log_nl();
652    if cow_after_parent_write != cow_parent_value {
653        log("[init-test:cow:parent] ERROR: parent write did not stick\n");
654        exit_process(15);
655    }
656    log("[init-test:cow] SUCCESS: CoW isolation validated for parent/child writes\n");
657
658    log_section("STEP 10/12: targeted CoW multi-page test (4 pages)");
659    log("[init-test:cow4k] buffer address=");
660    log_hex_u64(cow_multi_addr());
661    log(" size=");
662    log_u64((4096 * 4) as u64);
663    log_nl();
664
665    for page in 0..4usize {
666        let base = page * 4096;
667        cow_multi_write(base, (0x10 + page as u8) as u8);
668        cow_multi_write(base + 17, (0x40 + page as u8) as u8);
669        cow_multi_write(base + 4095, (0x70 + page as u8) as u8);
670        log_cow_multi_page_snapshot("[init-test:cow4k:parent:init]", page);
671    }
672
673    let cow_multi_fork = call::fork();
674    let cow_multi_child_pid = match cow_multi_fork {
675        Ok(v) => v,
676        Err(e) => {
677            log("[init-test:cow4k] fork failed errno=");
678            log_u64(e.to_errno() as u64);
679            log_nl();
680            exit_process(16);
681        }
682    };
683
684    if cow_multi_child_pid == 0 {
685        log_sep_star();
686        log("[init-test:cow4k:child] validating inherited page fingerprints\n");
687        for page in 0..4usize {
688            log_cow_multi_page_snapshot("[init-test:cow4k:child:before]", page);
689        }
690        log("[init-test:cow4k:child] mutating all 4 pages with child-only fingerprints\n");
691        for page in 0..4usize {
692            let base = page * 4096;
693            cow_multi_write(base, (0x91 + page as u8) as u8);
694            cow_multi_write(base + 17, (0xA1 + page as u8) as u8);
695            cow_multi_write(base + 4095, (0xB1 + page as u8) as u8);
696            log_cow_multi_page_snapshot("[init-test:cow4k:child:after]", page);
697        }
698        log("[init-test:cow4k:child] exiting code 88\n");
699        exit_process(88);
700    }
701
702    log("[init-test:cow4k:parent] child pid=");
703    log_u64(cow_multi_child_pid as u64);
704    log_nl();
705    let mut cow_multi_status: i32 = -1;
706    let mut waited_multi_ok = false;
707    for attempt in 0..2000usize {
708        let waited_multi = call::waitpid(
709            cow_multi_child_pid as isize,
710            Some(&mut cow_multi_status),
711            call::WNOHANG,
712        );
713        match waited_multi {
714            Ok(0) => {
715                if attempt % 100 == 0 {
716                    log("[init-test:cow4k:parent] waitpid WNOHANG: child still running, attempt=");
717                    log_u64(attempt as u64);
718                    log_nl();
719                }
720                let _ = call::sched_yield();
721            }
722            Ok(pid_done) => {
723                log_result("[cow4k-parent] waitpid(cow4k-child, WNOHANG)", Ok(pid_done));
724                waited_multi_ok = true;
725                break;
726            }
727            Err(Error::Interrupted) => {
728                log("[init-test:cow4k:parent] waitpid interrupted (EINTR), retry attempt=");
729                log_u64((attempt + 1) as u64);
730                log_nl();
731                let _ = call::sched_yield();
732            }
733            Err(e) => {
734                log_result("[cow4k-parent] waitpid(cow4k-child, WNOHANG)", Err(e));
735                break;
736            }
737        }
738    }
739    if !waited_multi_ok {
740        log("[init-test:cow4k:parent] ERROR: waitpid timeout after retries\n");
741        exit_process(17);
742    }
743    decode_wait_status(cow_multi_status);
744
745    log("[init-test:cow4k:parent] verifying parent view unchanged after child writes\n");
746    for page in 0..4usize {
747        let base = page * 4096;
748        let v0 = cow_multi_read(base);
749        let v1 = cow_multi_read(base + 17);
750        let v2 = cow_multi_read(base + 4095);
751        if v0 != (0x10 + page as u8) as u8
752            || v1 != (0x40 + page as u8) as u8
753            || v2 != (0x70 + page as u8) as u8
754        {
755            log("[init-test:cow4k:parent] ERROR: parent observed child mutation page=");
756            log_u64(page as u64);
757            log_nl();
758            exit_process(18);
759        }
760        log_cow_multi_page_snapshot("[init-test:cow4k:parent:verified]", page);
761    }
762
763    log("[init-test:cow4k:parent] now writing parent-only fingerprints\n");
764    for page in 0..4usize {
765        let base = page * 4096;
766        cow_multi_write(base, (0x21 + page as u8) as u8);
767        cow_multi_write(base + 17, (0x31 + page as u8) as u8);
768        cow_multi_write(base + 4095, (0x41 + page as u8) as u8);
769        log_cow_multi_page_snapshot("[init-test:cow4k:parent:after]", page);
770    }
771    log("[init-test:cow4k] SUCCESS: 4-page CoW isolation validated\n");
772
773    log_section("STEP 11/12: raw syscall sanity check for waitpid on no child again");
774    let mut st: i32 = 0;
775    let raw = unsafe {
776        raw_syscall(
777            number::SYS_PROC_WAITPID,
778            (-1isize) as usize,
779            (&mut st as *mut i32) as usize,
780            0,
781        )
782    };
783    log_raw_ret("SYS_PROC_WAITPID(-1, blocking)", raw);
784    log("[init-test] status buffer=");
785    log_i64(st as i64);
786    log_nl();
787
788    log_section("STEP 12/12: completed. exiting init-test with code 0");
789    log_sep_eq();
790    exit_process(0)
791}