Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/redox-os/redox/llms.txt

Use this file to discover all available pages before exploring further.

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