{"id":50986493,"url":"https://github.com/paulodhiambo/openv","last_synced_at":"2026-06-19T19:32:31.950Z","repository":{"id":363084654,"uuid":"1256919744","full_name":"paulodhiambo/openv","owner":"paulodhiambo","description":"RISC V Microkernel Operating System","archived":false,"fork":false,"pushed_at":"2026-06-18T07:47:00.000Z","size":171921,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-18T08:22:08.354Z","etag":null,"topics":["microkernel","operating-system","risc-v"],"latest_commit_sha":null,"homepage":"https://paulodhiambo.github.io/openv/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paulodhiambo.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T07:45:08.000Z","updated_at":"2026-06-07T10:49:48.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/paulodhiambo/openv","commit_stats":null,"previous_names":["paulodhiambo/openv"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/paulodhiambo/openv","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulodhiambo%2Fopenv","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulodhiambo%2Fopenv/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulodhiambo%2Fopenv/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulodhiambo%2Fopenv/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paulodhiambo","download_url":"https://codeload.github.com/paulodhiambo/openv/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulodhiambo%2Fopenv/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34546192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-19T02:00:06.005Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["microkernel","operating-system","risc-v"],"created_at":"2026-06-19T19:32:30.521Z","updated_at":"2026-06-19T19:32:31.942Z","avatar_url":"https://github.com/paulodhiambo.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# openv\n\n\u003e A RISC-V 64-bit microkernel OS written in Rust.\n\n[![CI](https://github.com/paulodhiambo/openv/actions/workflows/build.yml/badge.svg)](https://github.com/paulodhiambo/openv/actions/workflows/build.yml)\n[![Rust nightly](https://img.shields.io/badge/rust-nightly-orange)](rust-toolchain.toml)\n[![Architecture](https://img.shields.io/badge/arch-RISC--V%2064-blue)](#architecture)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\nopenv is a **RISC-V 64-bit microkernel** written in Rust. It boots under QEMU via OpenSBI, provides an interactive Unix-like environment, and implements a comprehensive POSIX-compatible kernel from scratch.\n\nFor detailed technical documentation, see the [Documentation Index](docs/index.md).\n\n- **Preemptive multitasking** — round-robin scheduler, 10 ms SBI timer slices\n- **Virtual memory** — Sv39 paging, COW fork, demand paging (zero-fill on access)\n- **VFS** — MemFS, ProcFS, DevFS, persistent block filesystem (OFS), mount points\n- **POSIX process model** — fork, exec, waitpid, pipes, SIGINT (Ctrl-C)\n- **IPC** — capability-style handles, bidirectional channels, Unix pipes\n- **Multi-user security** — Unix DAC (rwxr-xr-x), setuid/setgid, sudo group\n- **Networking** — virtio-mmio NIC driver + userspace smoltcp TCP/IP stack\n- **Interactive shell** — line editing, history, pipelines, I/O redirection\n- **SMP** — up to 4 harts, per-hart stacks, TLB shootdown via SBI IPI\n\n---\n\n## Quick Start\n\n```console\n# Prerequisites: Rust nightly + QEMU\nrustup toolchain install nightly \\\n  --component rust-src,llvm-tools-preview,rustfmt,clippy \\\n  --target riscv64gc-unknown-none-elf\n\nbrew install qemu          # macOS\n# apt install qemu-system-riscv64  # Ubuntu/Debian\n\n# Build userspace, package initrd, build kernel, and launch\nmake\n```\n\nYou'll land in a login prompt. Default credentials: **guest / guest** or **root / root**.\n\n```\n$ ls /\n$ cat /dummy.txt\n$ ls /proc\n$ ls /dev\n$ echo hello world\n$ cat /proc/1/status\n$ mkdir /tmp \u0026\u0026 ls /\n```\n\n---\n\n## Prerequisites\n\n| Tool | Version | Purpose |\n|------|---------|---------|\n| `rustup` + nightly | any recent nightly | Build kernel and userspace |\n| `rust-src` component | — | `build-std` for `core`/`alloc` |\n| `riscv64gc-unknown-none-elf` target | — | Cross-compilation |\n| `qemu-system-riscv64` | 7+ | Running the OS |\n| `riscv64-unknown-elf-gdb` | any | Optional: GDB debugging |\n\n---\n\n## Build System\n\n### Make targets\n\n| Target | Description |\n|--------|-------------|\n| `make` / `make all` | Build everything then run in QEMU (default) |\n| `make build` | Userspace → initrd → kernel (debug) |\n| `make build-release` | Full release build |\n| `make build-kernel` | Kernel only |\n| `make build-user` | Userspace binaries only |\n| `make initrd` | Package initrd from existing bins |\n| `make disk` | Create 8 MB `disk.img` for virtio-blk (auto-created by `run`) |\n| `make run` | Boot in QEMU; creates `disk.img` if absent |\n| `make debug` | Boot with GDB server on `:1234` |\n| `make image` | Build `openv.img` disk image |\n| `make fmt` | Format all Rust source |\n| `make clippy` | Lint kernel |\n| `make check` | `cargo check` kernel |\n| `make clean` | Remove build artifacts (preserves `disk.img` OFS data) |\n| `make clean-all` | Remove everything including `disk.img` |\n\n### Variables\n\n```console\nmake BINS=\"init sh\" QEMU_MEM=512M QEMU_CPUS=2 run\n```\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `BINS` | `init sh ls cat hello producer consumer doexec forktest net-smoltcp spin vfs-server pm-server rs-server virtio-blk-driver …` | Userspace binaries copied into the initrd |\n| `QEMU_MEM` | `512M` | QEMU guest memory |\n| `QEMU_CPUS` | `4` | Number of RISC-V harts |\n\n### Scripts\n\n| Script | Purpose |\n|--------|---------|\n| `scripts/build.sh [--release]` | Full build |\n| `scripts/run.sh` | Boot in QEMU |\n| `scripts/build_image.sh` | Create `openv.img` disk image |\n\n---\n\n## Project Structure\n\n```\nopenv/\n├── Makefile\n├── linker.ld                  # Kernel link at 0x80200000\n├── rust-toolchain.toml        # Nightly pin\n│\n├── src/                       ── Kernel ──────────────────────────────\n│   ├── boot.s                 # _start: park secondaries, BSS clear, kmain\n│   ├── main.rs                # kmain, panic handler, __halt_cpu\n│   ├── trap.rs                # Trap vector, ~60 syscalls, page faults, IRQs\n│   ├── uart.rs                # NS16550 UART driver, print!/println!\n│   ├── timer.rs               # SBI timer, 10 ms tick\n│   ├── plic.rs                # PLIC claim/complete\n│   ├── smp.rs                 # SMP startup, per-hart stacks, IPI\n│   ├── sync.rs                # Deadlock-detecting Mutex wrapper\n│   ├── errno.rs               # POSIX errno constants\n│   │\n│   ├── mm/                    ── Memory Management ────────────────────\n│   │   ├── pmm.rs             # Physical allocator: DTB detection, free-list, refcounts\n│   │   ├── vmm.rs             # Sv39 page tables, COW fork, demand paging\n│   │   ├── heap.rs            # Buddy allocator (16 MB heap)\n│   │   └── vmo.rs             # Virtual Memory Object\n│   │\n│   ├── vfs/                   ── Virtual File System ──────────────────\n│   │   ├── mod.rs             # Vnode trait, MountTable, lookup_path\n│   │   ├── tar.rs             # UStar parser → MemFS at boot\n│   │   ├── memfs.rs           # RoFile (zero-copy), MemFile (writable), MemDir\n│   │   ├── blockfs.rs         # Persistent block filesystem (OFS)\n│   │   ├── procfs.rs          # /proc/\u003cpid\u003e/status\n│   │   └── devfs.rs           # /dev/null  /dev/zero  /dev/tty\n│   │\n│   ├── posix/                 ── Process Model ────────────────────────\n│   │   ├── process.rs         # Process struct, PROCESS_TABLE, scheduler\n│   │   ├── spawn.rs           # posix_spawn, exit, sys_fork (COW), sys_exec\n│   │   ├── elf.rs             # ELF loader (PT_LOAD segments)\n│   │   ├── user.rs            # User/group DB, FNV-1a auth, sudo\n│   │   └── wait.rs            # waitpid, zombie reaping\n│   │\n│   ├── ipc/                   ── Inter-Process Communication ───────────\n│   │   ├── channel.rs         # Bidirectional message channels\n│   │   └── handle.rs          # HandleTable, KernelObject, pipes, FileDescription\n│   │\n│   ├── net/                   ── Networking ───────────────────────────\n│   │   ├── mod.rs             # NetDevice trait, probe/init\n│   │   ├── virtio_mmio.rs     # Virtio-mmio NIC driver (legacy, device-id=1)\n│   │   ├── virtio_net.rs      # Loopback fallback device\n│   │   ├── socket.rs          # Socket registry, pending accepts\n│   │   ├── virtqueue.rs       # Virtio split virtqueue implementation\n│   │   └── pktbuf.rs          # Packet buffer pool\n│   │\n│   ├── block/                 ── Block Devices ────────────────────────\n│   │   ├── mod.rs             # Block device registry\n│   │   └── virtio_blk.rs      # Virtio-blk driver (device-id=2)\n│   │\n│   └── drivers/               # FDT probe, interrupt dispatch table\n│\n└── user/                      ── Userspace ────────────────────────────\n    ├── libos/                 # POSIX syscall shim + 2 MB heap + _start\n    ├── init/                  # PID 1: login prompt, shell respawn\n    ├── sh/                    # Shell: editing, history, pipes, redirection\n    ├── ls/                    # Directory listing\n    ├── cat/                   # File / stdin concatenation\n    ├── hello/                 # Smoke-test binary\n    ├── spin/                  # CPU spin test (preemption verification)\n    ├── producer/ consumer/    # IPC pipe test programs\n    ├── doexec/ forktest/      # exec/fork test programs\n    └── net-smoltcp/           # Userspace TCP/IP daemon (smoltcp)\n```\n\n---\n\n## Architecture\n\n```\n╔══════════════════════════════════════════════════════════════════╗\n║  User processes (S-mode, U-bit PTEs)                             ║\n║   init │ sh │ ls │ cat │ net-smoltcp │ spin │ ...                ║\n║   └── libos (ecall wrappers, 2 MB bump heap, _start)             ║\n╠══════════════════════════════════════════════════════════════════╣\n║  Kernel trap handler  src/trap.rs                                ║\n║   ├─ Syscall dispatch (~60 syscalls, a7 = number)                ║\n║   ├─ Demand paging  (Exception 12/13 → alloc zero page)         ║\n║   ├─ COW fault      (Exception 15   → copy physical page)       ║\n║   ├─ Timer IRQ      (Interrupt 5    → re-queue, rearm SBI)      ║\n║   ├─ TLB shootdown  (Interrupt 1    → sfence.vma)               ║\n║   └─ External IRQ   (Interrupt 9/11 → PLIC → driver dispatch)   ║\n╠══════════════════════════════════════════════════════════════════╣\n║  Kernel subsystems                                               ║\n║   PMM  ── physical pages, refcounts (free-list + u16 array)      ║\n║   VMM  ── Sv39 page tables, clone_user_space, destroy_user_space ║\n║   VFS  ── Vnode trait, MemFS/ProcFS/DevFS/OFS, mount table      ║\n║   POSIX── Process table, FIFO scheduler, ELF loader, waitpid    ║\n║   IPC  ── Channels, HandleTable, pipes, FileDescriptions        ║\n║   NET  ── Virtio-mmio NIC driver, socket registry               ║\n║   BLK  ── Virtio-blk driver, OFS persistent filesystem          ║\n╠══════════════════════════════════════════════════════════════════╣\n║  RISC-V hardware (QEMU virt machine)                             ║\n║   NS16550 UART │ SBI timer │ PLIC │ Virtio-MMIO NIC/Blk          ║\n╚══════════════════════════════════════════════════════════════════╝\n```\n\n### Key Design Decisions\n\n**Pragmatic microkernel.** The kernel provides memory management, IPC, and trap dispatch. POSIX semantics (process lifecycle, VFS, scheduling) live in the kernel for v1 pragmatism. A strict Fuchsia-style split — with VFS and process servers in userspace — is the intended v2 direction.\n\n**Non-reentrant spinlocks.** `crate::sync::Mutex` is a deadlock-detecting wrapper around `spin::Mutex` that tracks the owning hart and panics on recursive acquisition. Timer IRQs therefore only *re-queue* the current process — they never call `schedule()` from interrupt context.\n\n**Capability-style handles.** File descriptors are `HandleTable` entries storing `KernelObject` variants (`Console`, `Channel`, `File`, `PipeRead`, `PipeWrite`, `Vmo`). `dup`/`dup2` clone the handle slot. Drop closes and frees the resource. Pipe EOF is detected via a `Weak` reference on the write-end sentinel `Arc\u003c()\u003e`.\n\n**COW fork + demand paging.** `sys_fork` marks all writable user pages in both parent and child as read-only and increments their refcounts. The first write to a shared page traps to `handle_store_page_fault`, which allocates a private copy, copies content, installs a writable PTE, and decrements the old refcount. Unmapped pages are zero-filled on first access.\n\n**UART line discipline.** The kernel drains the full UART FIFO on every timer tick (`poll_uart_into_linedisc`). Reads from the console syscall pull exclusively from `LINE_DISC_BUFFER`, eliminating any race between the timer ISR and `sys_read`. Raw mode (used by the shell for arrow-key history) delivers bytes immediately; cooked mode buffers until `\\n`.\n\n**Initrd-based root filesystem.** A UStar tar archive (`test_root.tar`) is passed via the DTB `chosen` node. The kernel parses it into a MemFS tree at boot. `/proc` (ProcFS) and `/dev` (DevFS) are layered on top via the mount table. OFS mounts at `/mnt` when a virtio-blk device is present; the first boot auto-formats a blank disk. `make run` creates `disk.img` automatically if it does not exist.\n\n**Userspace TCP/IP.** The kernel provides only raw Ethernet send/receive via two syscalls. The full TCP/IP stack (smoltcp) runs in the `net-smoltcp` userspace daemon, with connections proxied to user processes via kernel IPC channels.\n\n---\n\n## Syscall Table\n\n| # | Name | Signature | Description |\n|---|------|-----------|-------------|\n| 0 | `yield` | `()` | Yield CPU to next runnable process |\n| 1 | `exit` | `(status: i32) → !` | Terminate process, wake parent |\n| 2 | `write` | `(fd, buf, len) → isize` | Write to fd (Console/File/Pipe/Channel) |\n| 3 | `pipe` | `(\u0026[u32; 2]) → i32` | Create pipe pair, fill [read_fd, write_fd] |\n| 5 | `read` | `(fd, buf, max) → isize` | Read from fd (blocks until data) |\n| 6 | `spawn` | `(path, len) → i32` | Spawn child process, return PID |\n| 8 | `open` | `(path, len, flags) → i32` | Open file, return fd |\n| 9 | `close` | `(fd) → i32` | Close fd |\n| 10 | `net_send` | `(buf, len) → isize` | Send raw Ethernet frame |\n| 11 | `net_recv` | `(buf, max) → isize` | Receive raw Ethernet frame |\n| 12 | `getdents` | `(path, len, buf, max) → isize` | List directory (null-separated names) |\n| 23 | `setuid` | `(uid) → i32` | Set user ID |\n| 24 | `setgid` | `(gid) → i32` | Set group ID |\n| 26 | `create` | `(path, len) → i32` | Create/truncate file, return writable fd |\n| 27 | `mkdir` | `(path, len) → i32` | Create directory |\n| 28 | `unlink` | `(path, len) → i32` | Remove file |\n| 29 | `rename` | `(old, olen, new, nlen) → i32` | Rename file |\n| 30 | `getuid` | `() → u32` | Real UID |\n| 31 | `geteuid` | `() → u32` | Effective UID |\n| 32 | `getgid` | `() → u32` | Real GID |\n| 33 | `getegid` | `() → u32` | Effective GID |\n| 34 | `authenticate` | `(user, ulen, pass, plen) → u32` | Verify credentials, return UID or -1 |\n| 35 | `can_sudo` | `(uid) → i32` | 1 if uid can sudo |\n| 37 | `set_echo` | `(enabled) → i32` | Toggle terminal echo |\n| 38 | `set_raw` | `(enabled) → i32` | Toggle raw mode (char-by-char) |\n| 39 | `set_fg_pid` | `(pid) → i32` | Set foreground process for SIGINT delivery |\n| 40 | `socket` | `() → i32` | Create proxied socket fd |\n| 41 | `daemon_next_socket` | `() → usize` | Net daemon: pop next socket |\n| 42 | `daemon_create_conn` | `(listen_sid) → i32` | Net daemon: create accepted connection |\n| 43 | `accept` | `(listen_fd) → i32` | Accept incoming connection (blocks) |\n| 44 | `bind` | `(fd, addr, alen) → i32` | Bind socket to address |\n| 45 | `listen` | `(fd, backlog) → i32` | Mark socket as listening |\n| 46 | `connect` | `(fd, addr, alen) → i32` | Connect to remote address |\n| 47 | `sock_send` | `(fd, buf, len) → isize` | Send data on socket |\n| 48 | `sock_recv` | `(fd, buf, max) → isize` | Receive data on socket (blocks) |\n| 50 | `fork` | `() → i32` | COW fork; 0 in child, child PID in parent |\n| 51 | `exec` | `(path, len) → i32` | Replace process image with ELF |\n| 52 | `waitpid` | `(pid, status_ptr, opts) → i32` | Wait for child exit, reap zombie |\n| 53 | `getpid` | `() → i32` | Current process PID |\n| 54 | `getppid` | `() → i32` | Parent process PID |\n| 55 | `chdir` | `(path, len) → i32` | Change working directory |\n| 56 | `getcwd` | `(buf, max) → isize` | Copy cwd to user buffer |\n| 57 | `dup` | `(oldfd) → i32` | Duplicate fd to lowest free slot |\n| 58 | `dup2` | `(oldfd, newfd) → i32` | Duplicate fd to specific slot |\n\n---\n\n## Virtual File System\n\nThe VFS is built around the `Vnode` trait — every file-system object implements it:\n\n```rust\npub trait Vnode: Send + Sync {\n    fn stat(\u0026self) -\u003e Stat;\n    fn read(\u0026self, offset: usize, buf: \u0026mut [u8]) -\u003e Result\u003cusize, \u0026'static str\u003e;\n    fn write_at(\u0026self, offset: usize, buf: \u0026[u8]) -\u003e Result\u003cusize, \u0026'static str\u003e;\n    fn lookup(\u0026self, name: \u0026str) -\u003e Result\u003cArc\u003cdyn Vnode\u003e, \u0026'static str\u003e;\n    fn readdir(\u0026self) -\u003e Result\u003cVec\u003cDirEntry\u003e, \u0026'static str\u003e;\n    fn create(\u0026self, name: \u0026str) -\u003e Result\u003cArc\u003cdyn Vnode\u003e, \u0026'static str\u003e;\n    fn mkdir(\u0026self, name: \u0026str) -\u003e Result\u003cArc\u003cdyn Vnode\u003e, \u0026'static str\u003e;\n    fn unlink(\u0026self, name: \u0026str) -\u003e Result\u003c(), \u0026'static str\u003e;\n    fn rename(\u0026self, old: \u0026str, new: \u0026str) -\u003e Result\u003c(), \u0026'static str\u003e;\n    fn truncate(\u0026self, size: usize) -\u003e Result\u003c(), \u0026'static str\u003e;\n}\n```\n\n### Backends\n\n| Backend | Mount point | Description |\n|---------|-------------|-------------|\n| **MemFS** | `/` | Root filesystem from initrd TAR. `RoFile` = zero-copy slice into initrd; `MemFile` = writable `Vec\u003cu8\u003e`; `MemDir` = `BTreeMap\u003cString, Arc\u003cdyn Vnode\u003e\u003e` |\n| **ProcFS** | `/proc` | Enumerates running PIDs; each exposes `/proc/\u003cpid\u003e/status` |\n| **DevFS** | `/dev` | `/dev/null` (discards reads/writes), `/dev/zero` (zero-filled reads), `/dev/tty` (UART alias) |\n| **OFS** | `/mnt` | Optional persistent on-disk filesystem on virtio-blk. Formatted automatically if the disk is blank. |\n\n---\n\n## Process Model\n\n```\n  kmain\n    └─ posix_spawn(\"/init\") ──► PID 1 (init)\n                                  ├─ fork() + exec(\"/sh\") ──► PID 2 (shell)\n                                  │    ├─ fork() + exec(\"/ls\") ──► PID 3 (ls)\n                                  │    └─ fork() + exec(\"/cat\") ──► PID 4 (cat)\n                                  └─ waitpid(-1) ◄─── shell exits → respawn\n```\n\n**Lifecycle states:** `Running → Stopped (waitpid block) → Zombie(status) → reaped`\n\n**Scheduler:** priority-based `BTreeMap\u003cu8, VecDeque\u003cPid\u003e\u003e`. `schedule()` pops from the lowest-numbered (highest-priority) non-empty bucket, switches SATP, calls `return_to_user`. Idles with `wfi` when all buckets are empty.\n\nPriority levels: `PRIO_REALTIME=0`, `PRIO_HIGH=8`, `PRIO_NORMAL=16`, `PRIO_LOW=24`, `PRIO_IDLE=31`. Boot servers (pm-server, vfs-server, rs-server, …) run at `PRIO_HIGH`; all user processes — including driver children of `PRIO_HIGH` servers — always start at `PRIO_NORMAL`, preventing priority inheritance from starving foreground processes.\n\n**Preemption:** The SBI timer fires every 10 ms. The ISR drains the UART FIFO (so characters are never dropped regardless of what the current process is doing), then re-queues the current PID into `RUN_QUEUE` at the process's stored priority. The next call to `schedule()` switches to the highest-priority runnable process.\n\n**COW Fork:**\n\n```\nparent: [PTE_R|V → page A]    fork()    child: [PTE_R|V → page A]\n                               ──────►\nBoth writable PTEs cleared; page A refcount = 2.\nFirst write in either process → fault → allocate page B, copy A→B, refcount(A)--\n```\n\n---\n\n## Security Model\n\nopenv uses standard Unix Discretionary Access Control:\n\n- **9-bit mode** (`rwxr-xr-x`) stored per-vnode\n- **check_access()** — compares process `euid`/`egid` against vnode `uid`/`gid`/`mode`\n- **Root bypass** — `euid == 0` skips all permission checks\n- **setuid-bit** — `exec` sets `euid = file.uid` when `mode \u0026 0o4000`\n- **sudo group** (GID 27) — members can call `sys_authenticate` and `sys_setuid(0)` to become root\n- **Passwords** — FNV-1a 64-bit hashes (demo quality; replace with a proper KDF before network exposure)\n\nDefault users:\n\n| Username | UID | GID | Password | Sudo? |\n|----------|-----|-----|----------|-------|\n| root | 0 | 0 | `root` | yes |\n| guest | 1000 | 1000 | `guest` | yes |\n\n---\n\n## TTY / Line Discipline\n\nThe UART terminal supports two modes controlled by `sys_set_raw`:\n\n**Cooked mode (default)** — Characters are buffered in `LINE_DISC_BUFFER` until Enter:\n- Regular characters → echo + append to buffer\n- `BS` / `DEL` → visual erase + pop from buffer\n- `Ctrl-C` (`0x03`) → kill foreground process (or deliver EOF to blocked read if no fg process)\n- `Enter` (`\\r`/`\\n`) → echo `\\n`, deliver full line to `read()` caller\n\n**Raw mode** — Every character returned immediately without buffering or echo. Used by the shell for arrow-key history navigation. Characters are accumulated in `LINE_DISC_BUFFER` and consumed one-at-a-time by `sys_read`.\n\n---\n\n## Networking\n\nThe networking architecture keeps the kernel thin:\n\n```\nuser process          net-smoltcp daemon         kernel\n    │                       │                      │\n    │  sys_socket()         │                      │\n    ├──────────────────────────────────────────────►│ creates channel pair\n    │◄── fd (user end) ─────────────────────────────┤ queues daemon end\n    │                       │  sys_daemon_next_socket()\n    │                       ├─────────────────────►│\n    │                       │◄── daemon_fd ─────────┤\n    │                       │                      │\n    │  sys_bind(fd, addr)   │                      │\n    ├──────────────────────►│ BIND opcode via channel\n    │  sys_listen(fd, n)    │  smoltcp.listen(addr)\n    ├──────────────────────►│\n    │  sys_accept(fd)       │  sys_daemon_create_conn(sid)\n    │  [blocks]             ├─────────────────────►│ delivers new fd to user\n    │◄── new_conn_fd ────── ◄─ wake user ──────────┤\n```\n\nRaw Ethernet frames flow via `sys_net_send` / `sys_net_recv`. smoltcp handles ARP, IP, TCP/UDP entirely in userspace.\n\n---\n\n## Debugging\n\n**GDB:**\n\n```console\nmake debug\n# In another terminal:\nriscv64-unknown-elf-gdb target/riscv64gc-unknown-none-elf/debug/openv\n(gdb) target remote :1234\n(gdb) b rust_trap_handler\n(gdb) continue\n```\n\n**Raw UART prints** (work before heap/VMM init):\n\n```rust\nuse crate::uart::Uart;\nlet mut u = Uart::new();\nu.put_char(b'!');\n```\n\n**Panic handler** prints file + line via UART and halts all harts.\n\n---\n\n## CI / CD\n\nGitHub Actions runs on every push and pull request to `main`/`master`:\n\n```\nlint (kernel) ──┐\n                ├─ [both pass] ──► build ──► artifacts\nlint (user)  ──┘\n                                    └─► docs ──► GitHub Pages\n```\n\n**`lint` job** (parallel matrix — kernel + userspace):\n- `cargo check` — type errors, missing modules\n- `cargo clippy -D warnings` — all warnings are errors (style lints selectively suppressed via `-A`)\n\n**`build` job** (depends on lint):\n- Builds debug + release kernel and all userspace binaries\n- Packages `test_root.tar` initrd\n- Reports binary sizes in the GitHub Step Summary via `llvm-size`\n- Uploads artifacts: `openv-kernel-debug`, `openv-kernel-release`, `initrd`\n\n**`docs` job** (depends on build, runs on push and PR):\n- Builds rustdocs with `--document-private-items` and the ayu theme\n- Deploys to GitHub Pages (on push only)\n\n**Release bundle** (push to `main`/`master` only):\n- `openv-\u003cbranch\u003e-\u003csha\u003e/` — kernel + initrd + `BUILD_INFO.txt`, retained 90 days\n\n---\n\n## Current Status\n\n### Working\n- Boot on QEMU `virt` (single-hart and multi-hart `-smp N`)\n- UART output, `println!`/`print!` macros\n- Physical memory management (free-list + per-page u16 refcounts)\n- Sv39 page tables, kernel identity map\n- Demand paging (zero-fill on first access)\n- COW fork + store-fault page copy\n- Timer interrupts (10 ms), preemptive re-queuing, idle WFI\n- Process creation, ELF loading, exec, fork, waitpid, zombie reaping\n- Round-robin scheduler\n- Initrd (UStar tar) → MemFS; ProcFS at `/proc`; DevFS at `/dev`\n- VFS: create, mkdir, unlink, rename, readdir, mount table\n- Optional persistent OFS filesystem on virtio-blk (auto-format on blank disk)\n- File descriptors, dup/dup2, pipes with EOF detection\n- IPC channels and bidirectional messaging\n- UART line discipline: cooked + raw mode, echo, Ctrl-C SIGINT\n- Interactive shell: line editing, arrow-key history, pipelines, I/O redirection\n- User/group database, password authentication, sudo\n- Virtio-net driver + userspace smoltcp TCP/IP (net-smoltcp daemon)\n- SMP startup (secondary harts park on `SMP_GO` flag → wake → scheduler)\n- setuid/setgid bits, Unix DAC permission checks\n- `set_fg_pid` syscall for correct Ctrl-C delivery to foreground child processes\n\n### Planned\n- Full signal subsystem (sigaction, sigprocmask, sigreturn trampoline)\n- Job control (process groups, foreground/background, tcsetpgrp)\n- `mmap` / `munmap`\n- Hard preemption (context switch inside the timer ISR, not at next syscall)\n- Porting a real C toolchain (musl libc)\n\n---\n\n## Known Limitations\n\n1. **Preemption is soft** — the timer ISR re-queues the current process; the actual context switch happens at the next return from user space (next syscall or next timer boundary). A tight `loop {}` (no syscalls) will not be preempted mid-instruction; use `sys_yield` or the `spin` binary to test.\n2. **Single TTY** — `LINE_DISC_BUFFER` is global, not per-session. Multiple concurrent login sessions on separate virtual TTYs are not supported.\n3. **No signal delivery** — Ctrl-C delivers either EOF to a blocked `read` or kills the foreground PID directly. There is no `sigaction`/`kill`/`sigprocmask` ABI.\n4. **Password hashing** — FNV-1a 64-bit is used for demo purposes. Do not expose the network interface without replacing it with Argon2 or bcrypt.\n5. **OFS has no fsync** — writes to the virtio-blk OFS filesystem are immediately issued but there is no explicit flush/sync syscall.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulodhiambo%2Fopenv","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaulodhiambo%2Fopenv","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulodhiambo%2Fopenv/lists"}