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 _pgid = check_ok(ctx, "getpgid(0)", call::getpgid(0)).unwrap_or(0);
189 let sid = check_ok(ctx, "getsid(0)", call::getsid(0)).unwrap_or(0);
190 if sid == pid {
191 check_expect_err(
192 ctx,
193 "setpgid(0,0) while session leader -> EPERM",
194 call::setpgid(0, 0),
195 Error::PermissionDenied,
196 );
197 } else {
198 let _ = check_ok(ctx, "setpgid(0,0)", call::setpgid(0, 0));
199 }
200 check_expect_one_of(
201 ctx,
202 "setsid()",
203 call::setsid(),
204 Error::PermissionDenied,
205 Error::InvalidArgument,
206 );
207
208 let _ = check_ok(ctx, "raw SYS_GETUID", unsafe { syscall1(SYS_GETUID, 0) });
209 let _ = check_ok(ctx, "raw SYS_GETEUID", unsafe { syscall1(SYS_GETEUID, 0) });
210 let _ = check_ok(ctx, "raw SYS_GETGID", unsafe { syscall1(SYS_GETGID, 0) });
211 let _ = check_ok(ctx, "raw SYS_GETEGID", unsafe { syscall1(SYS_GETEGID, 0) });
212 let cur_uid = unsafe { syscall1(SYS_GETUID, 0) }.unwrap_or(0);
213 let cur_gid = unsafe { syscall1(SYS_GETGID, 0) }.unwrap_or(0);
214 let _ = check_ok(ctx, "raw SYS_SETUID(current uid)", unsafe {
215 syscall1(SYS_SETUID, cur_uid)
216 });
217 let _ = check_ok(ctx, "raw SYS_SETGID(current gid)", unsafe {
218 syscall1(SYS_SETGID, cur_gid)
219 });
220}
221
222fn test_memory(ctx: &mut Ctx) {
224 section("Memory: brk / mmap / mprotect / mremap / munmap");
225
226 let base = check_ok(ctx, "brk(0)", call::brk(0)).unwrap_or(0);
227 let grow = base + PAGE_SIZE * 2;
228 let _ = check_ok(ctx, "brk(grow)", call::brk(grow));
229 let _ = check_ok(ctx, "brk(shrink)", call::brk(base));
230
231 let mapped = check_ok(ctx, "SYS_MMAP anon private RW 2 pages", unsafe {
232 syscall6(
233 number::SYS_MMAP,
234 0,
235 PAGE_SIZE * 2,
236 PROT_READ | PROT_WRITE,
237 MAP_PRIVATE | MAP_ANON,
238 0,
239 0,
240 )
241 })
242 .unwrap_or(0);
243
244 if mapped != 0 {
245 let ptr = mapped as *mut u8;
246 unsafe {
247 core::ptr::write_volatile(ptr, 0xAA);
248 core::ptr::write_volatile(ptr.add(PAGE_SIZE), 0xBB);
249 }
250 }
251
252 let _ = check_ok(ctx, "SYS_MPROTECT RO", unsafe {
253 syscall3(number::SYS_MPROTECT, mapped, PAGE_SIZE * 2, PROT_READ)
254 });
255 let _ = check_ok(ctx, "SYS_MPROTECT RW", unsafe {
256 syscall3(
257 number::SYS_MPROTECT,
258 mapped,
259 PAGE_SIZE * 2,
260 PROT_READ | PROT_WRITE,
261 )
262 });
263 let remapped = check_ok(ctx, "SYS_MREMAP grow to 3 pages (MAYMOVE)", unsafe {
264 syscall4(
265 number::SYS_MREMAP,
266 mapped,
267 PAGE_SIZE * 2,
268 PAGE_SIZE * 3,
269 MREMAP_MAYMOVE,
270 )
271 })
272 .unwrap_or(mapped);
273 let _ = check_ok(ctx, "SYS_MREMAP shrink back to 2 pages", unsafe {
274 syscall4(
275 number::SYS_MREMAP,
276 remapped,
277 PAGE_SIZE * 3,
278 PAGE_SIZE * 2,
279 MREMAP_MAYMOVE,
280 )
281 });
282 let _ = check_ok(ctx, "SYS_MUNMAP final", unsafe {
283 syscall2(number::SYS_MUNMAP, remapped, PAGE_SIZE * 2)
284 });
285}
286
287fn test_fs(ctx: &mut Ctx) {
289 section("Filesystem and CWD syscalls");
290
291 const DIR: &str = "/tmp/iso_syscalls_suite";
292 const FILE: &str = "/tmp/iso_syscalls_suite/data.txt";
293 const FILE_RENAMED: &str = "/tmp/iso_syscalls_suite/data_renamed.txt";
294 const FILE_LINK: &str = "/tmp/iso_syscalls_suite/data_link.txt";
295 const FILE_SYMLINK: &str = "/tmp/iso_syscalls_suite/data_symlink.txt";
296
297 let _ = unsafe { syscall2(SYS_UNLINK, FILE.as_ptr() as usize, FILE.len()) };
298 let _ = unsafe {
299 syscall2(
300 SYS_UNLINK,
301 FILE_RENAMED.as_ptr() as usize,
302 FILE_RENAMED.len(),
303 )
304 };
305 let _ = unsafe { syscall2(SYS_UNLINK, FILE_LINK.as_ptr() as usize, FILE_LINK.len()) };
306 let _ = unsafe {
307 syscall2(
308 SYS_UNLINK,
309 FILE_SYMLINK.as_ptr() as usize,
310 FILE_SYMLINK.len(),
311 )
312 };
313 let _ = unsafe { syscall2(SYS_RMDIR, DIR.as_ptr() as usize, DIR.len()) };
314
315 let _ = check_ok(ctx, "SYS_MKDIR /tmp/iso_syscalls_suite", unsafe {
316 syscall3(SYS_MKDIR, DIR.as_ptr() as usize, DIR.len(), 0o755)
317 });
318
319 let file_fd = check_ok(ctx, "SYS_OPEN create RW file", unsafe {
320 syscall3(
321 number::SYS_OPEN,
322 FILE.as_ptr() as usize,
323 FILE.len(),
324 O_READ | O_WRITE | O_CREATE | O_TRUNC,
325 )
326 })
327 .unwrap_or(usize::MAX);
328
329 if file_fd != usize::MAX {
330 let msg = b"syscall-iso-test\n";
331 let _ = check_ok(ctx, "SYS_WRITE file", unsafe {
332 syscall3(number::SYS_WRITE, file_fd, msg.as_ptr() as usize, msg.len())
333 });
334 let _ = check_ok(ctx, "SYS_LSEEK file -> 0", unsafe {
335 syscall3(number::SYS_LSEEK, file_fd, 0, SEEK_SET)
336 });
337 let mut buf = [0u8; 32];
338 let _ = check_ok(ctx, "SYS_READ file", unsafe {
339 syscall3(
340 number::SYS_READ,
341 file_fd,
342 buf.as_mut_ptr() as usize,
343 msg.len(),
344 )
345 });
346 let mut st = data::FileStat::zeroed();
347 let _ = check_ok(ctx, "SYS_FSTAT file", unsafe {
348 syscall2(
349 number::SYS_FSTAT,
350 file_fd,
351 &mut st as *mut data::FileStat as usize,
352 )
353 });
354 let _ = check_ok(ctx, "SYS_FCHMOD file_fd 0644", unsafe {
355 syscall2(SYS_FCHMOD, file_fd, 0o644)
356 });
357 let _ = check_ok(ctx, "SYS_FTRUNCATE file_fd -> 4", unsafe {
358 syscall2(SYS_FTRUNCATE, file_fd, 4)
359 });
360 }
361
362 let dir_fd = check_ok(ctx, "SYS_OPEN dir O_DIRECTORY", unsafe {
363 syscall3(
364 number::SYS_OPEN,
365 DIR.as_ptr() as usize,
366 DIR.len(),
367 O_READ | O_DIRECTORY,
368 )
369 })
370 .unwrap_or(usize::MAX);
371
372 let _ = check_ok(ctx, "SYS_CHDIR dir", unsafe {
373 syscall2(SYS_CHDIR, DIR.as_ptr() as usize, DIR.len())
374 });
375 let mut cwd = [0u8; 128];
376 let _ = check_ok(ctx, "SYS_GETCWD", unsafe {
377 syscall2(SYS_GETCWD, cwd.as_mut_ptr() as usize, cwd.len())
378 });
379 if dir_fd != usize::MAX {
380 let _ = check_ok(ctx, "SYS_FCHDIR back", unsafe {
381 syscall1(SYS_FCHDIR, dir_fd)
382 });
383 }
384
385 let _ = check_ok(ctx, "SYS_UMASK set 022", unsafe {
386 syscall1(SYS_UMASK, 0o022)
387 });
388 let _ = check_ok(ctx, "SYS_CHMOD file 0644", unsafe {
389 syscall3(SYS_CHMOD, FILE.as_ptr() as usize, FILE.len(), 0o644)
390 });
391 let _ = check_ok(ctx, "SYS_RENAME file -> data_renamed.txt", unsafe {
392 syscall4(
393 SYS_RENAME,
394 FILE.as_ptr() as usize,
395 FILE.len(),
396 FILE_RENAMED.as_ptr() as usize,
397 FILE_RENAMED.len(),
398 )
399 });
400 let _ = check_ok(ctx, "SYS_LINK renamed -> hardlink", unsafe {
401 syscall4(
402 SYS_LINK,
403 FILE_RENAMED.as_ptr() as usize,
404 FILE_RENAMED.len(),
405 FILE_LINK.as_ptr() as usize,
406 FILE_LINK.len(),
407 )
408 });
409 let _ = check_ok(ctx, "SYS_SYMLINK renamed -> symlink", unsafe {
410 syscall4(
411 SYS_SYMLINK,
412 FILE_RENAMED.as_ptr() as usize,
413 FILE_RENAMED.len(),
414 FILE_SYMLINK.as_ptr() as usize,
415 FILE_SYMLINK.len(),
416 )
417 });
418 let mut rl = [0u8; 64];
419 check_expect_err(
420 ctx,
421 "SYS_READLINK regular file -> EINVAL",
422 unsafe {
423 syscall4(
424 SYS_READLINK,
425 FILE_RENAMED.as_ptr() as usize,
426 FILE_RENAMED.len(),
427 rl.as_mut_ptr() as usize,
428 rl.len(),
429 )
430 },
431 Error::InvalidArgument,
432 );
433
434 if file_fd != usize::MAX {
435 let _ = check_ok(ctx, "SYS_CLOSE file", unsafe {
436 syscall1(number::SYS_CLOSE, file_fd)
437 });
438 }
439 if dir_fd != usize::MAX {
440 let _ = check_ok(ctx, "SYS_CLOSE dir", unsafe {
441 syscall1(number::SYS_CLOSE, dir_fd)
442 });
443 }
444 let _ = check_ok(ctx, "SYS_UNLINK hardlink", unsafe {
445 syscall2(SYS_UNLINK, FILE_LINK.as_ptr() as usize, FILE_LINK.len())
446 });
447 let _ = check_ok(ctx, "SYS_UNLINK symlink", unsafe {
448 syscall2(
449 SYS_UNLINK,
450 FILE_SYMLINK.as_ptr() as usize,
451 FILE_SYMLINK.len(),
452 )
453 });
454 let _ = check_ok(ctx, "SYS_UNLINK renamed file", unsafe {
455 syscall2(
456 SYS_UNLINK,
457 FILE_RENAMED.as_ptr() as usize,
458 FILE_RENAMED.len(),
459 )
460 });
461 let _ = check_ok(ctx, "SYS_RMDIR dir", unsafe {
462 syscall2(SYS_RMDIR, DIR.as_ptr() as usize, DIR.len())
463 });
464}
465
466fn test_handles(ctx: &mut Ctx) {
468 section("Handle syscalls via semaphore handle");
469
470 let sem = check_ok(ctx, "SYS_SEM_CREATE initial=1", unsafe {
471 syscall1(number::SYS_SEM_CREATE, 1)
472 })
473 .unwrap_or(usize::MAX);
474 if sem == usize::MAX {
475 return;
476 }
477
478 let mut info = data::HandleInfo {
479 resource_type: 0,
480 permissions: 0,
481 resource: 0,
482 };
483 let _ = check_ok(ctx, "handle_info(sem)", call::handle_info(sem, &mut info));
484 log("[info] resource_type=");
485 log_u64(info.resource_type as u64);
486 log(" perms=");
487 log_hex_u64(info.permissions as u64);
488 log(" resource=");
489 log_hex_u64(info.resource);
490 log("\n");
491
492 let _ = check_ok(
493 ctx,
494 "handle_wait(sem immediate)",
495 call::handle_wait_timeout(sem, 0),
496 );
497 let _ = check_ok(ctx, "SYS_SEM_WAIT consume token", unsafe {
498 syscall1(number::SYS_SEM_WAIT, sem)
499 });
500 check_expect_err(
501 ctx,
502 "handle_wait_timeout(sem empty, 1ms) -> ETIMEDOUT",
503 call::handle_wait_timeout(sem, 1_000_000),
504 Error::TimedOut,
505 );
506 let _ = check_ok(ctx, "SYS_SEM_POST produce token", unsafe {
507 syscall1(number::SYS_SEM_POST, sem)
508 });
509 let _ = check_ok(
510 ctx,
511 "handle_wait(sem after post)",
512 call::handle_wait_timeout(sem, 1_000_000),
513 );
514
515 let self_pid = call::getpid().unwrap_or(0);
516 let dup = check_ok(
517 ctx,
518 "handle_grant(self_pid)",
519 call::handle_grant(sem, self_pid),
520 )
521 .unwrap_or(usize::MAX);
522 if dup != usize::MAX {
523 let _ = check_ok(ctx, "handle_revoke(dup)", call::handle_revoke(dup));
524 }
525 match unsafe { syscall1(number::SYS_SEM_CLOSE, sem) } {
526 Ok(v) => ok(ctx, "SYS_SEM_CLOSE(original)", v),
527 Err(Error::NotFound) | Err(Error::BadHandle) => {
528 ok(ctx, "SYS_SEM_CLOSE(original already cleaned)", 0)
529 }
530 Err(e) => fail(ctx, "SYS_SEM_CLOSE(original)", e),
531 }
532}
533
534#[panic_handler]
535fn panic(_info: &PanicInfo) -> ! {
537 log_err("[test_syscalls] PANIC\n");
538 call::exit(250)
539}
540
541#[no_mangle]
542pub extern "C" fn _start() -> ! {
544 let mut ctx = Ctx { pass: 0, fail: 0 };
545
546 section("Strat9 syscall ISO test suite (verbose + broad coverage)");
547 test_process_and_ids(&mut ctx);
548 test_memory(&mut ctx);
549 test_fs(&mut ctx);
550 test_handles(&mut ctx);
551
552 section("Summary");
553 log("[summary] pass=");
554 log_u64(ctx.pass);
555 log(" fail=");
556 log_u64(ctx.fail);
557 log("\n");
558
559 if ctx.fail == 0 {
560 call::exit(0);
561 } else {
562 call::exit(1);
563 }
564}