Skip to main content

What are Schemes?

Schemes are Redox’s unified resource access mechanism. Similar to Plan 9’s “everything is a file” philosophy, Redox takes it further with “everything is a URL”.
In Redox, all resources—files, network connections, devices, IPC—are accessed through URL-like paths called schemes.

Scheme Syntax

scheme:[path]

Examples:
file:/path/to/file.txt
tcp:example.com:80
display:0/input
pty:
rand:
memory:0x1000/0x100

Scheme Architecture

How Schemes Work

Opening a Resource

use std::fs::File;

// Opens file: scheme
let file = File::open("/file/document.txt")?;

// Internally translates to:
// open("file:/document.txt", O_RDONLY)
1

Application Request

Application calls open("tcp:example.com:80")
2

Library Translation

relibc/libredox parses the URL and makes a syscall
3

Kernel Routing

Kernel routes the request to the tcp: scheme provider
4

Scheme Handler

Network service handles the request and returns a file descriptor
5

Return to Application

Application receives a file descriptor to read/write

Scheme Communication Flow

Built-in Schemes

Redox provides many built-in schemes for different purposes:

Kernel Schemes

Provided directly by the kernel:
// debug: - Kernel debug output
let mut debug = File::create("debug:")?;
writeln!(debug, "Debug message")?;

// event: - Event notification
let event = File::open("event:")?;

// time: - System time
let time = File::open("time:")?;

// sys: - System information
let sys = File::open("sys:context")?;

File Schemes

File system access:
// file: - Main file system
let file = File::open("file:/home/user/document.txt")?;

// Or using Unix-style paths (automatic translation)
let file = File::open("/home/user/document.txt")?;
//                    ^ becomes file:/home/user/document.txt
Paths starting with / are automatically converted to file: scheme URLs by relibc.

Network Schemes

Provided by smolnetd:
use std::net::TcpStream;
use std::io::{Read, Write};

// Connects via tcp: scheme
let mut stream = TcpStream::connect("example.com:80")?;

// HTTP request
stream.write_all(b"GET / HTTP/1.1\r\n")?;
stream.write_all(b"Host: example.com\r\n")?;
stream.write_all(b"\r\n")?;

let mut response = String::new();
stream.read_to_string(&mut response)?;

Device Schemes

Hardware device access:
# Device symlinks from base.toml
/dev/null    -> /scheme/null     # Null device
/dev/random  -> /scheme/rand     # Random numbers
/dev/urandom -> /scheme/rand     # Random numbers
/dev/zero    -> /scheme/zero     # Zero bytes
// rand: - Random number generator
use std::fs::File;
use std::io::Read;

let mut rng = File::open("rand:")?;
let mut random_bytes = [0u8; 32];
rng.read_exact(&mut random_bytes)?;

// null: - Discard all writes
let mut null = File::create("null:")?;
null.write_all(b"This data disappears")?;

// zero: - Infinite zero bytes
let mut zero = File::open("zero:")?;
let mut zeros = [0u8; 1024];
zero.read_exact(&mut zeros)?; // All zeros

IPC Schemes

Inter-process communication:
use std::fs::File;
use std::io::{Read, Write};

// Create shared memory region
let mut shm = File::create("shm:my-region")?;
shm.write_all(b"Shared data")?;

// Another process can access it
let mut shm = File::open("shm:my-region")?;
let mut data = String::new();
shm.read_to_string(&mut data)?;
// Message passing between processes
let tx = File::create("chan:messages")?;
let rx = File::open("chan:messages")?;

// Send message
tx.write_all(b"Hello from sender")?;

// Receive message
let mut msg = String::new();
rx.read_to_string(&mut msg)?;
use std::os::unix::net::UnixStream;

// Connect to Unix socket
let stream = UnixStream::connect("/tmp/socket")?;
use std::os::unix::net::UnixDatagram;

// Create datagram socket
let socket = UnixDatagram::bind("/tmp/socket")?;

Display Schemes

Graphics and display access:
// display: - Direct framebuffer access
let mut display = File::open("display:0")?;

// orbital: - Window management
use orbclient::{Color, Renderer, Window};

let mut window = Window::new(
    100, 100, 800, 600,
    "My Application"
)?;

window.set(Color::rgb(255, 0, 0));
window.sync();

Terminal Schemes

// pty: - Pseudo-terminal
use std::fs::File;

let pty = File::open("pty:")?;
// Returns master and slave PTY pair

Other Schemes

sudo:

Privilege escalation for authorized users

audio:

Audio device access

log:

System logging

Scheme Permissions

Schemes have fine-grained permission control:
# From base.toml - User scheme permissions
[user_schemes.root]
schemes = ["*"]  # Root has access to all schemes

[user_schemes.user]
schemes = [
  # Kernel schemes
  "debug", "event", "memory", "pipe", "serio", "irq", "time", "sys",
  
  # Base schemes
  "rand", "null", "zero", "log",
  
  # Network schemes
  "ip", "icmp", "tcp", "udp",
  
  # IPC schemes
  "shm", "chan", "uds_stream", "uds_dgram",
  
  # File schemes
  "file",
  
  # Display schemes
  "display.vesa", "display*",
  
  # Other schemes
  "pty", "sudo", "audio", "orbital",
]
Users can only access schemes explicitly granted in their permission list. Attempting to access unauthorized schemes results in “Permission denied” errors.

Implementing a Scheme Provider

You can implement custom scheme providers:
use redox_scheme::{Scheme, Response};
use syscall::{Error, Result, EBADF, EINVAL};
use std::collections::BTreeMap;

struct MyScheme {
    next_fd: usize,
    handles: BTreeMap<usize, Vec<u8>>,
}

impl MyScheme {
    fn new() -> Self {
        MyScheme {
            next_fd: 0,
            handles: BTreeMap::new(),
        }
    }
}

impl Scheme for MyScheme {
    fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result<usize> {
        // Create new handle
        let fd = self.next_fd;
        self.next_fd += 1;
        
        self.handles.insert(fd, Vec::new());
        Ok(fd)
    }
    
    fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<usize> {
        if let Some(data) = self.handles.get_mut(&id) {
            let len = std::cmp::min(buf.len(), data.len());
            buf[..len].copy_from_slice(&data[..len]);
            data.drain(..len);
            Ok(len)
        } else {
            Err(Error::new(EBADF))
        }
    }
    
    fn write(&mut self, id: usize, buf: &[u8]) -> Result<usize> {
        if let Some(data) = self.handles.get_mut(&id) {
            data.extend_from_slice(buf);
            Ok(buf.len())
        } else {
            Err(Error::new(EBADF))
        }
    }
    
    fn close(&mut self, id: usize) -> Result<usize> {
        self.handles.remove(&id).ok_or(Error::new(EBADF))?;
        Ok(0)
    }
    
    // Implement other methods as needed...
}

fn main() {
    // Register scheme with kernel
    let mut scheme = MyScheme::new();
    let socket = syscall::open(
        ":myscheme",
        syscall::O_RDWR | syscall::O_CREAT
    ).expect("Failed to create scheme");
    
    // Handle scheme requests
    loop {
        let mut packet = syscall::Packet::default();
        syscall::read(socket, &mut packet).expect("Failed to read packet");
        
        let response = scheme.handle(&packet);
        syscall::write(socket, &response).expect("Failed to write response");
    }
}
1

Implement Scheme Trait

Implement the Scheme trait with handlers for open, read, write, close, etc.
2

Register with Kernel

Open a special path (:schemename) to register your scheme
3

Handle Requests

Loop reading packets from the kernel and responding
4

Run as Service

Run your scheme provider as a system service

Scheme Namespaces

Processes can have their own scheme namespaces:
// Process A might see:
// file: -> RedoxFS on /dev/sda1

// Process B (in container) might see:
// file: -> Different filesystem
// tcp: -> Virtual network
Scheme namespaces enable containerization and sandboxing in Redox.

Advantages of Scheme System

1. Unified Interface

All resources use the same API:
use std::fs::File;
use std::io::{Read, Write};

// Same code pattern for different resources
fn read_resource(url: &str) -> std::io::Result<String> {
    let mut file = File::open(url)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

// Works with any scheme
let file_data = read_resource("file:/data.txt")?;
let network_data = read_resource("tcp:api.example.com:80")?;
let random_data = read_resource("rand:")?;

2. Flexibility

Pluggable

Replace scheme implementations without changing applications

Virtual

Create virtual resources (e.g., virtual file systems)

Remote

Access remote resources transparently

Composable

Combine schemes for powerful abstractions

3. Security

Fine-grained access control:
// User can only access permitted schemes
// Attempt to access unauthorized scheme:
let result = File::open("memory:0x1000");
// Error: Permission denied

4. Network Transparency

// Local file
let file = File::open("file:/local/data.txt")?;

// Could be extended for remote files
let file = File::open("nfs:server.example.com/data.txt")?;
// Same API, different backend

Comparison with Traditional Unix

AspectUnix /devRedox Schemes
Syntax/dev/sda1disk:0/1 or /dev/sda1
NetworkSockets APItcp:host:port
IPCSeparate APIschan:name, shm:name
FlexibilityFixed pathsURL-based, flexible
PermissionsFile permissionsScheme permissions
NamespacesMount pointsScheme namespaces

Practical Examples

Example 1: HTTP Client

use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;

fn http_get(host: &str, path: &str) -> std::io::Result<String> {
    // Connect via tcp: scheme
    let mut stream = TcpStream::connect(format!("{}:80", host))?;
    
    // Send HTTP request
    write!(stream, "GET {} HTTP/1.1\r\n", path)?;
    write!(stream, "Host: {}\r\n", host)?;
    write!(stream, "Connection: close\r\n\r\n")?;
    
    // Read response
    let reader = BufReader::new(stream);
    let mut response = String::new();
    
    for line in reader.lines() {
        response.push_str(&line?);
        response.push('\n');
    }
    
    Ok(response)
}

fn main() {
    match http_get("example.com", "/") {
        Ok(response) => println!("Response:\n{}", response),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Example 2: Device Access

use std::fs::File;
use std::io::{Read, Write};

fn generate_random_file(path: &str, size: usize) -> std::io::Result<()> {
    // Read from random device
    let mut rng = File::open("rand:")?;
    let mut random_data = vec![0u8; size];
    rng.read_exact(&mut random_data)?;
    
    // Write to file
    let mut file = File::create(path)?;
    file.write_all(&random_data)?;
    
    Ok(())
}

fn main() {
    generate_random_file("/file/random.bin", 1024)
        .expect("Failed to generate random file");
}

Example 3: IPC Communication

use std::fs::File;
use std::io::{Read, Write};
use std::thread;

fn main() {
    // Create shared memory region
    let mut sender = File::create("shm:example").unwrap();
    
    // Spawn receiver thread
    let receiver = thread::spawn(move || {
        let mut shm = File::open("shm:example").unwrap();
        let mut data = String::new();
        shm.read_to_string(&mut data).unwrap();
        println!("Received: {}", data);
    });
    
    // Send data
    sender.write_all(b"Hello from sender!").unwrap();
    
    receiver.join().unwrap();
}

Next Steps

Architecture Overview

Return to architecture overview

System Components

Learn about system components

Build System

Build Redox from source

Contributing

Contribute to Redox OS

Build docs developers (and LLMs) love