RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

foundry / nbtest.rs
6390 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
//! nbtest -- checks O_NONBLOCK: "would block" becomes EAGAIN instead.
//!
//! A blocking read on an empty pipe sleeps until data comes. With
//! O_NONBLOCK (set with fcntl(F_SETFL), ioctl(FIONBIO), pipe2 or
//! SOCK_NONBLOCK) it returns -EAGAIN at once ("try again later"), which
//! is what an event loop wants: it only reads when poll said "ready", and
//! must never get stuck on one fd.
#![no_std]
#![no_main]

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

extern crate alloc;

use ulib::*;

const FIONBIO: u64 = 0x5421;

fn main() -> i64 {
    let mut c = Checks::new("nbtest");
    let mut buf = [0u8; 4096];

    // Pipes.
    let (r, w) = pipe2(O_NONBLOCK).unwrap();
    c.check(read(r, &mut buf) == -EAGAIN, "pipe: read with nothing there: EAGAIN");
    let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];
    c.check(poll(&mut p, 0) == 0, "pipe: poll says not readable");
    let mut total = 0;
    loop {
        let n = write(w, &buf);
        if n < 0 {
            c.check(n == -EAGAIN, "pipe: write to a full pipe: EAGAIN");
            break;
        }
        total += n;
    }
    println!("nbtest: the pipe took {} bytes before it was full", total);
    c.check(total == 65536, "pipe: holds 64 KiB, like Linux");
    let mut p = [PollFd { fd: w as i32, events: POLLOUT, revents: 0 }];
    c.check(poll(&mut p, 0) == 0, "pipe: poll says a full pipe isn't writable");
    c.check(read(r, &mut buf) == 4096, "pipe: then reading works");
    c.check(poll(&mut p, 0) == 1 && p[0].revents & POLLOUT != 0, "pipe: room again -> POLLOUT");
    close(w);
    while read(r, &mut buf) > 0 {}
    c.check(read(r, &mut buf) == 0, "pipe: writer gone -> end of file, not EAGAIN");
    close(r);

    // fcntl and FIONBIO switch it on and off.
    let (r, w) = pipe().unwrap();
    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "fcntl F_GETFL: blocking by default");
    set_nonblocking(r, true);
    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK != 0, "fcntl F_SETFL O_NONBLOCK sticks");
    c.check(read(r, &mut buf) == -EAGAIN, "fcntl-made nonblocking pipe: EAGAIN");
    let off = 0i32;
    ioctl(r, FIONBIO, &off as *const i32 as u64);
    c.check(fcntl(r, F_GETFL, 0) as u64 & O_NONBLOCK == 0, "ioctl FIONBIO 0 turns it off");
    close(r);
    close(w);

    // Sockets (over loopback).
    let l = socket(SOCK_STREAM, 0);
    bind(l, 7080);
    listen(l, 2);
    set_nonblocking(l, true);
    c.check(accept(l).0 == -EAGAIN, "socket: accept with nobody calling: EAGAIN");
    let s = socket(SOCK_STREAM | O_NONBLOCK, 0);
    let r = connect(s, Ip([127, 0, 0, 1]), 7080);
    c.check(r == 0 || r == -EINPROGRESS, "socket: nonblocking connect: EINPROGRESS (or done at once)");
    let mut p = [PollFd { fd: s as i32, events: POLLOUT, revents: 0 }];
    c.check(poll(&mut p, 2000) == 1 && p[0].revents & POLLOUT != 0, "socket: poll POLLOUT = connected");
    let mut p = [PollFd { fd: l as i32, events: POLLIN, revents: 0 }];
    c.check(poll(&mut p, 2000) == 1, "socket: listener POLLIN = a connection to accept");
    let (a, _) = accept(l);
    c.check(a >= 0, "socket: accept it");
    c.check(read(s, &mut buf) == -EAGAIN, "socket: recv with no data: EAGAIN");
    write(a, b"hi");
    let mut p = [PollFd { fd: s as i32, events: POLLIN, revents: 0 }];
    poll(&mut p, 2000);
    c.check(read(s, &mut buf) == 2, "socket: data after POLLIN");
    close(a);
    close(s);
    close(l);

    // A terminal: the slave side of a pty.
    if let Ok((m, n)) = openpty() {
        let slave = open(&alloc::format!("/dev/pts/{}", n), O_RDWR | O_NOCTTY | O_NONBLOCK);
        c.check(read(slave, &mut buf) == -EAGAIN, "tty: read with no line typed: EAGAIN");
        close(slave);
        close(m);
    } else {
        c.check(false, "openpty");
    }

    // Small extras libc needs: pread/pwrite, writev, clocks, nanosleep.
    let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR);
    writev(fd, &[b"hello ", b"vectored ", b"world"]);
    pwrite(fd, b"HELLO", 0);
    let n = pread(fd, &mut buf, 6);
    c.check(&buf[..n.max(0) as usize] == b"vectored world", "writev + pread at an offset");
    c.check(lseek(fd, 0, SEEK_CUR) == 20, "pread/pwrite leave the file position alone");
    lseek(fd, 0, SEEK_SET);
    let n = read(fd, &mut buf);
    c.check(&buf[..n.max(0) as usize] == b"HELLO vectored world", "pwrite overwrote the start");
    close(fd);
    unlink("/tmp/nbtest.txt");
    // readv: one read filling several buffers in turn.
    let fd = open("/tmp/nbtest.txt", O_CREAT | O_TRUNC | O_RDWR);
    write(fd, b"abcdefgh");
    lseek(fd, 0, SEEK_SET);
    let (mut a, mut b) = ([0u8; 3], [0u8; 5]);
    let n = readv(fd, &mut [&mut a, &mut b]);
    c.check(n == 8 && &a == b"abc" && &b == b"defgh", "readv fills the buffers in order");
    close(fd);
    unlink("/tmp/nbtest.txt");

    // select and ppoll on a pipe: not readable, then readable.
    let (r, w) = pipe2(0).unwrap();
    let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);
    fd_set(&mut rs, r);
    let t0 = now_us();
    c.check(select(r + 1, &mut rs, &mut ws, 20_000) == 0 && now_us() - t0 >= 20_000, "select: times out after 20 ms on an empty pipe");
    write(w, b"!");
    let (mut rs, mut ws) = ([0u64; 16], [0u64; 16]);
    fd_set(&mut rs, r);
    fd_set(&mut ws, w);
    c.check(select(w.max(r) + 1, &mut rs, &mut ws, 0) == 2 && fd_isset(&rs, r) && fd_isset(&ws, w), "select: pipe readable + writable");
    let mut p = [PollFd { fd: r as i32, events: POLLIN, revents: 0 }];
    c.check(ppoll_us(&mut p, 0) == 1 && p[0].revents & POLLIN != 0, "ppoll: POLLIN");
    close(r);
    close(w);

    // getcwd and uname, which libc start-up and `uname -a` use.
    let mut cwd = [0u8; 64];
    let n = getcwd(&mut cwd);
    c.check(n > 0 && cwd[0] == b'/', "getcwd gives an absolute path");
    let mut ub = [0u8; 6 * 65];
    let u = uname(&mut ub);
    c.check(u[0] == "Rustbelt" && u[3].starts_with("stage 15"), "uname: Rustbelt, stage 15");

    let (secs, _) = clock_gettime(0);
    c.check(secs > 1_600_000_000, "CLOCK_REALTIME is a real date (after 2020)");
    let t0 = now_us();
    nanosleep_us(30_000);
    c.check(now_us() - t0 >= 25_000, "nanosleep 30 ms");
    let mut link = [0u8; 64];
    let n = readlink("/proc/self/exe", &mut link);
    c.check(n > 0 && link[..n as usize].ends_with(b"nbtest"), "readlink /proc/self/exe = this program");
    c.done()
}