Skip to main content

test_syscalls/
syscalls.rs

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
29/// Writes fd.
30fn write_fd(fd: usize, msg: &str) {
31    let _ = call::write(fd, msg.as_bytes());
32}
33
34/// Implements log.
35fn log(msg: &str) {
36    write_fd(1, msg);
37}
38
39/// Implements log err.
40fn log_err(msg: &str) {
41    write_fd(2, msg);
42}
43
44/// Implements log u64.
45fn 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
61/// Implements log hex u64.
62fn 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
78/// Implements section.
79fn section(title: &str) {
80    log("\n============================================================\n");
81    log("[test_syscalls] ");
82    log(title);
83    log("\n============================================================\n");
84}
85
86/// Implements ok.
87fn 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
98/// Implements fail.
99fn 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
108/// Implements check ok.
109fn 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
122/// Implements check expect one of.
123fn 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
153/// Implements check expect err.
154fn 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
181/// Implements test process and ids.
182fn 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
222/// Implements test memory.
223fn 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
287/// Implements test fs.
288fn 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
466/// Implements test handles.
467fn 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]
535/// Implements panic.
536fn panic(_info: &PanicInfo) -> ! {
537    log_err("[test_syscalls] PANIC\n");
538    call::exit(250)
539}
540
541#[no_mangle]
542/// Implements start.
543pub 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}