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
39fn write_fd(fd: usize, msg: &str) {
41 let _ = call::write(fd, msg.as_bytes());
42}
43
44fn log(msg: &str) {
46 write_fd(1, msg);
47}
48
49fn log_err(msg: &str) {
51 write_fd(2, msg);
52}
53
54fn log_nl() {
56 log("\n");
57}
58
59fn log_sep_star() {
61 log("************************************************************\n");
62}
63
64fn log_sep_eq() {
66 log("============================================================\n");
67}
68
69fn log_section(title: &str) {
71 log_sep_star();
72 log("[init-test] ");
73 log(title);
74 log_nl();
75 log_sep_eq();
76}
77
78fn 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
97fn 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
107fn 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
124fn 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
150fn 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
165unsafe 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
183fn 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
202fn cow_addr() -> u64 {
204 core::ptr::addr_of!(COW_SENTINEL) as u64
205}
206
207fn cow_read() -> u64 {
209 unsafe { core::ptr::read_volatile(core::ptr::addr_of!(COW_SENTINEL)) }
210}
211
212fn cow_write(value: u64) {
214 unsafe {
215 core::ptr::write_volatile(core::ptr::addr_of_mut!(COW_SENTINEL), value);
216 }
217}
218
219fn cow_multi_addr() -> u64 {
221 core::ptr::addr_of!(COW_MULTI) as u64
222}
223
224fn 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
229fn 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
239fn 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
257fn exit_process(code: usize) -> ! {
259 call::exit(code)
260}
261
262fn stack_top_for(buf: *mut u8, len: usize) -> usize {
264 (buf as usize + len) & !0xFusize
265}
266
267extern "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]
287fn panic(_info: &PanicInfo) -> ! {
289 log_err("[init-test] PANIC detected, exiting with code 222\n");
290 exit_process(222)
291}
292
293#[no_mangle]
294pub 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}