TinyCoreOS

Applies to version: TinyCoreOS Pre-Alpha 5 2.0

A graphical operating system built from scratch in C++20 and x86-64 assembly.
Current milestone: M44 - Preferences System
Next: M45 - Shell Window (CLI)
Prebuilt images for VMWare: https://tinycoreos.com/build/.

Video: Development trailers and demo clips.

Trailers: YouTube Trailer 1 | YouTube Trailer 2 | YouTube Trailer 3

About me: Adrian Pucek. Contact: [enable JavaScript]

Index

Build and run

Daily usage

Reference

First steps (5 minutes)

After make run and boot/login in TinyCoreOS:

help
ipconfig
resolve github.com
ls /disk
date
time
nc

Tip: if DNS/network is not up yet, run netinit and retry resolve.


Latest CoreDesk updates

Latest CoreShell updates


Milestones

ID Name Status Key deliverables
M0 Build Environment ✅ Done MSYS2 + x86_64-elf toolchain, Limine, QEMU
M1 Bootloader ✅ Done 32-bit → 64-bit long mode, GDT, paging, SSE
M2 Kernel Basics ✅ Done IDT, PIC, PIT (100 Hz), PS/2 keyboard, VGA console
M3 Memory Manager ✅ Done PMM (bitmap + alloc_n_frames), kernel heap, operator new/delete
M4 Virtual Memory + kstd ✅ Done VMM (4-level page tables), #PF handler, kstd::Vector<T>
M5 Preemptive Scheduler ✅ Done Kernel threads, round-robin, switch_context in ASM
M6 User Space + Syscalls ✅ Done Ring 3, TSS, SYSCALL/SYSRET, user-space task
M7a VFS + RamFS ✅ Done VFS abstraction layer, RamFS backend, 8 syscalls, FdTable per task
M7b ATA driver + TinyFS ✅ Done ATA PIO driver, custom TinyFS, dual-disk support, CoreShell first boot
M8 Graphical Framebuffer ✅ Done VESA/VBE framebuffer, embedded font, desktop chrome, dual-mode console
M8+ Extended Shell & Services ✅ Done TCP stack, HTTP/1.1 server (v2), background jobs, startup scripts, security P1-P4
M9 CodeDesk (Window Manager) ✅ Done PS/2 mouse, double buffering, overlapping windows, compositor, drag/focus/resize
M10 ELF Loader + Process Isolation ✅ Done ELF64 loader, 2MiB→4KiB page splitting, exec shell command, Linux ABI syscalls
M11 POSIX File I/O Syscalls ✅ Done open/close/read/write/stat/lseek/getdents64, openat, linux ABI structs
M12 Process Environment ✅ Done per-task CWD, getcwd/chdir, uname, readlink, openat with relative paths
M13 Network Syscalls ✅ Done socket/bind/listen/accept/connect/sendto/recvfrom, active TCP client, UDP
M14 Threading ✅ Done clone(56) creates a thread with its own stack, futex(202) FUTEX_WAIT/WAKE, TLS via FS_BASE (CLONE_SETTLS)
M15 Signals ✅ Done rt_sigaction(13), rt_sigprocmask(14), rt_sigreturn(15), kill(62), sigframe + trampoline
M16 I/O Multiplexing ✅ Done select(23), poll(7), pipe(22)/pipe2(293), O_NONBLOCK, fcntl F_GETFL/F_SETFL
M17 Device Nodes ✅ Done /dev/null, /dev/zero, /dev/tty, /dev/fb0 (mmap), /proc/meminfo
M18 First Real Application ✅ Done Graceful ring-3 #PF/#UD (task kill instead of panic); shell PATH search; user-space ELF framework
M19 CodeDesk Syscall Bridge ✅ Done SYS_WM_CREATE/DESTROY/MAP_FB/FLUSH/POLL_EVENT/SET_TITLE (500-505); PMM-backed window pixel buffers; libtwm.h header-only API
M20 Graphical File Manager ✅ Done musl ELF: folder browser in a CodeDesk window, mouse + keyboard navigation, icons, scrollbar
M21 CoreShell & F-key UI ✅ Done Dynamic F-key bar (Files/Net/Monitor sub-menus), kill, copy, CPU idle hinting, CodeDesk dirty flag, embedded README help
M22 Userspace Runtime Fixes ✅ Done Relative-path CWD resolution for all path syscalls, LinuxDirent64 packed ABI fix, FSGSBASE CPUID guard, syscall arg ABI fix, compositor yield, CWD inheritance
M23 TTF Font System ✅ Done stb_truetype renderer, antialiased glyph cache, runtime font switching (font command), fonts/ directory, make fonts-to-fat
M24 Process Management ✅ Done Per-process CR3 (isolated address spaces), fork(), execve(), waitpid(), getppid, setpgid/getpgid, setsid/getsid, per-task brk/mmap arenas
M25 TTY termios Layer ✅ Done struct Termios (Linux ABI), ioctl dispatch: TCGETS/TCSETS/TCSETSW/TCSETSF, TIOCGWINSZ/TIOCSWINSZ, TIOCGPGRP/TIOCSPGRP; canonical + raw mode reads; isatty() returns 1
M26 File System Group ✅ Done Real symlinks (S_IFLNK) + hard links in VFS/RamFS with resolve_no_follow(); extended /proc (cpuinfo, version, uptime, stat, self/status, /proc/self → symlink); /sys skeleton; /run; SYS_SYMLINK/LINK/READLINK; SYS_FLOCK/MOUNT/UMOUNT2; inotify stubs; SYS_FCNTL F_GETLK/SETLK
M27 Missing Syscalls ✅ Done nanosleep (yield-based), mremap (grow-in-place), clock_gettime (real PIT ticks), file-backed mmap, setuid/setgid/setfsuid (VFS uid update), getrusage (zero struct), times (returns ticks), getresuid/getresgid
M28 Init & User-space ✅ Done PID 1 init task (zombie-reaper + orphan adoption via exit_current), full FHS layout (/etc /home /lib /root /usr /var), Linux-format /etc/passwd /etc/shadow /etc/group /etc/hostname /etc/hosts /etc/os-release, make busybox target
M29 Hardware Drivers ✅ Done AHCI/SATA (DMA, PCI BAR5 MMIO, IDENTIFY, LBA48), NVMe (Admin + I/O queues, Identify NS), LAPIC + I/O APIC (ACPI MADT), USB HID boot-protocol keyboard, blkdev expanded to 16 devices
M30 Network & TCP/IP ✅ Done DHCP client (DISCOVER→OFFER→REQUEST→ACK), software loopback (127.0.0.1), /etc/hosts lookup before DNS, AF_UNIX domain sockets (bind/connect/accept + ring-buffer pairs), dhcp shell command, netinit auto-DHCP
M31 Shell I/O Plumbing ✅ Done dup/dup2/dup3 + File::refcount, pread64/pwrite64, truncate/ftruncate (ramfs grow/shrink), rename/renameat/renameat2, getpgrp, FD_MAX→64, stdin/stdout respect dup2 redirections
M32 PTY ✅ Done /dev/ptmx (open allocates master), /dev/pts/N (slave), bidirectional 4 KiB ring buffers, N_TTY line discipline (canonical/raw, echo, ICRNL, ONLCR), TIOCGPTN/TIOCSPTLCK/TIOCSCTTY ioctls, TCGETS/TCSETS per-PTY termios
M33 Busybox on Disk ✅ Done Full argc/argv/envp in ELF loader (Linux ABI stack layout); SYS_EXECVE parses user-space argv[]; bb-install (TFTP + chmod 755 + ~60 symlinks); PATH extended; argv[0] override for busybox dispatch; interactive sh prompt: TIOCGPGRP returns caller pgid, SIGTTIN/SIGTTOU ignored, fd 0/1/2 pre-installed as /dev/tty, F_DUPFD_CLOEXEC respects min-fd; make run guards build/busybox; Python no longer required; fork fix enter_userspace_fork restores RBX/RBP/R12-R15; writev(20) + readv(19) + sched_yield(24); ANSI escape parser + \r in console; getdents64 debug removed
M34 CoreShell Enhancements ✅ Done Stream redirection > / >> (console hook, VFS write); pipe operator \| (multi-stage, double-buffer, grep/cat/tail pipe-aware); built-in grep (-i -n -r -l); kernel logging (klog); env-var help; help coloring
M35 CoreShell CLI Completeness ✅ Done Built-in rm [-r][-f], rmdir, touch, echo [-n], mv (alias rename), cp (alias copy), ln [-s] (alias link/symlink), uptime, source/., alias/unalias; alias expansion in dispatch
M36 SSH Client & Server ✅ Done sshd (port 22, worker pool via tcp_srv, up to 5 concurrent sessions) + ssh [user@]host [cmd] incl. ssh localhost; crypto: curve25519-sha256 KEX, chacha20-poly1305@openssh.com cipher, ssh-ed25519 host key (standard Ed25519 with SHA-512 via Monocypher building blocks + own SHA-256/SHA-512); password auth aligned with passwd/shadow behavior
M37 AC97 Audio ✅ Done Intel ICH AC97 PCI driver; DMA ring buffer (32 × 4 KiB, 128 KiB); /dev/dsp; beep [freq] [ms], play <wav/pcm>, volume [0-100]; -device AC97 QEMU flag
M38 NC File Manager ✅ Done Not Commander dual-panel text file manager (nc): copy/move/rename/mkdir/delete (recursive + marked), sort by name/date/size (s key), Ctrl+O shell-suspend/resume mini-shell, Shortcuts menu (This PC / User / Desktop / Docs / Music / Downloads / Pictures / Videos / Root), FTP client (ftpc), Insert=mark, Del=delete, soft cursor in input dialogs, F2 refresh, bare-name rename-in-place, exit sets shell CWD, pre-login F-key bar hidden
M39 Widget Toolkit (Intuition) ✅ Done libui widgets in production apps: buttons, labels, text/input patterns, list/grid views, dialog-style layouts used across CoreDesk/Control Panel
M40 Screen & Palette System ✅ Done Backdrop/wallpaper handling, palette reload, runtime cursor switching (arrow/wait), runtime mode switch (twm_set_mode) from Control Panel
M41 Icon System ✅ Done Themed desktop icons loaded from disk (PNG/BMP), category/fallback resolution, transparent icon rendering, robust icon metadata parsing
M42 Workbench Desktop ✅ Done CoreDesk desktop with icon grid, drawer launching, taskbar/sysbar stats, context menus, My stuff dynamic submenu, minimized redraw path
M43 Menu System ✅ Done Global top screen menu with multi-level submenus (up to deep nesting), disabled/check/radio items, Win/Ctrl+Esc activation, mouse+keyboard navigation
M44 Preferences System ✅ Done Unified in-window Control Panel applets (ScreenMode/Palette/Font/Sound/Input/Time/Startup), persistent prefs in /disk/disk0/prefs/, Default Apps mapping UI
M45 Shell Window (CLI) 🔲 Plan PTY terminal window, scrollback, mouse copy/paste, dynamic resizing
M46 Dock / Application Bar 🔲 Plan Application dock (AmiDock), icons with tooltips, auto-hide
M47 Multimedia Viewer 🔲 Plan MultiView (PNG/BMP/WAV), stb_image, zoom, waveform audio preview
M48 GUI Text Editor 🔲 Plan Syntax highlighting, undo/redo, Find & Replace, tabs
M49 Visual Theming 🔲 Plan Window skins (Classic/AmigaOS4/Dark), animations, wallpapers, .theme format
M50 Notifications & Clipboard 🔲 Plan Notification banner, system clipboard, SYS_CLIPBOARD_COPY/PASTE

Prerequisites

Tool Version Purpose
MSYS2 latest Shell environment on Windows
x86_64-elf GCC toolchain 13.x Freestanding cross-compiler
NASM 2.x Assembler
QEMU latest Emulator (qemu-system-x86_64)
xorriso latest ISO creation
Git 2.x Fetching Limine bootloader

The toolchain is installed natively under MSYS2 - no Docker required.
See msys2-env.sh for the one-time setup script.


Quick start

# 1. Enter MSYS2 and load the toolchain path (once per session)
source ./msys2-env.sh

# 2. Download Limine bootloader binaries (once)
make limine-get

# 3. Download Monocypher crypto library (once, required for SSH)
make monocypher-get

# 4. Build kernel ELF + bootable ISO
make

# 5. Run in QEMU  (disk images created automatically)
make run

TinyCoreOS works in VMware Workstation when the VM uses devices covered by current drivers.

Notes:
- VMXNET3 is visible in lspci, but not yet supported by the network stack.
- Legacy SCSI controllers (BusLogic / LSI Logic Parallel) are not supported yet - use SATA/NVMe.
- VMware AHCI may expose many implemented-but-empty SATA ports; TinyCoreOS now suppresses those empty-port lines in boot output.

VMware debug logging setup (qemu-debug equivalent)

Use a virtual serial port mapped to a host file. This gives you a persistent boot/runtime log similar to QEMU -serial.

  1. Power off the VM.
  2. Open VM Settings -> Add... -> Serial Port.
  3. Select Output to file and choose a host path, e.g. C:\temp\tinycoreos-vmware-serial.log.
  4. Enable Connect at power on.
  5. Boot the VM and inspect the log file on the host.

Notes:
- TinyCoreOS writes boot/kernel logs to COM1, so this captures early boot too.
- VMware also writes hypervisor/device diagnostics to vmware.log in the VM directory.
- If the serial log file stays empty, verify COM1 is present and connected.

PowerShell live tail (host):

Get-Content "C:\temp\tinycoreos-vmware-serial.log" -Wait

Debug targets

make debug          # QEMU paused, GDB on port 1234
make debug-boot     # QEMU with -no-reboot -d int,cpu_reset
make serial         # QEMU output to stdout, no window

Supported drivers

TinyCoreOS currently targets virtualized hardware profiles (QEMU / VirtualBox / VMware Workstation).

Network

Not supported yet:
- VMware VMXNET3 (15ad:07b0)
- VirtIO-Net

Storage

Not supported yet:
- Legacy SCSI VM controllers (BusLogic / LSI Parallel)
- VirtIO-Blk / VirtIO-SCSI drivers

Audio

Display and input

PCI ID -> driver module

PCI ID Device (short) Driver module
8086:100e Intel 82540EM (E1000) e1000
8086:100f Intel 82545EM (E1000) e1000
8086:10d3 Intel 82574L (E1000E profile) e1000
1022:2000 AMD PCnet-FAST III pcnet
1022:2001 AMD PCnet (variant) pcnet
8086:2922 Intel ICH9 AHCI SATA ahci
8086:2415 Intel ICH AC97 Audio ac97
8086:2668 Intel HD Audio (ICH6/ICH9) hda
15ad:07b0 VMware VMXNET3 not supported
15ad:07c0 VMware PVSCSI not supported
104b:1040 BusLogic SCSI not supported
1000:0030 LSI Logic Parallel SCSI not supported

SMP diagnostics (dev)

When smpap is enabled in iso/limine.cfg, use these runtime checks:

cat /proc/smp_sched
smpkick 5
smpkick ap 1 5
cat /proc/smp_sched

Interpretation:
- resched_ipi_sent should increase after smpkick.
- resched_ipi_hits / resched_ipi_hits_cpuN confirm IPI delivery/handling per CPU.
- schedule_attempts / schedule_switches track scheduler activity under SMP tests.
- syscall_bkl_taken / syscall_bkl_bypassed show how much syscall traffic still goes
through the global BKL versus lock-free whitelist paths.
- syscall_bkl_taken_* highlights the hottest still-locked syscall families
(read, ioctl, futex, openat, close, wm_poll_event, wm_flush,
write, wm_create/destroy/map_fb, clipboard, readv/writev,
wm_get_size/wm_set_cursor/wm_move, file metadata paths (open/stat/fstat/lstat/getdents/access),
plus memory/time/path buckets (brk/mmap/munmap/clock_gettime/getcwd),
and additional scheduler/signal-fd buckets (poll/select/fcntl/rt_sigprocmask/set_tid_addr/waitpid),
plus process/UI text buckets (execve/clone/fork/kill/wm_set_title/font_draw_str/font_draw_to/shellcore_exec),
plus fd/plumbing buckets (lseek/pread64/pwrite64/dup/dup2/dup3/pipe/pipe2),
plus socket/net buckets (socket/connect/accept/accept4/sendto/recvfrom/bind/listen/shutdown
and net_dns_resolve/net_tcp_connect/net_tcp_connected/net_tcp_send/net_tcp_recv/net_tcp_close/net_poll),
plus WM/fclip extension buckets (wm_set_style/wm_set_desktop/wm_get_desktop/wm_win_desktop/wm_win_sticky
wm_set_mode/wm_set_cursor_bmp/wm_cursor_frame/fclip_begin/fclip_add/fclip_count/fclip_op/fclip_get/fclip_clear/fclip_gen),
plus WM menu/popup buckets (wm_menu_set/wm_menu_poll/wm_popup_show/wm_popup_poll/wm_get_focused/wm_raise),
plus WM/control buckets (wm_list_windows/power_off/reboot),
and other).
- syscall_bkl_taken_read_stdin / syscall_bkl_taken_read_fd and
syscall_bkl_taken_write_console / syscall_bkl_taken_write_fd split
read/write into fallback TTY/console paths versus fd-backed file paths.
- syscall_bkl_taken_waitpid_nohang / syscall_bkl_taken_waitpid_blocking
split wait family pressure into polling (WNOHANG) versus blocking waits.
- syscall_bkl_taken_mmap_anon / syscall_bkl_taken_mmap_file /
syscall_bkl_taken_mmap_fb0 split mmap pressure into anonymous, generic
file-backed, and framebuffer (/dev/fb0) mappings.
- syscall_bypass_* fields split lock-free traffic by syscall family
(sched_yield, nanosleep, socket name queries, kill(0,0), plus hot read-only
paths: getpid, gettid, getppid, getpgrp, getuid, geteuid,
get_ticks, brk, mmap(MAP_ANONYMOUS), waitpid(WNOHANG), fd-backed read/write, access, close, open (safe read-only subset),
wm_get_screen_size, wm_set_cursor, wm_set_style, wm_win_sticky, wm_get_focused, wm_menu_poll, wm_popup_poll, wm_list_windows,
wm_flush, wm_poll_event,
getdents,
font_measure, font_draw_to, clipboard_count, clipboard_gen, and other).

Current SMP checkpoint (incremental, stable):
- AP bring-up (smpap) is stable for 2 CPU QEMU runs.
- Reschedule IPI (smpkick) is wired and observable via /proc/smp_sched.
- Syscall path uses a conservative, argument-gated BKL bypass policy.
- sched_yield(24) and nanosleep(35) are now lock-free in syscall entry
(scheduler still applies its own SMP safety path internally).
- wm_poll_event(504) is now lock-free at syscall entry, backed by dedicated
WM internal locking for wid/event-ring critical sections.
- WM bridge now has dedicated internal locking for wid-table and event-ring
critical sections (prep step for safely reducing BKL pressure on WM poll path).
- FdTable operations (alloc/get/release/replace) are now internally
protected by a per-table spinlock as groundwork for future VFS-path BKL reductions.
- vfs::File now includes a dedicated per-file spinlock and atomic refcount
helpers, with vfs::read/write/readdir/ftruncate serialized per open-file instance.
- vfs::pread/pwrite now provide positional I/O with lock-scoped offset restore,
and mmap file-backed copying uses the same per-file lock domain.
- read(0) keyboard fallback and write(1/2) direct-console fallback remain under
BKL, but fd-backed read/write paths now bypass BKL and are tracked via
syscall_bypass_read_fd / syscall_bypass_write_fd.
- waitpid(61) now bypasses BKL for WNOHANG probes only; blocking waits stay
under BKL and telemetry exposes both sides via
syscall_bkl_taken_waitpid_nohang / syscall_bkl_taken_waitpid_blocking
and syscall_bypass_waitpid_nohang.
- wm_set_cursor(520) now bypasses BKL and is tracked via
syscall_bypass_wm_set_cursor; cursor mode updates use atomic loads/stores
in WM to avoid races with compositor-side hover updates.
- wm_set_style(530) and wm_win_sticky(534) now bypass BKL, backed by
existing wid-table locking in WM and tracked via
syscall_bypass_wm_set_style / syscall_bypass_wm_win_sticky.
- brk(12) now bypasses BKL; heap growth still synchronizes through PMM/VMM
internal locks, and lock-free traffic is tracked via syscall_bypass_brk.
- mmap(9) now bypasses BKL for MAP_ANONYMOUS mappings; file-backed mmap
paths remain under BKL and are split into mmap_file / mmap_fb0 telemetry.
- A dedicated VFS gate lock now protects syscall-level VFS metadata paths
(open/close/stat/lstat/fstat/access/getdents/openat) and enables lock-free
getdents dispatch without relying on the global BKL.
- close(3) now runs lock-free at syscall entry, guarded by the same VFS gate
+ per-task FdTable locking.
- open(2) now bypasses BKL only for a safe subset (O_RDONLY without
O_CREAT/O_TRUNC), keeping mutating opens under BKL.
- New telemetry counters help guide the next whitelist steps with runtime data.
- syscall_bkl_taken_other is now partially decomposed into dedicated buckets
(brk, mmap, munmap, clock_gettime, getcwd, wm_move) to narrow the
next lock-free candidate with less guesswork.
- Decomposition was extended with poll/select/fcntl/rt_sigprocmask/set_tid_addr/waitpid
to identify high-frequency non-VFS pressure still hidden under other.
- Decomposition was further extended with execve/clone/fork/kill/wm_set_title/font_draw_str/font_draw_to/shellcore_exec
to expose process-launch/UI-rendering overhead currently folded into other.
- Decomposition now also covers lseek/pread64/pwrite64/dup/dup2/dup3/pipe/pipe2
to isolate file-offset/descriptor plumbing pressure from residual other.
- Decomposition now also covers socket/net syscall families
(socket/connect/accept/accept4/sendto/recvfrom/bind/listen/shutdown
and net_dns_resolve/net_tcp_connect/net_tcp_connected/net_tcp_send/net_tcp_recv/net_tcp_close/net_poll)
to isolate network-path pressure from residual other.
- Decomposition now also covers WM extension (530-537) and file-clipboard (550-556)
syscall families to isolate desktop-control/file-clipboard pressure from residual other.
- Clipboard history/file-clipboard paths now use dedicated internal locking in WM,
enabling safe lock-free bypass for read-only clipboard_count/clipboard_gen.
- font_draw_to(516) now bypasses BKL (pure draw into caller-provided pixel
buffer), with dedicated syscall_bypass_font_draw_to telemetry.
- wm_get_focused(542) now bypasses BKL (read-only focused-window query),
backed by WM wid-table locking and dedicated syscall_bypass_wm_get_focused
telemetry.
- wm_menu_poll(539) and wm_popup_poll(541) now bypass BKL; their pending-id
handoff paths use atomic set/poll in WM to avoid races with compositor updates.
- wm_list_windows(546) now bypasses BKL, backed by wid-table locking in
wm::list_windows_wid() and dedicated syscall_bypass_wm_list_windows telemetry.
- Decomposition now also covers WM menu/popup extension syscall families
(wm_menu_set/wm_menu_poll/wm_popup_show/wm_popup_poll/wm_get_focused/wm_raise)
to isolate remaining desktop-control pressure from residual other.
- Decomposition also tracks wm_list_windows(546) and power control syscalls
(power_off(544), reboot(545)) to reduce residual other in desktop shell runs.


Project layout

TinyCoreOS/
├── iso/
│   └── limine.cfg              # Limine bootloader config (Multiboot2)
├── src/
│   ├── kernel/
│   │   ├── boot.asm            # 32→64-bit long mode, SSE, identity-map 0-1 GiB + 3-4 GiB
│   │   ├── linker.ld           # Kernel linked at 1 MiB, exports _kernel_end
│   │   ├── main.cpp            # kernel_main: full init sequence + idle loop
│   │   ├── console.{hpp,cpp}   # Dual-mode console: VGA text (fallback) + framebuffer
│   │   ├── panic.{hpp,cpp}     # kernel::panic + PANIC / ASSERT macros
│   │   ├── new.cpp             # operator new/delete -> heap::alloc/free
│   │   ├── multiboot2.h        # Multiboot2 tag structures (incl. framebuffer tag)
│   │   ├── io.hpp              # inb / outb port I/O
│   │   ├── gdt/                # 64-bit GDT + TSS descriptor, ltr
│   │   ├── idt/                # IDT, isr_stubs.asm, interrupt dispatch
│   │   ├── pic/                # 8259A PIC remap (IRQ0-15 -> vec 32-47)
│   │   ├── vfs/
│   │   │   ├── vfs.{hpp,cpp}   # VFS abstraction (Inode+uid, File, Superblock, FdTable, chmod/chown/rename)
│   │   │   └── ramfs.{hpp,cpp} # RamFS backend (heap-linked lists, setattr)
│   │   ├── fs/
│   │   │   ├── tinyfs.{hpp,cpp}# TinyFS v4: 4KiB-block FS, dynamic layout, file-level dedupe, double indirect, mtime, version
│   │   │   └── fat32.{hpp,cpp} # FAT32 driver (LFN, mtime, file version counter via nt_res)
│   │   ├── syscall/
│   │   │   ├── syscall.{hpp,cpp}      # MSR init, syscall_dispatch (SYS_EXIT..SYS_STAT)
│   │   │   └── syscall_entry.asm      # SYSCALL/SYSRET entry point + enter_userspace
│   │   ├── userspace/
│   │   │   └── user_init.asm          # Demo ring-3 program (position-independent)
│   │   ├── readme_data.asm            # Embeds README.md as .rodata blob → /tmp/readme.md at boot
│   │   ├── drivers/
│   │   │   ├── ata.{hpp,cpp}   # ATA PIO driver (28-bit LBA, primary bus, polling, 2 drives)
│   │   │   ├── fb.{hpp,cpp}    # Linear framebuffer driver (put_pixel, fill_rect, scroll_region_up)
│   │   │   ├── timer.{hpp,cpp} # PIT 100 Hz, ticks(), calls sched::tick
│   │   │   ├── keyboard.{hpp,cpp} # PS/2 scancode -> ASCII, Page Up/Down, arrow keys, Tab, F-keys
│   │   │   └── ac97.{hpp,cpp}  # Intel ICH AC97 audio: PCI scan, DMA ring, /dev/dsp, beep/play/volume
│   │   ├── gfx/
│   │   │   ├── font.{hpp,cpp}  # Embedded 8×8→8×16 bitmap font (ASCII 0x20-0x7E)
│   │   │   ├── canvas.{hpp,cpp}# Desktop chrome: title bar, live sysinfo, dynamic F-key bar (set_fkey_bar/reset_fkey_bar)
│   │   │   └── wm.{hpp,cpp}    # Window manager: compositor, drag/focus/resize, wid-based syscall API, dirty-flag rendering
│   │   ├── net/
│   │   │   ├── e1000.{hpp,cpp} # Intel 82540EM NIC driver (MMIO, TX/RX descriptor rings, polling)
│   │   │   ├── net.{hpp,cpp}   # Network stack: Ethernet II, ARP, IPv4, ICMP, UDP, DNS, TCP
│   │   │   ├── dns.{hpp,cpp}   # DNS A-record client (UDP port 53)
│   │   │   ├── httpd.{hpp,cpp} # HTTP/1.1 server (v2; static files, dir listing, runs as bg task)
│   │   │   ├── ftpd.{hpp,cpp}  # FTP server (plaintext, PASV, ACL + auth backends)
│   │   │   └── ssh.{hpp,cpp}   # SSH2 server + client (curve25519-sha256 + chacha20-poly1305 + ed25519)
│   │   ├── shell/
│   │   │   └── shell.{hpp,cpp} # Interactive kernel shell (70+ commands, history, tab completion,
│   │   │                       #   background jobs, startup scripts, users/groups, chmod/chown,
│   │   │                       #   aliases, stream redirection, pipes, NC file manager, dynamic F-key bar)
│   │   ├── zip/
│   │   │   └── zip.{hpp,cpp}   # ZIP reader/writer (Store + Deflate via miniz_tinfl)
│   │   ├── mm/
│   │   │   ├── pmm.{hpp,cpp}   # Physical Memory Manager (bitmap + alloc_n_frames)
│   │   │   ├── heap.{hpp,cpp}  # Kernel heap (first-fit free list, realloc, C malloc/free wrappers)
│   │   │   └── vmm.{hpp,cpp}   # Virtual Memory Manager (page-table walker, map/unmap/get_phys)
│   │   └── sched/
│   │       ├── task.hpp        # struct Task (RSP, privilege, FdTable, ring-list)
│   │       ├── scheduler.{hpp,cpp} # Round-robin, create_task/create_user_task, yield, kill_task, hint_idle
│   │       └── context.asm     # switch_context (save/restore 6 callee-saved regs)
│   ├── kstd/
│   │   ├── utility.hpp         # kstd::move, kstd::forward
│   │   ├── placement_new.hpp   # operator new(size_t, void*) for freestanding
│   │   ├── unique_ptr.hpp      # kstd::unique_ptr<T>
│   │   ├── vector.hpp          # kstd::Vector<T> - heap-backed dynamic array
│   │   └── cstring.hpp         # strlen, strcmp, strncmp, strncpy, strrchr, memcpy, memset, memmove
│   └── vendor/
│       ├── miniz/
│       │   ├── miniz_tinfl.{h,c}  # Deflate decompressor (richgel999/miniz)
│       │   ├── miniz_common.h
│       │   └── compat/         # Freestanding shim headers (stdlib, string, assert, …)
│       ├── stb/
│       │   ├── stb_truetype.h       # stb_truetype (nothings.org) — TTF rasterizer
│       │   ├── stb_truetype_impl.c  # Single-TU implementation (compiled as C)
│       │   └── compat/              # Freestanding shim headers
│       └── monocypher/
│           └── monocypher.{h,c}  # Monocypher 4.0.2 — X25519, ChaCha20, Poly1305, EdDSA (download: make monocypher-get)
├── userspace/
│   ├── libtwm.h                # Header-only user-space WM API (M19) - no libc required
│   └── filemanager.c           # Graphical file manager (M20) - musl ELF
├── tools/limine/               # Limine bootloader binaries (git clone, gitignored)
├── msys2-env.sh
├── Makefile
└── README.md

Architecture overview

Boot sequence

BIOS/UEFI
  └── Limine (Multiboot2)
        ├── reads iso/limine.cfg
        ├── loads kernel.elf into RAM
        ├── switches display → linear framebuffer (1024→768→32bpp VESA)
        └── jumps to boot.asm  [32-bit protected mode]
              ├── verify Multiboot2 magic (0x36D76289)
              ├── set up temporary GDT
              ├── enable PAE + paging
              -     ├── PDPT[0] → pd0: identity-map 0-1 GiB (2 MiB pages)
              -     └── PDPT[3] → pd3: identity-map 3-4 GiB (VESA FB BAR)
              ├── set EFER.LME → long mode (64-bit)
              ├── enable SSE (CR4.OSFXSR | CR4.OSXMMEXCPT)
              └── far-jump → kernel_main(mb2_addr)       src/kernel/main.cpp

Kernel init sequence (kernel_main - src/kernel/main.cpp)

Each step prints [OK] to the screen when it succeeds.

# Call What it does Source file
1 call_global_ctors() Run C++ static constructors (.init_array) src/kernel/main.cpp
2 console::init() VGA 80→25 text mode + COM1 serial output src/kernel/console.cpp
3 scan MB2 tags Locate framebuffer tag + detect text cmdline flag src/kernel/main.cpp
4 fb::init() Register VESA framebuffer (addr, width, height, pitch) src/kernel/drivers/fb.cpp
5 console::init_fb() Switch console rendering to pixel framebuffer src/kernel/console.cpp
6 pmm::init(mb2_addr) Parse MB2 memory map, build physical page bitmap src/kernel/mm/pmm.cpp
7 heap::init() First-fit free-list kernel heap (backs operator new) src/kernel/mm/heap.cpp
8 gdt::init() Load 64-bit GDT (null/kern/user segments) + TSS src/kernel/gdt/gdt.cpp
9 idt::init() Install 256 interrupt gates (ISRs + IRQ stubs) src/kernel/idt/idt.cpp
10 vmm::init() Register #PF handler on vector 14 src/kernel/mm/vmm.cpp
11 syscall::init() Configure EFER.SCE, STAR, LSTAR, SFMASK MSRs src/kernel/syscall/syscall.cpp
12 vfs::init() + ramfs::create_superblock() + vfs::mount_root() Mount RamFS as / src/kernel/vfs/vfs.cpp, src/kernel/vfs/ramfs.cpp
13 vfs::mkdir("/bin /dev /tmp") Create standard directories in RamFS src/kernel/vfs/vfs.cpp
14 embed README Write .rodata blob → /tmp/readme.md (from readme_data.asm) src/kernel/main.cpp, src/kernel/readme_data.asm
15 devfs::init() Create /dev/null, /dev/zero, /dev/tty, /dev/fb0, /proc/meminfo src/kernel/devfs/devfs.cpp
16 VFS self-test Write + read /tmp/hello.txt to verify RamFS src/kernel/main.cpp
17 blkdev::register_ata(0,1) Register ATA drives 0 and 1 in block device table src/kernel/drivers/blkdev.cpp
18 ata::init() IDENTIFY DEVICE on primary bus; print drive models src/kernel/drivers/ata.cpp
19 vfs::mkdir("/disk/disk0 /disk/disk1") Create mount-point stubs in RamFS src/kernel/vfs/vfs.cpp
20 VMM self-test Map page at 2 GiB, write 0xDEADBEEF, verify readback src/kernel/main.cpp
21 Vector self-test kstd::Vector<uint32_t> - 16 elements, sum = 1240 src/kernel/main.cpp
22 sched::init() Create Task 0 = kernel_main (becomes the idle task) src/kernel/sched/scheduler.cpp
23 pic::init() Remap 8259A: IRQ 0-7 → vec 32-39, IRQ 8-15 → vec 40-47 src/kernel/pic/pic.cpp
24 timer::init(100) PIT at 100 Hz → IRQ0 → sched::tick() → context switch src/kernel/drivers/timer.cpp
25 keyboard::init() PS/2 on IRQ1; scancode → ASCII ring buffer src/kernel/drivers/keyboard.cpp
26 uhci::init() Scan PCI for UHCI USB controller; set up TD/QH rings src/kernel/drivers/uhci.cpp
27 wm::init() + mouse::init() Compositor + terminal canvas; PS/2 mouse on IRQ12 src/kernel/gfx/wm.cpp, src/kernel/drivers/mouse.cpp
28 e1000::init() + net::init() Scan PCI for Intel 82540EM; init TCP/IP stack src/kernel/net/e1000.cpp, src/kernel/net/net.cpp
29 sched::create_task("compositor") Compositor task: wm::compose_and_flip() at ~30 fps src/kernel/sched/scheduler.cpp, src/kernel/gfx/wm.cpp
30 sched::create_task("shell") Spawn interactive shell with 64 KiB stack src/kernel/sched/scheduler.cpp, src/kernel/shell/shell.cpp
31 sched::create_user_task("user_init") Ring-3 demo task at 0x40000000 src/kernel/sched/scheduler.cpp, src/kernel/userspace/user_init.asm
32 STI Enable interrupts - scheduler takes over, tasks start running src/kernel/main.cpp
33 sched::create_task("usb_stage2") Start async USB stage-2 worker (wait → probe → MSC/HID detect) src/kernel/main.cpp
34 shell::boot_done = true Signal shell task that boot output is complete src/kernel/main.cpp
35 for(;;) hlt kernel_main becomes the idle task (Task 0) src/kernel/main.cpp

Shell startup (shell::run - src/kernel/shell/shell.cpp)

Runs as a separate scheduler task, waits for boot_done, then:

# Call What it does Source file
1 while (!boot_done) pause Wait until kernel_main finishes printing boot messages src/kernel/shell/shell.cpp
2 tinyfs::mount(0) Try to mount ATA drive 0 as TinyFS v4 src/kernel/fs/tinyfs.cpp
3 vfs::mount_at("/disk/disk0", sb) Attach TinyFS superblock to VFS tree src/kernel/vfs/vfs.cpp
4 create_disk_layout("/disk/disk0") Bootstrap /etc, /log, /home/root, /usr/bin; create default passwd, shadow, group, resolv.conf, httpd.conf, ftpd.conf, pci.ids; create empty auth.log, error.log, services.log, kernel.log src/kernel/shell/shell.cpp
5 run_startup_services() Read /etc/startup-services; launch each line as a background task src/kernel/shell/shell.cpp
6 startup_login() Show login: prompt; verify password (max 3 attempts → 3 s lockout); write to auth.log src/kernel/shell/shell.cpp
7 net_load_config() Parse /etc/networks; configure IP/netmask/gateway on eth0 src/kernel/shell/shell.cpp, src/kernel/net/net.cpp
8 dns_load_config() Parse /etc/resolv.conf; set DNS nameserver src/kernel/shell/shell.cpp, src/kernel/net/dns.cpp
9 tinyfs::mount(1) Silent auto-mount of ATA drive 1 (optional data disk) src/kernel/fs/tinyfs.cpp
10 hist_load() Load ~/.history for the logged-in user src/kernel/shell/shell.cpp
11 canvas::update_sysinfo() First CPU%/RAM update on title bar src/kernel/gfx/canvas.cpp
12 print_prompt() Print user@tinycore:/$ src/kernel/shell/shell.cpp
13 main loop keyboard::getchar() → dispatch_fg() → repeat; sysinfo refresh every 1 s src/kernel/shell/shell.cpp

Timing (approximate, QEMU)

  0 ms   BIOS POST
500 ms   Limine loads kernel.elf from ISO
501 ms   boot.asm: long mode, paging
502 ms   kernel_main: console → PMM → heap → GDT → IDT → VMM → syscall
503 ms   VFS/RamFS → devfs → ATA → sched → PIC → PIT → keyboard
504 ms   WM → mouse → e1000 → tasks created → STI
519 ms   async USB stage-2 task starts (150 ms settle runs in background)
520 ms   shell::run() starts, boot_done received
521 ms   disk0 mount + disk layout bootstrap
522 ms   "login: " prompt  (or immediate shell if diskless)
670 ms   USB stage-2 block printed + login prompt repainted

Graphical Framebuffer (M8)

The Limine bootloader sets up a 1024 → 768 → 32 bpp VESA framebuffer.
QEMU uses -vga std (VBE 2.0). The framebuffer physical address
(typically 0xFD000000) is in the 3-4 GiB range, identity-mapped in
boot.asm via pd3 - no runtime page-table changes needed.

Desktop layout

-───────────────────────────────────────────────────────────────────
-  TinyCoreOS  M22 - Userspace Runtime Fixes   CPU 1%  RAM 210 MiB-  → 28 px title bar (live sysinfo top-right)
├──────────────────────────────────────────────────────────────────+
-                                                                  -
-  [OK] TinyFS v4: 4 KiB blocks, up to 2 TB partition, up to 4 GB file, file-level dedupe -
-  [OK] Disk 0 (TinyFS v4) mounted at /disk/disk0                 -
-  login: root                                                     -
-  CoreShell  (type 'help' or press F1 for README)                 -
-  root@tinycore:/$ _                                              -
-                      (terminal area - Page Up/Down scrolls)      -
├──────────────────────────────────────────────────────────────────+
- F1 Readme - F2 Files - F3 Tasks - F4 Net - → - F8 Monitor - F9 Cls-  → dynamic F-key bar
└──────────────────────────────────────────────────────────────────-

Font rendering

TinyCoreOS uses stb_truetype for antialiased
TrueType font rendering (see Font System for full details).

Default font: Hack Regular (embedded in the kernel binary as a C array;
always available regardless of filesystem state).

Glyphs are pre-rasterized at boot into an 8 → 16 px grayscale alpha cache
(95 printable ASCII chars). Each pixel is alpha-blended at draw time - no
per-character floating-point cost at runtime.

Box-drawing characters (U+2500 range, encoded as 0x80-0x8A internally) use the
original 1-bit bitmap renderer as a fallback since TTF fonts do not contain them.

Dual-mode console

Feature VGA text mode Framebuffer mode
Output target 0xB8000 VGA cell buffer Linear pixel framebuffer
Scrollback VGA CRTC start-address Character buffer + fb::scroll_region_up()
Colour 4-bit VGA attribute 32-bit RGB via canvas::RGB[16]
Page Up/Down CRTC hardware scroll Full buffer redraw (200-row history)

CoreShell

Runs as a kernel task (ring 0). Features:

Dynamic F-key Bar (M21)

The bottom status bar shows function key labels that change with the active mode.
Pressing a labelled F-key either executes the command immediately or pre-fills
the command line so arguments can be added before pressing Enter.

Default mode

Key Action
F1 Open /tmp/readme.md in the built-in editor
F2 Enter Files sub-menu
F3 Reserved (Tasks, no default action)
F4 Enter Net sub-menu
F5 Enter Logs sub-menu
F6 Show command history tail
F7 Reserved (Jobs, no default action)
F8 Enter Custom sub-menu (from shell.prefs)
F9 Clear screen

Files sub-menu (entered via F2; F10 returns to default)

Key Injects / runs
F1 ls
F2 cat
F3 find .
F4 edit
F5 copy
F6 rename
F10 Back

Net sub-menu (entered via F4; F10 returns to default)

Key Injects / runs
F1 ipconfig
F2 netinit
F3 ping
F4 arp
F10 Back

Logs sub-menu (entered via F5; F10 returns to default)

Key Shows (last 30 lines via tail)
F1 /disk/disk0/log/auth.log - login / su audit trail
F2 /disk/disk0/log/error.log - error log
F3 /disk/disk0/log/services.log - service events
F4 /disk/disk0/log/kernel.log - kernel messages
F10 Back

Custom sub-menu (entered via F8; F10 returns to default)

Slots F1..F9 are loaded from /disk/disk0/prefs/shell.prefs using:

If a slot has no action, it remains visible but inactive.

Monitor sub-menu (optional / non-default mapping)

Key Injects / runs
F1 lscpu
F2 lsram
F3 lsproc
F4 lspci
F5 lseth
F6 lsusb
F7 jobs
F8 sensors
F10 Back

Filesystem commands

Command Description
pwd Print current working directory
cd [path] Change directory
ls [-a] [-c] [-u] [-s] [path] List directory (-a detailed columns incl. Created/Modified/Cks, -c sort by creation time, -u owner, -s recursive dir sizes)
mkdir <path> Create directory
rm [-r] [-f] [-v] <path\|glob ...> Remove files/dirs (-r recursive, -f force, -v verbose; wildcard support * ?; prints summary)
write <path> [text] Create / overwrite file
cat [-su] <path\|glob> [...] Print file contents (Ctrl+C to interrupt); -s sort lines, -u unique lines
tail [-n N] [-f] <path\|glob> [...] Print last N lines (default 30); wildcard support; -f follows exactly one resolved file
grep [-inrlsu] <pat> <file\|dir\|glob> [...] Search in files; -i ignore case, -n line numbers, -r recursive, -l filenames only, -s sort output, -u unique output; reads from pipe when no file given
find <path> [name] Recursive filename search: wildcard match (*,?) or substring mode when no wildcard
rename <old> <new> Rename file or directory
copy [/y\|/s] <src> <dst> Recursive copy; src supports wildcard (*,?) expansion in last path component
edit [path] Full-screen text editor (F2 save, F3 save-as, Esc quit)
clear Clear the terminal screen

I/O operators

Syntax Description
cmd > file Redirect stdout to file (overwrite)
cmd >> file Redirect stdout to file (append)
cmd1 \| cmd2 Pipe: stdout of cmd1 becomes stdin of cmd2
cmd1 \| cmd2 \| cmd3 Multi-stage pipe (up to 8 stages)
cmd1 \| cmd2 > file Pipe and redirect final stage to file

Pipe-aware built-ins (read from pipe when no file argument is given): grep, cat, tail.

Archive commands

Command Description
zip <out.zip> <file\|glob...> Create ZIP archive (Store method); input supports wildcard expansion
unzip <file.zip> [dest] Extract ZIP (Store + Deflate via miniz)
zipls <file.zip> List ZIP archive contents

Disk / filesystem commands

Command Description
df Disk usage: capacity, used, free, %, mount point
format [drv] [size] Format TinyFS v4 drive; optional size limits partition range
mount [drv] [path] Mount TinyFS v4 drive; creates standard dirs on first mount
tfs status [path] TinyFS mount health: read-only state, corrupted inodes, dedupe stats
tfs dedupe scan [path] Force full dedupe scan and print scanned/deduped counters

format size examples:

format 0          # first available drive (auto-resolve when vdrv0 is absent)
format 0 256M     # first 256 MiB only
format 0 4G       # first 4 GiB only
format 2 1T       # drive 2, first 1 TiB
format 0 512000   # raw sector count (512 000 → 512 B ? 256 MB)

Accepted size suffixes: K (KiB), M (MiB), G (GiB), T (TiB).
The rest of the physical disk beyond the specified size is left untouched.

TinyFS v4 dedupe smoke test:

format 0 /y
mount 0
write /disk/disk0/a "hello dedupe"
copy /disk/disk0/a /disk/disk0/b
tfs status /disk/disk0
ls -ac /disk/disk0

Expected:
- tfs status reports dedupe shared payloads >= 1 and non-zero dedupe saved bytes.
- Modifying one of the shared files triggers COW and increments dedupe COW events.
- After reboot + remount, tfs status still reports consistent dedupe counters.

Process management commands

Command Description
jobs List all background tasks (running / done)
kill <pid> Terminate a task by its scheduler PID
lsproc List all scheduler tasks (id, state, name)
<cmd> & Run any command in the background (8 slots)

System information commands

Command Description
lscpu CPU vendor, brand, family/model/stepping, features
lsram Physical memory: total, free, heap stats
lspci PCI device list with vendor/device IDs; enriched from /etc/pci.ids
lseth Ethernet interfaces: MAC address, model
lsusb USB devices attached to the UHCI controller

Network commands

Command Description
netinit Re-read /etc/networks + /etc/resolv.conf and (re)init NIC
dhcp [seconds] Request DHCP lease now (default 20s timeout, range 1-30s)
ipconfig Show interface mode and current IP/netmask/gateway (plus DNS for DHCP lease)
arp Show ARP cache
ping <host\|IP> ICMP echo request; resolves hostname via DNS
resolve <hostname> DNS A-record lookup
tftp <file> <dst> Download file from QEMU built-in TFTP server (10.0.2.2:69)
wget <url> <dst> HTTP downloader with progress output
update App update helper (manifest + package fetch)
ftp <host> ... Interactive FTP client (PASV data mode)
ftpc [session] Dual-panel FTP commander
httpd Manage HTTP/1.1 server (start|stop|status|restart, config: /etc/httpd.conf)
ftpd Manage FTP server (start|stop|status|restart, config: /etc/ftpd.conf)
sshd Manage SSH server (start|stop|status|restart)

User & security commands

Command Description
whoami Print current username
id Print uid=N(name) gid=N(name)
su [user] Switch user; root bypasses password; others authenticate
login <user> Log in (passwd + shadow check, 3-attempt lockout)
logout End session, return to login prompt
passwd [user] Change password (min 4 chars, salted FNV-1a hash)
useradd <user> Create account + home dir + standard subdirs (Desktop, Downloads, Documents, Music, Videos, Pictures, tmp) + .history + .env + .profile
userdel <user> Delete account and home directory

Group commands

Command Description
groups [user] List groups
groupadd <name> Create group (root only)
groupdel <name> Delete group (root only)
usermod -g <grp> <user> Add user to group (root only)

Permission commands

Command Description
chmod <octal> <path> Change mode (e.g. chmod 755 /disk/disk0/usr/bin)
chown <user> <path> Change owner (root only)

CoreShell GUI Apps

Two full-screen TUI applications are built directly into the kernel shell
(no busybox or ELF required). Each replaces the canvas F-key bar with its
own context while active, then restores the shell bar on exit.


nc - Not Commander

Dual-panel file manager inspired by Norton Commander.

nc

Layout

[menu bar] Left  File  Right  Commands  Options  Shortcuts
┌────────────── /left/path ─────────────┬───────────── /right/path ────────────┐
│ Name*  Size     T   Date     Ver      │ Name   Size      T   Date     Ver    │
│ ..                                  │ │ ..                                  │
│ Desktop/                            │ │ bin/                                │
│ Downloads/                          │ │ dev/                                │
│ ...                                 │ │ ...                                 │
└──────────────────────────────────────┴──────────────────────────────────────────┘
[status bar] panel stats + operation progress (copy/move/search/etc.)
[f-key bar] F1 Help  F2 Refresh  F3 View  F4 Edit  F5 Copy  F6 Move  F7 MkDir  F8 Del  F9 Menu  F10 Quit
[alt f-key bar] hold Alt: F5 ZIP  F6 UnZIP  F7 Find

Keyboard reference

Key Action
↑ / ↓ Move cursor in active panel
Tab Switch between left / right panel
Enter Open directory or file (Enter = Y in confirmation dialogs)
Backspace Go up one directory
Space / Ins Mark / unmark entry (Insert key = Space); for directories, marking also calculates recursive size
Del / F8 Delete file/directory under cursor (recursive for dirs)
s Cycle sort mode: Name → Date → Size (per panel, shown with * in header)
Ctrl+O Suspend NC, drop into mini-shell; Ctrl+O again restores NC
F1 Show keyboard reference
F2 Refresh both panels and drive list
F3 View file contents (read-only pager)
F4 Edit file in the built-in editor
F5 Copy marked entries to the other panel
F6 Move / rename; bare filename (no /) = rename in current directory
F7 Create directory in active panel
Alt+F5 ZIP selected entries
Alt+F6 UnZIP selected archive
Alt+F7 Find dialog
F8 Delete marked entries (confirmation required; recursive for dirs)
F9 Open top menu bar
F10 Quit Not Commander (with confirmation; sets shell CWD to active panel)

F9 top menu

Menu Options
Left / Right Re-read, Swap panels, Sort by name / date / size
File View, Edit, Copy, Move, MkDir, Delete, Select all, Deselect all
Commands Shell Ctrl+O, FTP client (ftpc)
Options About NC
Shortcuts This PC, User folder (Desktop / Documents / Music / Downloads / Pictures / Videos), Root /

ftpc - FTP Commander

Norton-Commander-style dual-panel FTP client.
Left panel = local disk, Right panel = remote FTP server.

ftpc [session_name]

Sessions are saved to /disk/disk0/etc/ftpclient.conf (INI format)
and restored across invocations.

Keyboard reference

Key Action
^ / → Move cursor in active panel
Tab Switch between local / remote panel
Enter Enter directory
Backspace / .. Go up one directory
F5 Transfer selected entry to the other panel
F7 Create directory in active panel
F8 Delete selected entry (confirmation required)
F9 Session manager - new connection, reconnect, delete session
F10 / q Quit ftpc

Session manager (F9)

Action Description
New Enter hostname, port, user, password → connect
Reconnect Re-open the current session
Delete Remove session from config file

Font System (M23)

Architecture

Kernel binary
  L→ hack_regular_data.h       → Hack Regular TTF embedded as uint8_t[]
       → font::init()
  src/vendor/stb/
  L→ stb_truetype_impl.c       → stb_truetype compiled as C (CFLAGS_STB, SSE2, -fno-builtin)
  L→ compat/                   → freestanding shims: string.h, stdlib.h, math.h, assert.h
       → stb_bake_glyph()
  src/kernel/gfx/font.cpp
  L→ s_cache[95][16][8]        → pre-baked grayscale alpha (12 KB, 95 ASCII glyphs)
       → font::draw_char()
  Alpha-blend over fb / window buffer

Glyph cache

At font::init() (called after heap::init()), stb_truetype rasterizes all 95
printable ASCII characters (0x20-0x7E) at 13 px height into an
s_cache[95][16][8] static array of 8-bit alpha values (? 12 KB total).

draw_char() / draw_char_to() then read from this cache with a fast integer
alpha-blend: out = fg → ?/255 + bg → (255-?)/255.

Box-drawing chars (0x80-0x8A) fall back to the original 1-bit bitmap renderer.

Runtime font switching

font                                        # list available fonts + show current
font /usr/share/fonts/JetBrainsMono.ttf    # switch immediately (re-bakes cache)

font::load_file(path) reads the TTF from VFS into a heap buffer, calls
stb_font_init(), re-bakes all 95 glyph cells, and frees the previous
heap-allocated font data. The change is instantaneous - the next
draw_char() call uses the new glyphs.

Adding fonts

Development / QEMU (manual):

mcopy -i build/fatdisk.img -o MyFont.ttf ::/usr/share/fonts/

Project fonts directory (fonts/):

cp MyFont.ttf fonts/
make fonts-to-fat        # requires mtools (pacman -S mingw-w64-x86_64-mtools)

Real hardware: include fonts in the disk image before flashing to USB/disk.

stb_truetype build notes

stb_truetype is compiled as a separate C translation unit with its own flags:

Flag Reason
-msse2 Float return values require XMM registers on x86-64 ABI
-fno-builtin Prevents GCC from emitting libm calls for sqrt/pow/cos
-I src/vendor/stb/compat Freestanding shims replace <string.h>, <stdlib.h>, <math.h>
-w Suppress vendor warnings
-O1 Light optimisation; SSE2 sqrtsd/sqrtss inline asm used for sqrt

malloc/free in stb are redirected to heap::alloc/heap::free via
extern "C" wrappers already present in heap.cpp.


Networking

All networking is polling-based (no interrupt handler) on the Intel 82540EM
(e1000) emulated by QEMU (-device e1000).

Layer Implementation
Ethernet II e1000 TX/RX descriptor rings
ARP Request/reply, 16-entry cache, opportunistic MAC learning
IPv4 Checksum, routing heuristic (same-subnet / gateway)
ICMP Echo request / reply (ping)
UDP Send / receive (dns)
DNS A-record resolver (UDP port 53)
TCP Active client + server-pool (tcp_srv) listeners; loopback path (127.0.0.1) and minimal in-order payload reassembly

Network config files

# /disk/disk0/etc/networks
[eth0]
mode    = dhcp

For static networking, switch to:

[eth0]
mode    = static
ip      = 10.0.2.15
netmask = 255.255.255.0
gateway = 10.0.2.2
# /disk/disk0/etc/resolv.conf
nameserver 10.0.2.3

HTTP Server (httpd)

A built-in HTTP/1.1 server (v2) that runs as a background scheduler task.

# /disk/disk0/etc/httpd.conf
port    = 80
docroot = /disk/disk0/usr/www
index   = index.html
max_sessions = 6
idle_timeout_sec = 5

# mime.types section
mime.html = text/html; charset=utf-8
mime.css  = text/css
mime.js   = application/javascript
mime.json = application/json
mime.png  = image/png
mime.jpg  = image/jpeg
mime.jpeg = image/jpeg

Usage:

netinit
httpd
# → open http://localhost:18000 in a host browser

FTP Server (ftpd)

Built-in FTP server running as a background scheduler task, with Explicit FTPS
(AUTH TLS / PBSZ 0 / PROT P) backed by mbedTLS.

Supported FTP commands

Command Purpose
USER, PASS, QUIT, NOOP Session/auth basics
SYST, FEAT, OPTS, TYPE, AUTH, PBSZ, PROT Capability, FTPS negotiation and transfer mode control
PWD, CWD, CDUP Directory navigation
PASV Passive data connection
LIST, NLST Directory/file listing
RETR, STOR, SIZE File download/upload/size
MKD, DELE, RMD Create dir, delete file, recursive remove dir
# /disk/disk0/etc/ftpd.conf
port = 21
#ports = 21,2121

max_sessions = 3
idle_timeout_sec = 60

pasv_port_start = 21000
pasv_port_end   = 21015

# IP ACL
#allow_ip = 127.0.0.1
#allow_ip = 10.0.2.*
#deny_ip  = 10.0.2.15

# Brute-force protection
auth_fail_window_sec = 300
auth_max_failures = 5
auth_block_sec = 180

# Auth backend: local | shadow
auth_backend = local

# Explicit FTPS policy (requires mbedTLS-enabled FTPS runtime)
tls_enable = 0
ftps_require_prot_p = 1

user.anonymous.root = /disk/disk0/usr/ftp
user.anonymous.password =
user.anonymous.write = 0

user.root.root = /disk/disk0/home/root
user.root.password =
user.root.write = 1

Usage:

netinit
ftpd start
ftpd status
ftpd stop

Startup Scripts

Two configuration files control automatic command execution:

/disk/disk0/etc/startup-services

Executed at boot before the login prompt, right after disk0 is mounted.
Each non-comment line is launched as a background task.

# /disk/disk0/etc/startup-services
# Uncomment to auto-start the web server at every boot:
# httpd
# ftpd
# netinit

/disk/disk0/etc/startup

Executed after each successful login as the logged-in user.
Each non-comment line is run as a foreground shell command.

# /disk/disk0/etc/startup
# cd /disk/disk0/home/root
# netinit

Edit both files with edit /disk/disk0/etc/startup.

Optional GUI integration: if /disk/disk0/prefs/startup.prefs exists, startup commands are filtered by its toggles (coredesk, network, httpd, ssh).
If the prefs file is missing, startup behavior stays exactly as before (shell-first path unchanged).


Dual-Disk Support

Drive QEMU index Default mount Role
Primary master index=0 /disk/disk0 System disk
Primary slave index=1 /disk/disk1 Data disk (auto-mounted if formatted)

Both disks are blank images created on first make run
(build/tinyfs.img, build/tinyfs1.img).
The default QEMU size is configurable in the Makefile; TinyFS v4 supports
up to ~2 TB per partition. Use format 0 <size> to initialise only a
portion of a larger disk.

Disk layout on disk0

/disk/disk0/
├── home/                   → user home directories
│   └── root/
│       └── .history
├── etc/
│   ├── passwd              → user database       (user:uid:homedir)
│   ├── shadow              → password hashes     (user:8charsalt:16charhash)
│   ├── group               → group database      (groupname:gid:member1,member2)
│   ├── networks            → NIC configuration   (INI-style)
│   ├── resolv.conf         → DNS nameservers
│   ├── pci.ids             → custom PCI device DB (VVVV:DDDD=Vendor|Device)
│   ├── httpd.conf          → HTTP server config
│   ├── ftpd.conf           → FTP server config
│   ├── startup             → post-login commands
│   └── startup-services    → boot-time background services
├── log/
│   └── auth.log            → login / su audit trail
└── usr/
    ├── bin/                → user ELF programs (M10+)
    └── www/                → HTTP server document root
        └── index.html

Security & User Management

Authentication files

File Format Notes
etc/passwd user:uid:homedir User database
etc/shadow user:8charsalt:16charhash Salted FNV-1a hashes
etc/group groupname:gid:member1,member2 Group database
log/auth.log <ticks> <event> Audit trail

Password hashing

FNV-1a 64-bit mixed with an 8-character hex salt (from timer tick xorshift):

root:a1b2c3d4:e5f6a7b8c9d0e1f2
     ^^^^^^^^ ^^^^^^^^^^^^^^^^
     8-char   16-char hash of (password + salt)
     salt

Empty hash = no password. Old single-field entries (user:hash) accepted for
backward compatibility. Minimum password length: 4 characters.

Login security

File permissions

All inodes carry mode (rwxrwxrwx) and uid. Enforced in open, mkdir,
unlink:

TinyFS v4 persists mode, uid, ctime, mtime, version, and dedupe
metadata (content_crc32c, owner/refcount) to disk
(OnDiskInode). FAT32 stores ctime/mtime in standard directory-entry
date/time fields and repurposes nt_res for the version counter. Both RamFS and
TinyFS implement setattr for chmod/chown.

The version field in vfs::Inode / vfs::Stat is a write counter
(1 = freshly created, increments on each write(), saturates at 255).
It is displayed in nc (Ver column) and ls -a.


VFS

Interface Operations
InodeOps lookup, create, mkdir, unlink, rename, setattr
FileOps read, write, readdir, close
vfs::rename Cross-directory rename (RamFS: linked-list splice; TinyFS: in-place)
vfs::mount_at Overlay a superblock at a path
vfs::chmod / chown Update permission bits / owner UID
vfs::set_current_uid Set active security context on login/logout

Filesystem tree

/                   → RamFS (always in RAM)
├── bin/
├── dev/
├── tmp/
└── disk/
    ├── disk0/      → TinyFS primary master (auto-mounted)
    └── disk1/      → TinyFS primary slave  (auto-mounted if formatted)

ATA PIO Driver

Operation Detail
Detection IDENTIFY DEVICE (0xEC); skips ATAPI
Addressing 28-bit LBA
Read CMD 0x20, poll DRQ, 256 → 16-bit words/sector
Write CMD 0x30, poll DRQ, write + FLUSH CACHE (0xE7)
Drives 0 = primary master, 1 = primary slave

TinyFS v4 On-Disk Format

TinyFS v4 is a custom scalable filesystem with 4 KiB data blocks and a
fully dynamic metadata layout calculated at format time.

Disk layout (512-byte sectors)

Sector 0               Superblock  (magic "TFS4", version 4)
Sectors 1 .. B         Block bitmap  (1 bit per 4 KiB data block)
Sectors B+1 .. B+I     Inode bitmap
Sectors B+I+1 .. B+I+T Inode table  (4 inodes → 128 B per sector)
Sector  data_start+    Data blocks   (4 KiB = 8 sectors, block-aligned)

All offsets (bitmap_start, imap_start, itbl_start, data_start) are
stored in the superblock and computed at format time - no hardcoded positions.

Capacity

Resource Limit
Partition ~2 TB (uint32_t block numbers → 4 KiB)
File size ~4 GB (12 direct + 1024 single + 10242 double indirect)
Inodes up to 1 000 000 (1 per 256 KiB of data)
Filename 247 characters

Key on-disk structures

struct OnDiskSb {           // 512 bytes, sector 0
    uint32_t magic;         // 0x54465334 ("TFS4")
    uint32_t version;       // 4
    uint32_t block_size;    // 4096
    uint32_t inode_count;   // total inodes
    uint64_t block_count;   // total 4 KiB data blocks
    uint64_t free_blocks;
    uint32_t free_inodes;
    uint32_t root_inode;    // always 0
    uint32_t bitmap_sectors, imap_sectors, itbl_sectors;
    uint64_t bitmap_start, imap_start, itbl_start, data_start;
    uint8_t  pad[424];
};

struct OnDiskInode {        // 128 bytes; 4 per 512-byte sector
    uint32_t mode, uid;
    uint64_t size;          // file size in bytes (uint64_t)
    uint32_t ctime, mtime;  // Unix timestamps
    uint32_t nlinks, blocks, flags;
    uint32_t direct[12];    // 4 KiB block numbers
    uint32_t indirect;      // single-indirect block
    uint32_t dindirect;     // double-indirect block
    uint32_t tindirect;     // triple-indirect (reserved)
    uint8_t  version;       // write counter (1-255)
    uint32_t content_crc32c;  // full-file hash for dedupe
    uint32_t dedup_owner_ino; // owner inode for shared payload
    uint32_t dedup_refcount;  // valid on owner inode
    uint8_t  dedup_flags;     // owner/shared flags
    uint32_t meta_crc32c;     // inode metadata checksum
    uint8_t  pad[14];
};

struct OnDiskDirent {       // 256 bytes; 16 per 4 KiB block
    uint32_t inode;         // 0 = unused
    uint16_t rec_len;       // always 256
    uint8_t  name_len;
    uint8_t  type;          // 1=file, 2=dir
    char     name[248];     // max 247 usable chars
};

Block addressing

Block range Mechanism Adds
0-11 direct[12] 48 KiB
12-1035 single-indirect +4 MiB
1036-1 049 611 double-indirect +4 GiB

Inode metadata fields

Every inode stores:
- ctime - creation time (Unix timestamp; filesystem-backed where available, fallback to mtime when unknown)
- mtime - last modification time (Unix timestamp, set by RTC on write)
- version - write counter, incremented on every write, saturates at 255, initialised to 1 at creation
- content_crc32c - full-file content hash used by TinyFS v4 file-level dedupe
- dedup_owner_ino / dedup_refcount - shared payload ownership and reference counting

vfs::stat() currently surfaces ctime, mtime, version, and metadata
checksum status. ls -a shows Created/Modified/Cks; nc and ls -a
show version.

How file-level dedupe works

TinyFS v4 deduplicates whole-file payloads (not variable chunks):

  1. On write/truncate completion, filesystem computes content_crc32c.
  2. Candidate files are filtered by same size + same content_crc32c.
  3. Candidates are verified block-by-block (CRC match alone is never trusted).
  4. On exact match:
    - current inode becomes shared member (dedup_owner_ino points to owner),
    - owner dedup_refcount is incremented,
    - duplicate payload blocks are released.
  5. On future write to a shared file, copy-on-write (COW) detaches it first
    so modifications do not affect other files sharing the payload.
  6. On unlink/truncate, refcount lifecycle is preserved:
    - non-owner unlink decrements owner refcount,
    - owner unlink elects replacement owner when needed,
    - physical blocks are freed only when effective refcount reaches zero.

You can force full re-deduplication with:

tfs dedupe scan /disk/disk0

And inspect live counters with:

tfs status /disk/disk0

Backward compatibility

TinyFS v4 uses a new magic/version (TFS4, version 4).
Older disks are recognised as "present but not formatted" and must be
reformatted with format [0|1] to use v4.


Text Editor (edit)

Full-screen terminal editor with line-oriented buffer and incremental redraw:

Key Action
Arrow keys Move cursor
Home / End Start / end of current line
Page Up / Dn Scroll one screen up / down
Backspace Delete character / join lines
Del Delete character under cursor / join with next line
Enter Insert newline / split line
F2 Save to current file
F3 Save As (prompts for filename)
Esc Quit (warns on unsaved changes)

ZIP Support

Function Detail
zip::list Iterates Central Directory
zip::extract Store (method 0) + Deflate (method 8 via miniz tinfl)
zip::create Store-method archives with proper EOCD

miniz_tinfl.c compiled as a separate C translation unit with freestanding
shim headers in src/vendor/miniz/compat/.


Memory layout

Region Virtual address Description
Kernel text/data/bss 0x100000 - _kernel_end Linked at 1 MiB
PMM bitmap after _kernel_end ~128 KiB for 4 GiB RAM
Kernel heap grows up from PMM first-fit free list
Identity map 0-1 GiB 0x00000000 - 0x3FFFFFFF 2 MiB huge pages
Identity map 3-4 GiB 0xC0000000 - 0xFFFFFFFF 2 MiB huge pages (VESA FB)
User code (M6) 0x40000000 4 KiB page, PF_USER
User stack (M6) 0x40001000 4 KiB page, PF_USER

Preemptive Scheduler

PIT fires IRQ0 at 100 Hz → sched::tick() → round-robin next Ready task →
switch_context.

switch_context(old_rsp*, new_rsp):
  push rbp/rbx/r12-r15   → save callee-saved regs on OLD stack
  [old_rsp*] = RSP        → store old RSP
  RSP = new_rsp           → load NEW stack
  pop r15-r12/rbx/rbp    → restore new task's regs
  ret                     → resume new task at saved RIP

CPU usage measurement

sched::tick() increments two counters on every PIT interrupt:

Tasks signal idleness by calling sched::hint_idle() immediately before a
hlt instruction or an unconditional sched::yield() with no work to do.
The next tick() sees the hint, skips s_busy_ticks, and clears the flag.
CPU% is sampled once per second: busy / total → 100.

Task lifecycle API

Function Description
sched::create_task(name) Spawn kernel task
sched::exit_current() Terminate the calling task
sched::kill_task(id) Mark any non-idle task Dead from outside
sched::hint_idle() Signal that the calling task is about to sleep/idle
sched::find_task(id) Look up a Ready/Running/Blocked task by ID
sched::for_each_task(fn) Iterate all non-Dead tasks

Syscall ABI (introduced in M6)

Number Name Arguments Returns
0 SYS_EXIT exit_code -
1 SYS_WRITE fd, buf*, len bytes written
2 SYS_OPEN path*, flags, mode fd or -1
3 SYS_CLOSE fd 0 or -1
4 SYS_READ fd, buf*, len bytes read
5 SYS_READDIR fd, DirEntry*, max count
6 SYS_MKDIR path*, mode 0 or -1
7 SYS_STAT path, Stat 0 or -1

Historical Notes

Detailed milestone-by-milestone implementation notes (M10-M44) were moved out of this file to keep the main README focused on current usage.


PHP Migration Plan


Make targets

Target Description
make / make all Build kernel ELF + ISO
make kernel Compile only (no ISO)
make iso Package into bootable ISO
make run QEMU graphical window; creates disk images if absent
make serial QEMU to stdout, no window
make debug QEMU paused, GDB on port 1234
make debug-boot QEMU with -no-reboot -d int,cpu_reset
make limine-get Clone Limine binaries (run once)
make monocypher-get Download Monocypher crypto library (run once, for SSH)
make userspace Build user-space ELF programs with musl-gcc (WSL2/Linux)
make userspace-docker Build user-space ELF programs inside Docker
make fonts-to-fat Copy fonts/*.ttf → build/fatdisk.img:/usr/share/fonts/ (requires mtools)
make clean Remove build/ (disk images included)

Toolchain

Component Target Notes
GCC 13.x x86_64-elf -std=c++20 -ffreestanding -fno-exceptions -fno-rtti
GCC 13.x (C) x86_64-elf -std=c11 -msse2 -fno-builtin for miniz_tinfl.c and stb_truetype_impl.c with compat shims
NASM ELF64 boot.asm, isr_stubs.asm, context.asm, syscall_entry.asm, readme_data.asm
Limine v5.x-branch-binary Multiboot2; handles VESA framebuffer setup
QEMU qemu-system-x86_64 -m 2048M -serial stdio -vga std -netdev user,id=net0,hostfwd=tcp::18000-:80,hostfwd=tcp::2222-:22,tftp=build -device e1000,netdev=net0

Compile flags of note:
-march=x86-64 -mno-sse -mno-sse2 -mno-avx -mno-avx2 - kernel: baseline x86-64, no SSE (kernels must not use XMM registers)
-mno-red-zone - required for ring-0 interrupt handlers
-fno-stack-protector - no libgcc stack cookie in freestanding
-fno-threadsafe-statics - no __cxa_guard_* needed
-MMD -MP - automatic header dependency tracking
-fno-omit-frame-pointer - RBP chain preserved for kernel stack traces

stb_truetype (vendor C, separate flags):
-msse2 -fno-builtin - SSE2 enabled (font init runs before scheduler; XMM safe at boot);
-fno-builtin prevents GCC from silently emitting libm calls for sqrt/pow/cos


Network & TCP/IP (M30)

M30 completes the network stack with the missing components needed for automatic configuration and inter-process communication.

DHCP Client (net/dhcp.hpp/cpp)
- Full RFC 2131 / 2132 four-message exchange: DISCOVER → OFFER → REQUEST → ACK
- net::udp_send_bcast() added to net.cpp - sends from 0.0.0.0 to 255.255.255.255 via broadcast MAC before an IP is assigned
- On ACK: calls net::set_config(0, ...) with leased IP/mask/gateway and dns::set_server(0, ...) with the DNS server
- dhcp [seconds] shell command: manual lease request with live output and configurable timeout
- netinit automatically runs DHCP when /etc/networks has mode=dhcp
- VMware/NAT compatibility: client now accepts valid DHCP OFFER/ACK packets shorter than 548 bytes
- DHCP negotiation phases (DISCOVER/OFFER/REQUEST/ACK, timeouts, NAK) are mirrored to hdd.log for diagnostics

Loopback Interface (127.0.0.1)
- net::loopback_send() injects raw IP packets into an 8-slot ring buffer (no NIC, no Ethernet)
- net::poll() drains the loopback ring before the NIC RX ring
- handle_ip() now accepts broadcast (255.255.255.255) and loopback (127.x.x.x) destinations in addition to the interface IP
- ping 127.0.0.1 and localhost resolution work without network

/etc/hosts Lookup
- resolve_host() wrapper in shell.cpp checks /disk/disk0/etc/hosts before calling dns::resolve()
- Standard format: <ip> <hostname> [<alias>...]; comments with #
- All commands that resolve hostnames (ping, wget, ftp, ftpc, smbclient, resolve) use the wrapper
- localhost resolves to 127.0.0.1 without a file (hardwired in dns::resolve)

AF_UNIX Domain Sockets (net/socket.hpp/cpp)
- AF_UNIX = 1, struct SockAddrUn { sun_family; sun_path[108] }
- KSock extended with domain, unix_pair, unix_side, unix_path
- Socket pool enlarged from 4 to 16 slots; 8 UnixPair ring-buffer pairs (8 KiB each direction)
- unix_bind(path) - registers path in binding table, creates VFS placeholder inode
- unix_connect(path) - finds listener, allocates pair, connects side A
- unix_accept(listener) - creates new KSock for side B, returns File*
- unix_read/unix_write - bidirectional ring buffers (A→B and B→A)
- Syscall layer updated: SYS_BIND, SYS_CONNECT, SYS_ACCEPT/ACCEPT4 dispatch on KSock::domain


Shell I/O Plumbing (M31)

M31 adds the syscalls required for a user-space shell to set up I/O redirection and pipes, as well as standard file-manipulation operations that musl/libc programs rely on.

dup / dup2 / dup3 (syscalls 32 / 33 / 292)
- vfs::File gains a refcount field (starts at 1); vfs::close() only destroys when it reaches 0
- FdTable::replace(new_fd, f) - atomically installs f and returns the old occupant for closing
- fork() and clone() bump the refcount for every inherited fd
- dup2(old_fd, new_fd) closes the previous file at new_fd before installing the new reference
- dup3 adds O_CLOEXEC semantics; requires old_fd != new_fd
- SYS_READ/SYS_WRITE now check fd_table[0/1/2] first - dup2 redirections to stdin/stdout work correctly

pread64 / pwrite64 (syscalls 17 / 18)
- Read/write at an arbitrary offset without changing f->pos
- Saves and restores f->pos around the underlying VFS call
- Required by sqlite3, busybox's dd, and many database-like programs

truncate / ftruncate (syscalls 76 / 77)
- vfs::FileOps gains a truncate(File*, int64_t new_size) callback (7th field)
- ramfs: file_truncate() grows (zero-fills) or shrinks the buffer; adjusts f->pos if beyond new size
- tinyfs / fat32: callback is nullptr; VFS generic fallback handles shrink-only via inode->size
- vfs::truncate(path, size) opens the file internally and delegates to ftruncate

rename / renameat / renameat2 (syscalls 82 / 264 / 316)
- vfs::rename() was already implemented in vfs.cpp; this adds the syscall dispatch
- renameat/renameat2 resolve paths relative to dirfd (AT_FDCWD treated as cwd)
- Required by editors, package managers, and any program using atomic save-to-temp patterns

getpgrp (syscall 111)
- Returns sched::current()->pgid; needed for job-control queries by shells

FD_MAX increased from 16 → 64
- Busybox and most programs need more than 16 simultaneous open files
- FdTable size increases from 128 to 512 bytes per task (heap-allocated, no stack impact)


PTY - Pseudo-Terminal (M32)

M32 adds a complete pseudo-terminal subsystem, enabling user-space shells (busybox sh, bash) to run interactively with job control, line editing, and proper terminal semantics.

Architecture (src/kernel/tty/pty.hpp / pty.cpp)
- Up to 4 simultaneous PTY pairs; each pair has two 4 KiB ring buffers:
- m2s (master→slave): keyboard/terminal-emulator input going to the program
- s2m (slave→master): program output going back to the terminal
- Per-pair tty::Termios struct - each PTY has its own independent terminal settings

/dev/ptmx - PTY master
- open("/dev/ptmx") / posix_openpt() is intercepted in SYS_OPEN/SYS_OPENAT before VFS
- Each open allocates a fresh slot and returns a master File* with custom FileOps
- ioctl(master_fd, TIOCGPTN, &n) → returns the PTY index n
- ioctl(master_fd, TIOCSPTLCK, &0) → unlocks the slave so it can be opened
- TCGETS/TCSETS on the master operate on the slave's termios

/dev/pts/N - PTY slave
- open("/dev/pts/N") is intercepted; returns nullptr if slot n is locked or unused
- ioctl(slave_fd, TIOCSCTTY, 0) - sets controlling terminal (stub; updates session)
- ioctl(slave_fd, TCGETS/TCSETS) - reads/writes the per-PTY termios
- TIOCGWINSZ / TIOCGPGRP delegate to global console state

N_TTY line discipline (slave side)
- Canonical mode (ICANON): slave_read blocks until \n or ^D; slave_poll_read scans the ring for a complete line
- Raw mode: bytes are delivered immediately on each vfs::read
- Echo (ECHO/ECHOE): input written to m2s is echoed back to s2m for the master to display; DEL (0x7F) sends BS SP BS erase sequence
- ICRNL: CR → NL on master write (input processing)
- ONLCR: NL → CR+NL on slave write (output post-processing)

Blocking reads
- Both master and slave read ops call sched::yield() until data is available - safe because SYS_READ enables interrupts (STI) before calling vfs::read


Busybox on Disk (M33)

M33 delivers a complete user-space toolchain on disk via the statically-linked busybox multi-call binary, plus the kernel infrastructure needed to run it correctly.

ELF loader - full Linux ABI stack (src/kernel/exec/elf.cpp)

elf::load now accepts argv[], argc, envp[], envc. The initial user-stack layout strictly follows the System V AMD64 ABI:

RSP → [argc]
      [argv[0] ptr] → [argv[argc-1] ptr] [NULL]
      [envp[0] ptr] → [envp[envc-1] ptr] [NULL]
      [AT_PAGESZ, 4096] [AT_HWCAP, 0] [AT_CLKTCK, 100]
      [AT_HWCAP2, 0] [AT_SECURE, 0] [AT_RANDOM, ptr] [AT_NULL, 0]
[high VA]  argv/envp strings (packed) + 16-byte random block

A built-in minimal environment is injected when envp is not provided:
HOME, PATH=/bin:/usr/bin:/disk/disk0/bin:/disk/disk0/usr/bin, TERM=vt100, SHELL=/bin/sh, USER=root.

SYS_EXECVE - user-space argv/envp (src/kernel/syscall/syscall.cpp)

The execve handler now copies the user-space char **argv and char **envp arrays into kernel buffers (heap-allocated, 8 KiB each, up to 64 strings) and passes them to elf::load. On failure the buffers are freed before calling sched::exit_current.

Shell - cmd_exec argv splitting

cmd_exec(arg, argv0_override) splits the tail of the exec argument string into tokens and builds a proper const char* argv[]. The optional argv0_override allows the PATH dispatcher to pass the typed command name (e.g. sh) as argv[0] even when the resolved binary is /disk/disk0/bin/busybox - this is the mechanism that makes busybox applet dispatch work via symlinks.

Shell - PATH search extended

The implicit PATH search now covers four directories in order:
1. /bin
2. /usr/bin
3. /disk/disk0/bin
4. /disk/disk0/usr/bin

bb-install shell command

bb-install [url]
  1. Downloads busybox via TFTP from 10.0.2.2:69 (QEMU built-in, no extra server needed) - or via HTTP if a full URL is passed
  2. chmod 755 /disk/disk0/bin/busybox
  3. Shows an interactive group-selection menu (Space = toggle, Enter = install, Esc = cancel):
Group Default Applets
Shell ? on sh
File utilities ? on ls cat cp mv rm find du df →
Text processing ? on grep sed awk sort head tail cut →
Process utils ? on ps kill top free sleep →
Archive ? on tar gzip bzip2 →
Network ? on wget ping netstat
System utils ? on date uname whoami id
Permissions ? on chmod chown chgrp
Scripting ? on test expr xargs seq
Misc ? on md5sum hexdump od strings sha256sum
Overlap/extra ? off mount umount dmesg vi ash hwclock →

nc (netcat) is always excluded - the name conflicts with the built-in Not Commander (nc).

Interactive shell fix (M33 post) - fully working ?

After the initial M33 implementation busybox sh loaded but exited immediately, and later ran but showed no prompt. The root causes were a sequence of missing kernel behaviours fixed iteratively:

Fix File What changed
TIOCGPGRP returns caller's pgid syscall.cpp tcgetpgrp(0) == getpgrp() → busybox sees itself as foreground process
SIGTTIN / SIGTTOU / SIGTSTP ignored signal.cpp Default action changed from "terminate" to "ignore" (real Linux: stop)
fd 0/1/2 pre-installed as /dev/tty shell.cpp Child process has real File* objects for stdio; dup()/fcntl(F_DUPFD_CLOEXEC) work
F_DUPFD_CLOEXEC respects min-fd syscall.cpp fcntl(0, F_DUPFD_CLOEXEC, 10) now returns fd ? 10 as POSIX requires
SYS_WRITEV (nr=20) syscall.cpp busybox uses writev() to write the prompt - missing syscall = invisible prompt
SYS_READV (nr=19) syscall.cpp scatter read; busybox uses for terminal input
sched_yield (nr=24) syscall.cpp cooperative yield used by busybox busy-wait loops
ANSI escape sequence parser console.cpp \033[K erase-to-EOL, cursor movement, SGR colors stripped; prompt no longer garbled
\r carriage return console.cpp \r now moves cursor to col 0 instead of rendering as literal character
getdents64 debug removed syscall.cpp removed [gd64] per-entry debug print that flooded the console on ls
fork register restore syscall_entry.asm, scheduler.cpp enter_userspace_fork saves/restores RBX/RBP/R12-R15; without this the fork child crashed immediately (R13 = wrong pointer → page fault in [R13+0x21])

Build guard - make run requires busybox

make run and make serial now call check-busybox first. If build/busybox is missing, make stops with a clear error and instructions. Python is no longer required for any build or run step (only optionally for make serve).

Complete M33 workflow

# Step 1 (host, once): get the busybox binary into build/
make busybox-prebuilt   # MSYS2/Windows: download pre-built x86-64 binary
# OR: make busybox      # WSL2/Linux: compile with musl-gcc
# OR: make busybox-docker

# Step 2 (host): start QEMU - TFTP is built in, no Python/HTTP server needed
make run

# Step 3 (kernel shell, first boot)
format 0            # initialise TinyFS on disk0 (once)
mount 0             # mount disk0 at /disk/disk0
netinit             # bring up eth0 (DHCP via QEMU SLIRP)
bb-install          # TFTP busybox + chmod 755 + interactive group-selection menu for symlinks

# Step 4: launch interactive busybox shell
sh                  # → busybox sh prompt  / #
ls /                # list root filesystem
echo hello world    # basic test

TFTP vs HTTP: bb-install uses QEMU's built-in TFTP server (10.0.2.2:69) which serves the build/ directory automatically - no make busybox-serve / Python needed. Pass an HTTP URL to use wget instead: bb-install http://10.0.2.2:9090/busybox.


CoreShell CLI Completeness (M35)

M35 rounds out the CoreShell built-in command set so day-to-day shell usage does not require busybox.

New built-in commands

Command Description
rm [-r] [-f] <path> Remove file; -r recursive directory removal, -f suppress errors
rmdir <dir> Remove empty directory
touch <file> Create file if absent, update mtime otherwise
echo [-n] <text> Print text; -n suppresses trailing newline
mv <src> <dst> Rename/move (alias for rename)
cp <src> <dst> Alias for copy (recursive; wildcard-capable source)
ln [-s] <target> <link> Hard link or -s symbolic link (alias for link/symlink)
uptime Elapsed time since boot in Xd Xh Xm Xs format
source / . <file> Execute shell script in-place (no subprocess)
alias <name>=<val> Define command alias (persisted in session)
unalias <name> Remove alias

Alias expansion

All aliases are expanded before dispatch, so user-defined shortcuts work transparently for every built-in and external command.

lsproc enhancements

lsproc now displays per-process memory usage and a full status legend:

PID  NAME             STATE PRIV  STK_KB  MEM_KB  CWD
──────────────────────────────────────────────────────
  1  init             S     K        64      64   /
  2  shell            R     K       128     132   /disk/disk0/home/root
  3  compositor       S     K        64      64   /

Legend:
  STATE: R=Running  S=Sleeping  D=Disk-wait  Z=Zombie
  PRIV:  K=Kernel   U=User      *=own address space
  MEM_KB = kernel stack + task_brk heap (user tasks only)

SSH Client & Server (M36)

M36 adds a full SSH2 implementation, enabling encrypted remote access to TinyCoreOS from any OpenSSH-compatible client.

Architecture (src/kernel/net/ssh.hpp / ssh.cpp)

The SSH implementation lives entirely in the kernel (ring-0). The server (sshd) runs on the TCP server pool (tcp_srv_listen/tcp_srv_accept) with a small worker pool (up to 5 concurrent sessions), while client mode uses the active TCP path, allowing stable local self-connect tests (ssh localhost) and concurrent service operation.

Recent reliability/UX updates:
- Per-session SSH scratch buffers replaced shared static packet buffers to avoid client/server data races.
- KEX flow was adjusted so the client sends KEXINIT immediately after version exchange (prevents localhost deadlock).
- SSH banner identity is built from release macros (TCOS_PRODUCT_NAME, TCOS_PRODUCT_CODENAME, TCOS_PRODUCT_VERSION) with safe token sanitization.
- Interactive shell output is buffered/chunked (instead of per-byte sends) for better stability.
- SSH shell prompt is visibly different: (ssh) $.

Cryptography stack

Primitive Source Notes
X25519 key agreement Monocypher ECDH for key exchange (curve25519-sha256)
ChaCha20 stream cipher Monocypher chacha20-poly1305@openssh.com AEAD
Poly1305 MAC Monocypher Packet authentication
Ed25519 signatures Monocypher primitives + custom SHA-512 Standard RFC 8032 ssh-ed25519 host key
SHA-256 Custom (from scratch) KEX hash, fingerprint
SHA-512 Custom (from scratch) Ed25519 signing
CSPRNG CPUID-detected RDRAND → fallback xorshift64 seeded from timer::ticks() + heap address entropy

SSH protocol subset

Layer Algorithm Notes
Key exchange curve25519-sha256 RFC 8731
Host key ssh-ed25519 RFC 8709
Encryption chacha20-poly1305@openssh.com OpenSSH extension
Authentication password passwd/shadow aware; supports empty-shadow compatibility flow for existing users
Channel session + exec / interactive shell SSH_MSG_CHANNEL_DATA loop

Host key persistence

On first sshd launch the server generates a random Ed25519 key pair and saves the private key to /disk/disk0/etc/ssh/ssh_host_ed25519_key. Subsequent starts load the saved key so the host fingerprint remains stable across reboots.

Shell integration

SSH sessions execute commands via shell::exec_cmd() (exported from shell.cpp). Output is captured with console::set_char_hook, normalized to CRLF for terminal clients, and sent in buffered chunks.

Commands

sshd start                            # start SSH server on port 22 (background task)
sshd status                           # check state
sshd stop                             # stop service
sshd restart                          # restart service
ssh [user@]host [command]             # connect to remote SSH server; masked password prompt (`*`)

Usage example

# Host (QEMU port-forwarded to 2222):
ssh -p 2222 root@localhost
# → prompts for password
# → interactive CoreShell session over encrypted SSH

# Self-connect inside TinyCoreOS for local debugging:
ssh localhost

# Run a single command:
ssh -p 2222 root@localhost "lsproc"

Monocypher vendor library

Monocypher is downloaded once via:

make monocypher-get    # fetches monocypher.c + monocypher.h from GitHub (tag 4.0.2)

The library is compiled as a plain C translation unit with -std=c11 -Os -fno-builtin and linked into the kernel. It is checked as a build prerequisite → a missing monocypher.c produces a clear error with instructions.


AC97 Audio (M37)

M37 adds a complete AC97 audio subsystem, enabling sound output from the kernel and user-space programs.

Architecture (src/kernel/drivers/ac97.hpp / ac97.cpp)

The driver targets QEMU's emulated Intel ICH AC97 codec (-device AC97, PCI 0x8086:0x2415).

Hardware layout

Component BAR Size Purpose
Native Audio Mixer (NAM) BAR0 (I/O) 256 B Codec mixer registers: volume, sample rate
Native Audio Bus Master (NABM) BAR1 (I/O) 64 B DMA controller: BDL, CIV, LVI, SR, CR

DMA ring buffer

BDL: 32 → 8-byte entries
     [phys_addr : 32] [count : 16 (→ int16_t)] [flags : 16]

DMA buffers: 32 → 4096 bytes = 128 KiB contiguous (pmm::alloc_n_frames)
Audio format: 48000 Hz, 16-bit signed, stereo (4 bytes / frame)
Buffer duration: 4096 / 4 / 48000 ? 21 ms per buffer
Ring capacity:  32 → 21 ms ? 680 ms

Playback flow

  1. Copy PCM data into DMA buffers (up to 32 per batch)
  2. Write BDL physical address to NABM_PO_BDBAR
  3. Set NABM_PO_LVI = last filled buffer index
  4. Set NABM_PO_CR bit 0 (RPBM) → DMA starts
  5. Poll NABM_PO_SR bit 0 (DCH = halted) or yield via sched::yield()
  6. Repeat for remaining data

Variable Rate Audio (VRA)

If the codec reports VRA support (NAM Extended Audio ID bit 0), the driver enables it and programs the front DAC sample rate register to 48000 Hz.

/dev/dsp

Registered as a VFS inode with custom FileOps. Writing raw PCM bytes calls ac97::play_pcm() directly → compatible with simple OSS-style audio programs.

Shell commands

Command Description
beep [freq_hz] [ms] Play a square-wave beep; defaults: 880 Hz, 200 ms
volume [0-100] Get or set master volume (maps to AC97 6-bit attenuation)
play <file> Play a WAV or raw PCM file (16-bit stereo 48000 Hz)

WAV support: the play command detects RIFF/WAV headers, validates 16-bit stereo format, and skips the standard 44-byte header automatically.

QEMU setup

# Makefile already adds -device AC97 to QEMU_FLAGS
make run

# Inside the OS:
beep               # 880 Hz beep (tests audio output)
beep 440 1000      # A4 note for 1 second
volume 80          # reduce volume to 80%
play /disk/disk0/sounds/test.wav

Roadmap

Phase 1 -> Widget Toolkit & Screen System (M39->M40) ✅

Foundation of the entire GUI stack. Without this layer, no graphical component can exist.

ID Name Key goals
M39 Widget Toolkit (Intuition) libui widgets and layouts used in production CoreDesk/Control Panel apps (button/label/input/list/dialog patterns)
M40 Screen & Palette System Backdrop/wallpaper, palette reload, runtime cursor (arrow/wait), runtime mode switch (twm_set_mode) without restart

Phase 2 -> Workbench Core (M41->M43)

Desktop with icons, drawers, and top-screen menus: the heart of the AmigaOS-style experience.

ID Name Key goals
M41 Icon System Themed PNG/BMP icons with category fallbacks, transparent rendering, robust icon metadata parsing
M42 Workbench Desktop CoreDesk desktop: icons, drawer launch, taskbar/sysbar, context menu, dynamic "My stuff", reduced redundant redraws
M43 Menu System Global top-screen menu with multi-level submenus, disabled/check/radio items, Win/Ctrl+Esc activation, mouse+keyboard navigation

Phase 3 -> System Applications (M44->M47)

Daily workflow tools: shell, preferences, multimedia viewer, and dock.

ID Name Key goals
M44 Preferences System Single-window Control Panel: ScreenMode/Palette/Font/Sound/Input/Time/Startup + Default Apps, persisted to /disk/disk0/prefs/
M45 Shell Window (CLI) PTY slave terminal window; ANSI escape codes; dynamic resize (TIOCSWINSZ); 1000-line scrollback; mouse copy/paste; window cloning (Amiga+K)
M46 Dock / Application Bar Application dock (left or bottom edge); icons with tooltips; launch bounce animation; auto-hide; config in prefs/dock.prefs
M47 Multimedia Viewer Datatypes: PNG (stb_image), BMP, PCM/WAV (AC97); MultiView -> extension-based preview; zoom fit/1:1/2x; waveform for audio files

Phase 4 -> Polish & Tooling (M48->M50)

Visual polish, text editor, and system notifications.

ID Name Key goals
M48 GUI Text Editor Syntax highlighting (C, Shell, INI); line numbers; undo/redo (200 ops); regex Find & Replace; tabs (tab bar)
M49 Visual Theming Window skins: Classic Amiga (gray), AmigaOS4 Blue (metal), Dark; window animations (zoom-in/out, fold); PNG desktop wallpaper; .theme format (ZIP)
M50 Notifications & Clipboard Notification banner (top-right, auto-hide after 4s); priorities: info/success/error; system clipboard; SYS_CLIPBOARD_COPY/PASTE; clipboard history (10 entries)