//! 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 len = getdents64(dir, &mut dents); check(len > 0, "getdents64 bin"); let (mut saw_demo, mut saw_child) = (false, false); print!("[demo] /mnt/disk0/bin:"); for_each_dirent(&dents, len.max(0) as usize, |name, d_type| { print!(" {}{}", name, if d_type == 4 { "/" } else { "" }); saw_demo |= name == "demo"; saw_child |= name == "child"; }); println!(); check(saw_demo && saw_child, "bin contains demo and child"); check(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() }