RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / import / kernel-user / demo.rs
6828 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Stage 6 demo: this program was itself loaded from the FAT32 disk.
//! It exercises the syscalls the shell (next stage) will need: cwd, reading
//! a file on the FAT disk, stat, lseek, getdents64, mkdir/unlink/rmdir,
//! pipes, and spawn + wait. Exit code = number of failed checks.
#![no_std]
#![no_main]

#[macro_use]
#[path = "ulib.rs"]
mod ulib;

use ulib::*;

static mut FAILURES: i64 = 0;

fn check(ok: bool, what: &str) {
    if !ok {
        println!("[demo] CHECK FAILED: {}", what);
        unsafe { FAILURES += 1 };
    }
}

fn cwd_is(expected: &str) -> bool {
    let mut buf = [0u8; 64];
    let n = getcwd(&mut buf);
    n >= 0 && &buf[..n as usize] == expected.as_bytes()
}

fn main() -> i64 {
    println!("[demo] pid {} started from /mnt/disk0/bin/demo", getpid());

    // --- current directory --------------------------------------------------
    check(cwd_is("/"), "cwd starts at /");
    check(chdir("/mnt/disk0") == 0, "chdir /mnt/disk0");
    check(cwd_is("/mnt/disk0"), "getcwd after chdir");

    // --- read a file from the FAT disk (relative path!) ---------------------
    let fd = open("readme.txt", 0);
    check(fd >= 3, "open readme.txt");
    let mut buf = [0u8; 256];
    let n = read(fd, &mut buf).max(0) as usize;
    let text = core::str::from_utf8(&buf[..n]).unwrap_or("?");
    print!("[demo] readme.txt from FAT32: {}", text.lines().next().unwrap_or(""));
    println!();
    check(text.starts_with("Hello from the FAT32 disk!"), "readme.txt contents");

    // --- stat / fstat / lseek ------------------------------------------------
    let mut st = Stat::default();
    check(stat("readme.txt", &mut st) == 0 && st.is_file() && st.size == n as i64, "stat readme.txt");
    check(fstat(1, &mut st) == 0 && st.is_char_device() && isatty(1), "fstat stdout = a terminal");
    check(stat("bin", &mut st) == 0 && st.is_dir(), "stat bin is a directory");
    check(lseek(fd, 6, SEEK_SET) == 6, "lseek SEEK_SET");
    let mut word = [0u8; 4];
    check(read(fd, &mut word) == 4 && &word == b"from", "read after lseek");
    check(lseek(fd, 0, SEEK_END) == n as i64, "lseek SEEK_END = size");
    check(lseek(1, 0, SEEK_SET) == -ESPIPE, "can't seek the console");
    close(fd);

    // --- list a directory with getdents64 -----------------------------------
    let dir = open("bin", 0);
    let mut dents = [0u8; 1024];
    let mut len = getdents64(dir, &mut dents);
    check(len > 0, "getdents64 bin");
    let (mut saw_demo, mut saw_child) = (false, false);
    print!("[demo] /mnt/disk0/bin:");
    // One call returns as many whole records as fit in the buffer; keep
    // calling until it returns 0 (the end of the directory).
    while len > 0 {
        for_each_dirent(&dents, len as usize, |name, d_type| {
            print!(" {}{}", name, if d_type == 4 { "/" } else { "" });
            saw_demo |= name == "demo";
            saw_child |= name == "child";
        });
        len = getdents64(dir, &mut dents);
    }
    println!();
    check(saw_demo && saw_child, "bin contains demo and child");
    check(len == 0 && getdents64(dir, &mut dents) == 0, "getdents64 at end returns 0");
    close(dir);

    // --- relative paths, "..", errors ----------------------------------------
    check(chdir("notes") == 0 && cwd_is("/mnt/disk0/notes"), "chdir notes");
    check(chdir("../..") == 0 && cwd_is("/mnt"), "chdir ../..");
    check(chdir("disk0/readme.txt") == -ENOTDIR, "chdir to a file -> ENOTDIR");
    check(chdir("/nope") == -ENOENT, "chdir to nothing -> ENOENT");

    // --- create and delete (ramfs /tmp and the FAT disk) ---------------------
    for base in ["/tmp", "/mnt/disk0/notes"] {
        let mut path_buf = [0u8; 64];
        let dir = join(&mut path_buf, base, "/newdir");
        check(mkdir(dir) == 0, "mkdir");
        check(mkdir(dir) == -EEXIST, "mkdir again -> EEXIST");
        let mut file_buf = [0u8; 64];
        let file = join(&mut file_buf, dir, "/f.txt");
        let fd = open(file, O_WRONLY | O_CREAT);
        check(write(fd, b"bytes") == 5, "write new file");
        close(fd);
        check(rmdir(dir) == -ENOTEMPTY, "rmdir non-empty -> ENOTEMPTY");
        check(unlink(dir) == -EISDIR, "unlink a directory -> EISDIR");
        check(unlink(file) == 0, "unlink file");
        check(rmdir(dir) == 0, "rmdir empty dir");
        check(stat(dir, &mut st) == -ENOENT, "gone after rmdir");
    }
    check(rmdir("/tmp") == -EBUSY, "rmdir a mount point -> EBUSY");
    println!("[demo] mkdir/unlink/rmdir work on ramfs and FAT32");

    // --- spawn a child with its stdout connected to a pipe -------------------
    chdir("/mnt/disk0");
    let (r, w) = pipe().unwrap_or((-1, -1));
    let opts = SpawnOptions { stdout: w, ..SpawnOptions::INHERIT };
    let pid = spawn("bin/child", "child\0pipe", &opts);
    check(pid > 0, "spawn child (pipe)");
    close(w); // only the child holds the write end now: EOF when it exits
    let mut got = [0u8; 128];
    let mut total = 0;
    loop {
        let n = read(r, &mut got[total..]); // blocks until data or EOF
        if n <= 0 {
            break;
        }
        total += n as usize;
    }
    close(r);
    let line = core::str::from_utf8(&got[..total]).unwrap_or("?").trim_end();
    println!("[demo] read from pipe: {:?}", line);
    check(line.starts_with("hello through a pipe"), "pipe contents");
    check(wait(pid) == 0, "child (pipe) exit code 0");

    // --- spawn + wait: exit codes ---------------------------------------------
    let pid = spawn("bin/child", "child\0with\0three args", &SpawnOptions::INHERIT);
    check(wait(pid) == 0, "child with args exits 0");
    let pid = spawn("bin/child", "child\042", &SpawnOptions::INHERIT);
    let code = wait(pid);
    println!("[demo] spawned child 42 (pid {}), wait -> exit code {}", pid, code);
    check(code == 42, "wait returns the exit code");
    check(wait(pid) == -ECHILD, "second wait -> ECHILD");
    let pid = spawn("/mnt/disk0/bin/evil", "", &SpawnOptions::INHERIT);
    check(wait(pid) == 139, "killed child -> 139");
    check(spawn("bin/nope", "", &SpawnOptions::INHERIT) == -ENOENT, "spawn missing -> ENOENT");
    check(spawn("readme.txt", "", &SpawnOptions::INHERIT) == -8, "spawn non-ELF -> ENOEXEC");
    check(wait(1) == -ECHILD, "wait for a non-child -> ECHILD");

    sleep_ms(100); // blocks in the kernel (no busy loop)

    let failures = unsafe { FAILURES };
    if failures == 0 {
        println!("[demo] all stage 6 checks passed, exiting with 0");
    }
    failures
}

/// Concatenate two strings into `buf` (no heap in user programs).
fn join<'a>(buf: &'a mut [u8], a: &str, b: &str) -> &'a str {
    buf[..a.len()].copy_from_slice(a.as_bytes());
    buf[a.len()..a.len() + b.len()].copy_from_slice(b.as_bytes());
    core::str::from_utf8(&buf[..a.len() + b.len()]).unwrap()
}