Programming for Redox OS
Tags: redox, rust, os
Published: 2018-07-05 17:52:55

---
title: Programming for Redox OS
published: true
description: This post will dive in on what schemes in Redox OS are and how much easier they can make your code.
tags: redox,rust,os
---

If you don't know what Redox OS is, it's an entire operating system written in Rust. This is because Rust has some amazing guarantees about memory and thread safety. To make it even safer, it's a microkernel as opposed to a monolithic kernel. That means that for example drivers and similar run in userspace and cannot crash the entire computer in the same way.

To communicate between processes and the kernel, Redox OS has custom schemes. It follows the tradition of having [everything be a URL](https://doc.redox-os.org/book/design/url_scheme_resource/everything_is_a_url.html). Schemes are dead simple and super awesome. You open a file with the path `scheme:path`. If no scheme is specified it defaults to the one of the current working directory - which is usually going to be `file:`.

## TCP

An example of the scheme is the TCP protocol. Even though networking is supposed to be hard, in redox it's dead simple. You just `open("tcp:address", O_RDWR)`.
Here's an example that curls example.com without any libraries what-so-ever (not an idiomatic use of the http protocol - prefer a library for doing http requests):
```
let mut file = File::open("tcp:93.184.216.34:80")?; // example.com
file.write(b"GET / HTTP/1.1\nHost: example.com\nConnection: close\n\n")?;

io::copy(&mut file, &mut io::stdout())?;
```

## Program IPC

Another example of a scheme is one I wrote myself - the ipcd daemon. It's a simple scheme for inter-process communication. You call `open("chan:name", O_CREAT)` to create a server, and `dup("listen")` to accept a connection. The `dup` syscall is for duplicating files, but can optionally take a path which can be used from the scheme.
Here is a simple hello world server using ipcd - this time we need the syscall library for redox in order to duplicate the file.
```Rust
let server = File::create("chan:hello")?;

loop {
    let stream = syscall::dup(server.as_raw_fd(), b"listen")?;
    let mut stream = unsafe { File::from_raw_fd(stream) };

    stream.write(b"Hello World!\n")?;
}
```
And to read from this ipc channel you just have to `cat chan:hello`.

## Events

This scheme is somewhat special, not because it has any special hardcoded exception to it, but because it's used everywhere, even in other schemes.
The event scheme is kind of like epoll in linux. You `write` an event to an instance and it registers to listen for events from file (or instance of a scheme), and then you `read` and it blocks until the first event.
There is an excellent example in the [event overhaul RFC](https://gitlab.redox-os.org/redox-os/rfcs/blob/master/text/0002-event-overhaul.md#detailed-design).

This scheme works very well with the `time:` scheme, and can be used for setting a timeout to reading. The time scheme is simple, can read the current time and write a deadline which triggers an event when that time is elapsed.
Here's an example that timeouts a read to 2 seconds.
```Rust
const TOKEN_TIME: usize = 0;
const TOKEN_FILE: usize = 1;

let mut selector = File::open("event:")?;
let mut some_file = /* anything you want to read from here, like a tcp stream */;

// Deadline
let mut timeout = File::open("time:")?;
let mut time = TimeSpec::default();
timeout.read(&mut time)?;
time.tv_sec += 2; // deadline to 2 seconds in the future
timeout.write(&time)?;

selector.write(Event {
    id: timeout.as_raw_fd(),
    flags: EVENT_READ,
    data: TOKEN_TIME
})?;
selector.write(Event {
    id: some_file,
    flags: EVENT_READ,
    data: TOKEN_FILE
})?;

let mut event = Event::default();
selector.read(&mut event)?;

match event.data {
    TOKEN_TIME => {
        // The timeout of 2 seconds was exceeded
    },
    TOKEN_FILE => {
        // The file can now be read!
        // How much can be read is undefined and depends on the scheme.
        // Most built-in schemes are nowadays edge triggered and you should
        // have the file be non-blocking and read over and over again
        // until you hit EAGAIN.
        // This matches the behavior of linux.
        let mut buf = [0; 16];
        loop {
            match some_file.read(&mut buf)? {
                Ok(0) => break, // EOF, we read everything!
                Ok(n) => {
                    // Handle the read here.
                    io::stdout().write(&buf[..n])?;
                },
                Err(ref e) if e.kind() == ErrorKind::WouldBlock => break, // can't read any more this round.
                Err(e) => return Err(e)
            }
        }
    }
}
```

# How does it work?

*Note: Writing a custom scheme requires root*

## Main Loop

The scheme is registered using the root scheme - which has no name. `open(":test", O_CREAT)` registers the scheme and returns a file you can read packets from.
How do you handle the packets? That's where the Scheme family of traits come in! They have a function called `handle` that is already implemented, that reads the packet and calls the correct trait function.
A simple loop may look like this:
```Rust
let mut handler = Handler; // handler implements SchemeMut
let mut scheme = File::create(":hello")?;
loop {
    let mut packet = Packet::default();
    scheme.read(&mut packet)?;

    handler.handle(&mut packet);
    scheme.write(&packet)?;
}
```
Sadly most schemes' loops look more difficult because they might need to read from multiple schemes at the same time, or reschedule blocking functions.

## Unique IDs

The scheme has functions like these:
```Rust
fn open(&mut self, path: &[u8], flags: usize, _uid: u32, _gid: u32) -> Result<usize>;
```
where you return any integer you want!
A cool thing about Redox schemes is that each instance of the scheme (file descriptor) gets mapped to an ID, and multiple file descriptors can have the same ID (so you don't have to implement `dup`s without a path yourself).
What almost all schemes do here is keep an integer that points to the next id, and then on `open` it adds the id to the an internal map together with some struct with data about the handle, and finally increments the integer.
Later functions are called with the id:
```
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<usize>
```

## Blocking

A blocking function like read is usually rescheduled by the scheme when it fails. Usually they used to use the EWOULDBLOCK error internally to add the packet to a list of todos and then later (on either another scheme event or some timer) try to process that packet again. To ease with this, we now have a SchemeBlock trait that can return a None to indicate that it should be rescheduled.
For an example of a really simple blocking loop, check [ipcd's main file](https://gitlab.redox-os.org/redox-os/ipcd/blob/19f8137eef18ea4cbcec119402741dae6feec68e/src/main.rs#L26-52).

## Events

To make your schemes support events, you just need to write a packet to the scheme file with the values of a=SYS_FEVENT,b=id,c=flag,d=count (I don't *think* count is used anymore, you might just leave it at 1). There is also an `fevent` scheme function that will let you know when somebody has registered your scheme to an event instance. This can be used to reset variables to keep track of notified state in schemes that send edge triggered events.