1#![no_std]
2#![no_main]
3
4use core::panic::PanicInfo;
5use strat9_syscall::{
6 call, data, error::Error, number, number::*, syscall1, syscall2, syscall3, syscall4, syscall6,
7};
8
9const PAGE_SIZE: usize = 4096;
10
11const PROT_READ: usize = 1;
12const PROT_WRITE: usize = 2;
13const MAP_PRIVATE: usize = 1 << 1;
14const MAP_ANON: usize = 1 << 5;
15const MREMAP_MAYMOVE: usize = 1 << 0;
16const SEEK_SET: usize = 0;
17
18const O_READ: usize = 1 << 0;
19const O_WRITE: usize = 1 << 1;
20const O_CREATE: usize = 1 << 2;
21const O_TRUNC: usize = 1 << 3;
22const O_DIRECTORY: usize = 1 << 5;
23
24struct Ctx {
25 pass: u64,
26 fail: u64,
27}
28
29fn write_fd(fd: usize, msg: &str) {
31 let _ = call::write(fd, msg.as_bytes());
32}
33
34fn log(msg: &str) {
36 write_fd(1, msg);
37}
38
39fn log_err(msg: &str) {
41 write_fd(2, msg);
42}
43
44fn log_u64(mut value: u64) {
46 let mut buf = [0u8; 21];
47 if value == 0 {
48 log("0");
49 return;
50 }
51 let mut i = buf.len();
52 while value > 0 {
53 i -= 1;
54 buf[i] = b'0' + (value % 10) as u8;
55 value /= 10;
56 }
57 let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
58 log(s);
59}
60
61fn log_hex_u64(mut value: u64) {
63 let mut buf = [0u8; 16];
64 for i in (0..16).rev() {
65 let nibble = (value & 0xF) as u8;
66 buf[i] = if nibble < 10 {
67 b'0' + nibble
68 } else {
69 b'a' + (nibble - 10)
70 };
71 value >>= 4;
72 }
73 log("0x");
74 let s = unsafe { core::str::from_utf8_unchecked(&buf) };
75 log(s);
76}
77
78fn section(title: &str) {
80 log("\n============================================================\n");
81 log("[test_syscalls] ");
82 log(title);
83 log("\n============================================================\n");
84}
85
86fn ok(ctx: &mut Ctx, label: &str, value: usize) {
88 ctx.pass += 1;
89 log("[OK] ");
90 log(label);
91 log(" -> ");
92 log_u64(value as u64);
93 log(" (");
94 log_hex_u64(value as u64);
95 log(")\n");
96}
97
98fn fail(ctx: &mut Ctx, label: &str, err: Error) {
100 ctx.fail += 1;
101 log_err("[FAIL] ");
102 log_err(label);
103 log_err(" -> ");
104 log_err(err.name());
105 log_err("\n");
106}
107
108fn check_ok(ctx: &mut Ctx, label: &str, res: core::result::Result<usize, Error>) -> Option<usize> {
110 match res {
111 Ok(v) => {
112 ok(ctx, label, v);
113 Some(v)
114 }
115 Err(e) => {
116 fail(ctx, label, e);
117 None
118 }
119 }
120}
121
122fn check_expect_one_of(
124 ctx: &mut Ctx,
125 label: &str,
126 res: core::result::Result<usize, Error>,
127 e1: Error,
128 e2: Error,
129) {
130 match res {
131 Ok(v) => {
132 if e1 == Error::Unknown(0) || e2 == Error::Unknown(0) {
133 ok(ctx, label, v);
134 } else {
135 ctx.fail += 1;
136 log_err("[FAIL] ");
137 log_err(label);
138 log_err(" -> expected error but got OK=");
139 log_u64(v as u64);
140 log_err("\n");
141 }
142 }
143 Err(e) => {
144 if e == e1 || e == e2 {
145 ok(ctx, label, 0);
146 } else {
147 fail(ctx, label, e);
148 }
149 }
150 }
151}
152
153fn check_expect_err(
155 ctx: &mut Ctx,
156 label: &str,
157 res: core::result::Result<usize, Error>,
158 expected: Error,
159) {
160 match res {
161 Ok(v) => {
162 ctx.fail += 1;
163 log_err("[FAIL] ");
164 log_err(label);
165 log_err(" -> expected ");
166 log_err(expected.name());
167 log_err(" got OK=");
168 log_u64(v as u64);
169 log_err("\n");
170 }
171 Err(e) => {
172 if e == expected {
173 ok(ctx, label, 0);
174 } else {
175 fail(ctx, label, e);
176 }
177 }
178 }
179}
180
181fn test_process_and_ids(ctx: &mut Ctx) {
183 section("Process IDs / Session / Group / Credentials");
184
185 let _pid = check_ok(ctx, "getpid()", call::getpid()).unwrap_or(0);
186 let _ = check_ok(ctx, "getppid()", call::getppid());
187 let _ = check_ok(ctx, "gettid()", call::gettid());
188 let _ = check_ok(ctx, "getpgid(0)", call::getpgid(0));
189 let _ = check_ok(ctx, "getsid(0)", call::getsid(0));
190 let _ = check_ok(ctx, "setpgid(0,0)", call::setpgid(0, 0));
191 check_expect_one_of(
192 ctx,
193 "setsid()",
194 call::setsid(),
195 Error::PermissionDenied,
196 Error::InvalidArgument,
197 );
198
199 let _ = check_ok(ctx, "raw SYS_GETUID", unsafe { syscall1(SYS_GETUID, 0) });
200 let _ = check_ok(ctx, "raw SYS_GETEUID", unsafe { syscall1(SYS_GETEUID, 0) });
201 let _ = check_ok(ctx, "raw SYS_GETGID", unsafe { syscall1(SYS_GETGID, 0) });
202 let _ = check_ok(ctx, "raw SYS_GETEGID", unsafe { syscall1(SYS_GETEGID, 0) });
203 let cur_uid = unsafe { syscall1(SYS_GETUID, 0) }.unwrap_or(0);
204 let cur_gid = unsafe { syscall1(SYS_GETGID, 0) }.unwrap_or(0);
205 let _ = check_ok(ctx, "raw SYS_SETUID(current uid)", unsafe {
206 syscall1(SYS_SETUID, cur_uid)
207 });
208 let _ = check_ok(ctx, "raw SYS_SETGID(current gid)", unsafe {
209 syscall1(SYS_SETGID, cur_gid)
210 });
211}
212
213fn test_memory(ctx: &mut Ctx) {
215 section("Memory: brk / mmap / mprotect / mremap / munmap");
216
217 let base = check_ok(ctx, "brk(0)", call::brk(0)).unwrap_or(0);
218 let grow = base + PAGE_SIZE * 2;
219 let _ = check_ok(ctx, "brk(grow)", call::brk(grow));
220 let _ = check_ok(ctx, "brk(shrink)", call::brk(base));
221
222 let mapped = check_ok(ctx, "SYS_MMAP anon private RW 2 pages", unsafe {
223 syscall6(
224 number::SYS_MMAP,
225 0,
226 PAGE_SIZE * 2,
227 PROT_READ | PROT_WRITE,
228 MAP_PRIVATE | MAP_ANON,
229 0,
230 0,
231 )
232 })
233 .unwrap_or(0);
234
235 if mapped != 0 {
236 let ptr = mapped as *mut u8;
237 unsafe {
238 core::ptr::write_volatile(ptr, 0xAA);
239 core::ptr::write_volatile(ptr.add(PAGE_SIZE), 0xBB);
240 }
241 }
242
243 let _ = check_ok(ctx, "SYS_MPROTECT RO", unsafe {
244 syscall3(number::SYS_MPROTECT, mapped, PAGE_SIZE * 2, PROT_READ)
245 });
246 let _ = check_ok(ctx, "SYS_MPROTECT RW", unsafe {
247 syscall3(
248 number::SYS_MPROTECT,
249 mapped,
250 PAGE_SIZE * 2,
251 PROT_READ | PROT_WRITE,
252 )
253 });
254 let remapped = check_ok(ctx, "SYS_MREMAP grow to 3 pages (MAYMOVE)", unsafe {
255 syscall4(
256 number::SYS_MREMAP,
257 mapped,
258 PAGE_SIZE * 2,
259 PAGE_SIZE * 3,
260 MREMAP_MAYMOVE,
261 )
262 })
263 .unwrap_or(mapped);
264 let _ = check_ok(ctx, "SYS_MREMAP shrink back to 2 pages", unsafe {
265 syscall4(
266 number::SYS_MREMAP,
267 remapped,
268 PAGE_SIZE * 3,
269 PAGE_SIZE * 2,
270 MREMAP_MAYMOVE,
271 )
272 });
273 let _ = check_ok(ctx, "SYS_MUNMAP final", unsafe {
274 syscall2(number::SYS_MUNMAP, remapped, PAGE_SIZE * 2)
275 });
276}
277
278fn test_fs(ctx: &mut Ctx) {
280 section("Filesystem and CWD syscalls");
281
282 const DIR: &str = "/tmp/iso_syscalls_suite";
283 const FILE: &str = "/tmp/iso_syscalls_suite/data.txt";
284
285 let _ = unsafe { syscall2(SYS_UNLINK, FILE.as_ptr() as usize, FILE.len()) };
286 let _ = unsafe { syscall2(SYS_RMDIR, DIR.as_ptr() as usize, DIR.len()) };
287
288 let _ = check_ok(ctx, "SYS_MKDIR /tmp/iso_syscalls_suite", unsafe {
289 syscall3(SYS_MKDIR, DIR.as_ptr() as usize, DIR.len(), 0o755)
290 });
291
292 let file_fd = check_ok(ctx, "SYS_OPEN create RW file", unsafe {
293 syscall3(
294 number::SYS_OPEN,
295 FILE.as_ptr() as usize,
296 FILE.len(),
297 O_READ | O_WRITE | O_CREATE | O_TRUNC,
298 )
299 })
300 .unwrap_or(usize::MAX);
301
302 if file_fd != usize::MAX {
303 let msg = b"syscall-iso-test\n";
304 let _ = check_ok(ctx, "SYS_WRITE file", unsafe {
305 syscall3(number::SYS_WRITE, file_fd, msg.as_ptr() as usize, msg.len())
306 });
307 let _ = check_ok(ctx, "SYS_LSEEK file -> 0", unsafe {
308 syscall3(number::SYS_LSEEK, file_fd, 0, SEEK_SET)
309 });
310 let mut buf = [0u8; 32];
311 let _ = check_ok(ctx, "SYS_READ file", unsafe {
312 syscall3(
313 number::SYS_READ,
314 file_fd,
315 buf.as_mut_ptr() as usize,
316 msg.len(),
317 )
318 });
319 let mut st = data::FileStat::zeroed();
320 let _ = check_ok(ctx, "SYS_FSTAT file", unsafe {
321 syscall2(
322 number::SYS_FSTAT,
323 file_fd,
324 &mut st as *mut data::FileStat as usize,
325 )
326 });
327 check_expect_err(
328 ctx,
329 "SYS_FCHMOD expected ENOSYS",
330 unsafe { syscall2(SYS_FCHMOD, file_fd, 0o644) },
331 Error::NotImplemented,
332 );
333 check_expect_err(
334 ctx,
335 "SYS_FTRUNCATE expected ENOSYS",
336 unsafe { syscall2(SYS_FTRUNCATE, file_fd, 4) },
337 Error::NotImplemented,
338 );
339 }
340
341 let dir_fd = check_ok(ctx, "SYS_OPEN dir O_DIRECTORY", unsafe {
342 syscall3(
343 number::SYS_OPEN,
344 DIR.as_ptr() as usize,
345 DIR.len(),
346 O_READ | O_DIRECTORY,
347 )
348 })
349 .unwrap_or(usize::MAX);
350
351 let _ = check_ok(ctx, "SYS_CHDIR dir", unsafe {
352 syscall2(SYS_CHDIR, DIR.as_ptr() as usize, DIR.len())
353 });
354 let mut cwd = [0u8; 128];
355 let _ = check_ok(ctx, "SYS_GETCWD", unsafe {
356 syscall2(SYS_GETCWD, cwd.as_mut_ptr() as usize, cwd.len())
357 });
358 if dir_fd != usize::MAX {
359 let _ = check_ok(ctx, "SYS_FCHDIR back", unsafe {
360 syscall1(SYS_FCHDIR, dir_fd)
361 });
362 }
363
364 let _ = check_ok(ctx, "SYS_UMASK set 022", unsafe {
365 syscall1(SYS_UMASK, 0o022)
366 });
367 check_expect_err(
368 ctx,
369 "SYS_CHMOD expected ENOSYS",
370 unsafe { syscall3(SYS_CHMOD, FILE.as_ptr() as usize, FILE.len(), 0o644) },
371 Error::NotImplemented,
372 );
373 check_expect_err(
374 ctx,
375 "SYS_RENAME expected ENOSYS",
376 unsafe {
377 syscall4(
378 SYS_RENAME,
379 FILE.as_ptr() as usize,
380 FILE.len(),
381 DIR.as_ptr() as usize,
382 DIR.len(),
383 )
384 },
385 Error::NotImplemented,
386 );
387 check_expect_err(
388 ctx,
389 "SYS_LINK expected ENOSYS",
390 unsafe {
391 syscall4(
392 SYS_LINK,
393 FILE.as_ptr() as usize,
394 FILE.len(),
395 DIR.as_ptr() as usize,
396 DIR.len(),
397 )
398 },
399 Error::NotImplemented,
400 );
401 check_expect_err(
402 ctx,
403 "SYS_SYMLINK expected ENOSYS",
404 unsafe {
405 syscall4(
406 SYS_SYMLINK,
407 FILE.as_ptr() as usize,
408 FILE.len(),
409 DIR.as_ptr() as usize,
410 DIR.len(),
411 )
412 },
413 Error::NotImplemented,
414 );
415 let mut rl = [0u8; 64];
416 check_expect_err(
417 ctx,
418 "SYS_READLINK expected ENOSYS",
419 unsafe {
420 syscall4(
421 SYS_READLINK,
422 FILE.as_ptr() as usize,
423 FILE.len(),
424 rl.as_mut_ptr() as usize,
425 rl.len(),
426 )
427 },
428 Error::NotImplemented,
429 );
430
431 if file_fd != usize::MAX {
432 let _ = check_ok(ctx, "SYS_CLOSE file", unsafe {
433 syscall1(number::SYS_CLOSE, file_fd)
434 });
435 }
436 if dir_fd != usize::MAX {
437 let _ = check_ok(ctx, "SYS_CLOSE dir", unsafe {
438 syscall1(number::SYS_CLOSE, dir_fd)
439 });
440 }
441 let _ = check_ok(ctx, "SYS_UNLINK file", unsafe {
442 syscall2(SYS_UNLINK, FILE.as_ptr() as usize, FILE.len())
443 });
444 let _ = check_ok(ctx, "SYS_RMDIR dir", unsafe {
445 syscall2(SYS_RMDIR, DIR.as_ptr() as usize, DIR.len())
446 });
447}
448
449fn test_handles(ctx: &mut Ctx) {
451 section("Handle syscalls via semaphore handle");
452
453 let sem = check_ok(ctx, "SYS_SEM_CREATE initial=1", unsafe {
454 syscall1(number::SYS_SEM_CREATE, 1)
455 })
456 .unwrap_or(usize::MAX);
457 if sem == usize::MAX {
458 return;
459 }
460
461 let mut info = data::HandleInfo {
462 resource_type: 0,
463 permissions: 0,
464 resource: 0,
465 };
466 let _ = check_ok(ctx, "handle_info(sem)", call::handle_info(sem, &mut info));
467 log("[info] resource_type=");
468 log_u64(info.resource_type as u64);
469 log(" perms=");
470 log_hex_u64(info.permissions as u64);
471 log(" resource=");
472 log_hex_u64(info.resource);
473 log("\n");
474
475 let _ = check_ok(
476 ctx,
477 "handle_wait(sem immediate)",
478 call::handle_wait_timeout(sem, 0),
479 );
480 let _ = check_ok(ctx, "SYS_SEM_WAIT consume token", unsafe {
481 syscall1(number::SYS_SEM_WAIT, sem)
482 });
483 check_expect_err(
484 ctx,
485 "handle_wait_timeout(sem empty, 1ms) -> ETIMEDOUT",
486 call::handle_wait_timeout(sem, 1_000_000),
487 Error::TimedOut,
488 );
489 let _ = check_ok(ctx, "SYS_SEM_POST produce token", unsafe {
490 syscall1(number::SYS_SEM_POST, sem)
491 });
492 let _ = check_ok(
493 ctx,
494 "handle_wait(sem after post)",
495 call::handle_wait_timeout(sem, 1_000_000),
496 );
497
498 let self_pid = call::getpid().unwrap_or(0);
499 let dup = check_ok(
500 ctx,
501 "handle_grant(self_pid)",
502 call::handle_grant(sem, self_pid),
503 )
504 .unwrap_or(usize::MAX);
505 if dup != usize::MAX {
506 let _ = check_ok(ctx, "handle_revoke(dup)", call::handle_revoke(dup));
507 }
508 match unsafe { syscall1(number::SYS_SEM_CLOSE, sem) } {
509 Ok(v) => ok(ctx, "SYS_SEM_CLOSE(original)", v),
510 Err(Error::NotFound) | Err(Error::BadHandle) => {
511 ok(ctx, "SYS_SEM_CLOSE(original already cleaned)", 0)
512 }
513 Err(e) => fail(ctx, "SYS_SEM_CLOSE(original)", e),
514 }
515}
516
517#[panic_handler]
518fn panic(_info: &PanicInfo) -> ! {
520 log_err("[test_syscalls] PANIC\n");
521 call::exit(250)
522}
523
524#[no_mangle]
525pub extern "C" fn _start() -> ! {
527 let mut ctx = Ctx { pass: 0, fail: 0 };
528
529 section("Strat9 syscall ISO test suite (verbose + broad coverage)");
530 test_process_and_ids(&mut ctx);
531 test_memory(&mut ctx);
532 test_fs(&mut ctx);
533 test_handles(&mut ctx);
534
535 section("Summary");
536 log("[summary] pass=");
537 log_u64(ctx.pass);
538 log(" fail=");
539 log_u64(ctx.fail);
540 log("\n");
541
542 if ctx.fail == 0 {
543 call::exit(0);
544 } else {
545 call::exit(1);
546 }
547}