feat: e100e Ethernet driver, ramdisk reorganization, shell rewrite, web server/client, and more

This commit is contained in:
2026-02-19 01:18:11 +01:00
parent 1a5d943649
commit d355d376f9
63 changed files with 5368 additions and 614 deletions
+66
View File
@@ -0,0 +1,66 @@
/*
* main.cpp
* cat - Display file contents
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: cat <filename>\n");
zenith::exit(1);
}
// Build VFS path. If the path already starts with "<digit>:", use as-is.
// Otherwise, prefix "0:/" to it.
char path[256];
bool hasPrefix = false;
if (args[0] >= '0' && args[0] <= '9' && args[1] == ':') {
hasPrefix = true;
}
int i = 0;
if (!hasPrefix) {
path[0] = '0'; path[1] = ':'; path[2] = '/';
i = 3;
}
int j = 0;
while (args[j] && i < 255) {
path[i++] = args[j++];
}
path[i] = '\0';
int handle = zenith::open(path);
if (handle < 0) {
zenith::print("cat: cannot open '");
zenith::print(args);
zenith::print("'\n");
zenith::exit(1);
}
uint64_t size = zenith::getsize(handle);
if (size == 0) {
zenith::close(handle);
zenith::exit(0);
}
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
buf[bytesRead] = '\0';
zenith::print((const char*)buf);
offset += bytesRead;
}
zenith::close(handle);
zenith::putchar('\n');
zenith::exit(0);
}
+27
View File
@@ -0,0 +1,27 @@
/*
* main.cpp
* clear - Clear terminal screen and framebuffer
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
// Clear the raw framebuffer (needed after graphical programs like DOOM)
Zenith::FbInfo fb;
zenith::fb_info(&fb);
uint8_t* pixels = (uint8_t*)zenith::fb_map();
if (pixels) {
for (uint64_t y = 0; y < fb.height; y++) {
uint32_t* row = (uint32_t*)(pixels + y * fb.pitch);
for (uint64_t x = 0; x < fb.width; x++) {
row[x] = 0x00000000;
}
}
}
// Reset the text console
zenith::print("\033[2J"); // Clear entire screen
zenith::print("\033[H"); // Move cursor to top-left
zenith::exit(0);
}
+56
View File
@@ -0,0 +1,56 @@
/*
* main.cpp
* date - Show current date and time
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static void print_int_padded(uint64_t n) {
if (n < 10) zenith::putchar('0');
print_int(n);
}
static const char* month_name(int m) {
static const char* months[] = {
"", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
if (m >= 1 && m <= 12) return months[m];
return "?";
}
extern "C" void _start() {
Zenith::DateTime dt;
zenith::gettime(&dt);
print_int(dt.Day);
zenith::putchar(' ');
zenith::print(month_name(dt.Month));
zenith::putchar(' ');
print_int(dt.Year);
zenith::print(", ");
print_int(dt.Hour);
zenith::putchar(':');
print_int_padded(dt.Minute);
zenith::putchar(':');
print_int_padded(dt.Second);
zenith::print(" UTC\n");
zenith::exit(0);
}
+495
View File
@@ -0,0 +1,495 @@
/*
* main.cpp
* DHCP client for ZenithOS
* Obtains network configuration automatically via DHCP (RFC 2131)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::memcpy;
using zenith::memset;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState st;
st.buf = buf;
st.pos = 0;
st.max = size > 0 ? size - 1 : 0;
while (*fmt) {
if (*fmt != '%') { pf_putc(&st, *fmt++); continue; }
fmt++;
char pad = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long val = va_arg(ap, int);
int neg = 0; unsigned long uval;
if (val < 0) { neg = 1; uval = (unsigned long)(-val); }
else uval = (unsigned long)val;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
break;
}
case 'c': { char c = (char)va_arg(ap, int); pf_putc(&st, c); break; }
case '%': pf_putc(&st, '%'); break;
default: pf_putc(&st, '%'); pf_putc(&st, *fmt); break;
}
if (*fmt) fmt++;
}
if (size > 0) {
if (st.pos < size) st.buf[st.pos] = '\0';
else st.buf[size - 1] = '\0';
}
return st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- DHCP constants ----
static constexpr uint8_t BOOTREQUEST = 1;
static constexpr uint8_t BOOTREPLY = 2;
static constexpr uint8_t HTYPE_ETH = 1;
static constexpr uint8_t HLEN_ETH = 6;
static constexpr uint16_t DHCP_SERVER_PORT = 67;
static constexpr uint16_t DHCP_CLIENT_PORT = 68;
static constexpr uint32_t DHCP_MAGIC = 0x63825363; // network byte order
static constexpr uint16_t BROADCAST_FLAG = 0x8000;
// DHCP message types
static constexpr uint8_t DHCPDISCOVER = 1;
static constexpr uint8_t DHCPOFFER = 2;
static constexpr uint8_t DHCPREQUEST = 3;
static constexpr uint8_t DHCPACK = 5;
static constexpr uint8_t DHCPNAK = 6;
// DHCP option codes
static constexpr uint8_t OPT_SUBNET = 1;
static constexpr uint8_t OPT_ROUTER = 3;
static constexpr uint8_t OPT_DNS = 6;
static constexpr uint8_t OPT_REQUESTED_IP = 50;
static constexpr uint8_t OPT_LEASE_TIME = 51;
static constexpr uint8_t OPT_MSG_TYPE = 53;
static constexpr uint8_t OPT_SERVER_ID = 54;
static constexpr uint8_t OPT_PARAM_LIST = 55;
static constexpr uint8_t OPT_END = 255;
// ---- DHCP packet structure ----
struct DhcpPacket {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t ciaddr;
uint32_t yiaddr;
uint32_t siaddr;
uint32_t giaddr;
uint8_t chaddr[16];
uint8_t sname[64];
uint8_t file[128];
uint8_t options[312];
} __attribute__((packed));
// ---- Byte order helpers (DHCP uses network byte order) ----
static uint16_t htons(uint16_t v) {
return (uint16_t)((v >> 8) | (v << 8));
}
static uint32_t htonl(uint32_t v) {
return ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) |
((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000u);
}
static uint32_t ntohl(uint32_t v) { return htonl(v); }
// ---- IP formatting ----
static void format_ip(char* buf, int size, uint32_t ip) {
const uint8_t* b = (const uint8_t*)&ip;
snprintf(buf, size, "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
}
static void format_mac(char* buf, int size, const uint8_t* mac) {
snprintf(buf, size, "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
// ---- Build DHCP packets ----
static uint32_t g_xid = 0x5A454E49; // "ZENI"
static void build_base(DhcpPacket* pkt, const uint8_t* mac) {
memset(pkt, 0, sizeof(DhcpPacket));
pkt->op = BOOTREQUEST;
pkt->htype = HTYPE_ETH;
pkt->hlen = HLEN_ETH;
pkt->xid = g_xid;
pkt->flags = htons(BROADCAST_FLAG);
memcpy(pkt->chaddr, mac, 6);
// Magic cookie
pkt->options[0] = 0x63;
pkt->options[1] = 0x82;
pkt->options[2] = 0x53;
pkt->options[3] = 0x63;
}
static int build_discover(DhcpPacket* pkt, const uint8_t* mac) {
build_base(pkt, mac);
int off = 4; // after magic cookie
// Option 53: DHCP Message Type = DISCOVER
pkt->options[off++] = OPT_MSG_TYPE;
pkt->options[off++] = 1;
pkt->options[off++] = DHCPDISCOVER;
// Option 55: Parameter Request List
pkt->options[off++] = OPT_PARAM_LIST;
pkt->options[off++] = 4;
pkt->options[off++] = OPT_SUBNET;
pkt->options[off++] = OPT_ROUTER;
pkt->options[off++] = OPT_DNS;
pkt->options[off++] = OPT_LEASE_TIME;
pkt->options[off++] = OPT_END;
return (int)sizeof(DhcpPacket) - 312 + off;
}
static int build_request(DhcpPacket* pkt, const uint8_t* mac,
uint32_t requestedIp, uint32_t serverId) {
build_base(pkt, mac);
int off = 4;
// Option 53: DHCP Message Type = REQUEST
pkt->options[off++] = OPT_MSG_TYPE;
pkt->options[off++] = 1;
pkt->options[off++] = DHCPREQUEST;
// Option 50: Requested IP Address
pkt->options[off++] = OPT_REQUESTED_IP;
pkt->options[off++] = 4;
memcpy(&pkt->options[off], &requestedIp, 4);
off += 4;
// Option 54: Server Identifier
pkt->options[off++] = OPT_SERVER_ID;
pkt->options[off++] = 4;
memcpy(&pkt->options[off], &serverId, 4);
off += 4;
// Option 55: Parameter Request List
pkt->options[off++] = OPT_PARAM_LIST;
pkt->options[off++] = 4;
pkt->options[off++] = OPT_SUBNET;
pkt->options[off++] = OPT_ROUTER;
pkt->options[off++] = OPT_DNS;
pkt->options[off++] = OPT_LEASE_TIME;
pkt->options[off++] = OPT_END;
return (int)sizeof(DhcpPacket) - 312 + off;
}
// ---- Parse DHCP options ----
struct DhcpOffer {
uint32_t offeredIp;
uint32_t serverId;
uint32_t subnetMask;
uint32_t router;
uint32_t dns;
uint32_t leaseTime;
uint8_t msgType;
bool valid;
};
static void parse_options(const DhcpPacket* pkt, DhcpOffer* offer) {
offer->offeredIp = pkt->yiaddr;
offer->serverId = 0;
offer->subnetMask = 0;
offer->router = 0;
offer->dns = 0;
offer->leaseTime = 0;
offer->msgType = 0;
offer->valid = false;
// Verify magic cookie
if (pkt->options[0] != 0x63 || pkt->options[1] != 0x82 ||
pkt->options[2] != 0x53 || pkt->options[3] != 0x63) {
return;
}
int off = 4;
while (off < 312) {
uint8_t code = pkt->options[off++];
if (code == OPT_END) break;
if (code == 0) continue; // pad
if (off >= 312) break;
uint8_t len = pkt->options[off++];
if (off + len > 312) break;
switch (code) {
case OPT_MSG_TYPE:
if (len >= 1) offer->msgType = pkt->options[off];
break;
case OPT_SUBNET:
if (len >= 4) memcpy(&offer->subnetMask, &pkt->options[off], 4);
break;
case OPT_ROUTER:
if (len >= 4) memcpy(&offer->router, &pkt->options[off], 4);
break;
case OPT_DNS:
if (len >= 4) memcpy(&offer->dns, &pkt->options[off], 4);
break;
case OPT_SERVER_ID:
if (len >= 4) memcpy(&offer->serverId, &pkt->options[off], 4);
break;
case OPT_LEASE_TIME:
if (len >= 4) {
uint32_t raw;
memcpy(&raw, &pkt->options[off], 4);
offer->leaseTime = ntohl(raw);
}
break;
}
off += len;
}
offer->valid = (offer->msgType != 0);
}
// ---- Broadcast destination ----
static constexpr uint32_t BROADCAST_IP = 0xFFFFFFFF;
// ---- Main ----
extern "C" void _start() {
char msg[256];
zenith::print("ZenithOS DHCP Client\n");
// 1. Get MAC address
Zenith::NetCfg origCfg;
zenith::get_netcfg(&origCfg);
char macStr[32];
format_mac(macStr, sizeof(macStr), origCfg.macAddress);
snprintf(msg, sizeof(msg), "MAC address: %s\n", macStr);
zenith::print(msg);
// 2. Set IP to 0.0.0.0 to allow broadcast send/receive
Zenith::NetCfg zeroCfg;
zeroCfg.ipAddress = 0;
zeroCfg.subnetMask = 0;
zeroCfg.gateway = 0;
zenith::set_netcfg(&zeroCfg);
// 3. Create UDP socket and bind to port 68
int fd = zenith::socket(Zenith::SOCK_UDP);
if (fd < 0) {
zenith::print("Error: failed to create UDP socket\n");
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
if (zenith::bind(fd, DHCP_CLIENT_PORT) < 0) {
zenith::print("Error: failed to bind to port 68\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 4. Send DISCOVER
DhcpPacket pkt;
int pktLen = build_discover(&pkt, origCfg.macAddress);
zenith::print("Sending DHCPDISCOVER...\n");
if (zenith::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
zenith::print("Error: failed to send DISCOVER\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 5. Wait for OFFER
DhcpPacket resp;
DhcpOffer offer;
uint64_t startMs = zenith::get_milliseconds();
bool gotOffer = false;
zenith::print("Waiting for DHCPOFFER...\n");
while (zenith::get_milliseconds() - startMs < 10000) {
uint32_t srcIp;
uint16_t srcPort;
int r = zenith::recvfrom(fd, (void*)&resp, sizeof(resp), &srcIp, &srcPort);
if (r > 0) {
if (resp.op == BOOTREPLY && resp.xid == g_xid) {
parse_options(&resp, &offer);
if (offer.valid && offer.msgType == DHCPOFFER) {
gotOffer = true;
break;
}
}
}
zenith::yield();
}
if (!gotOffer) {
zenith::print("Error: no DHCPOFFER received (timeout)\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
char ipStr[32];
format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), "Received OFFER: %s\n", ipStr);
zenith::print(msg);
// 6. Send REQUEST
pktLen = build_request(&pkt, origCfg.macAddress, offer.offeredIp, offer.serverId);
zenith::print("Sending DHCPREQUEST...\n");
if (zenith::sendto(fd, (const void*)&pkt, pktLen, BROADCAST_IP, DHCP_SERVER_PORT) < 0) {
zenith::print("Error: failed to send REQUEST\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 7. Wait for ACK
bool gotAck = false;
startMs = zenith::get_milliseconds();
zenith::print("Waiting for DHCPACK...\n");
while (zenith::get_milliseconds() - startMs < 10000) {
uint32_t srcIp;
uint16_t srcPort;
int r = zenith::recvfrom(fd, (void*)&resp, sizeof(resp), &srcIp, &srcPort);
if (r > 0) {
if (resp.op == BOOTREPLY && resp.xid == g_xid) {
parse_options(&resp, &offer);
if (offer.valid && offer.msgType == DHCPACK) {
gotAck = true;
break;
}
if (offer.valid && offer.msgType == DHCPNAK) {
zenith::print("Error: received DHCPNAK from server\n");
zenith::closesocket(fd);
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
}
}
zenith::yield();
}
zenith::closesocket(fd);
if (!gotAck) {
zenith::print("Error: no DHCPACK received (timeout)\n");
zenith::set_netcfg(&origCfg);
zenith::exit(1);
}
// 8. Apply configuration
Zenith::NetCfg newCfg;
newCfg.ipAddress = offer.offeredIp;
newCfg.subnetMask = offer.subnetMask;
newCfg.gateway = offer.router;
zenith::set_netcfg(&newCfg);
// 9. Print results
zenith::print("\nDHCP configuration applied:\n");
format_ip(ipStr, sizeof(ipStr), offer.offeredIp);
snprintf(msg, sizeof(msg), " IP Address: %s\n", ipStr);
zenith::print(msg);
format_ip(ipStr, sizeof(ipStr), offer.subnetMask);
snprintf(msg, sizeof(msg), " Subnet Mask: %s\n", ipStr);
zenith::print(msg);
format_ip(ipStr, sizeof(ipStr), offer.router);
snprintf(msg, sizeof(msg), " Gateway: %s\n", ipStr);
zenith::print(msg);
if (offer.dns != 0) {
format_ip(ipStr, sizeof(ipStr), offer.dns);
snprintf(msg, sizeof(msg), " DNS Server: %s\n", ipStr);
zenith::print(msg);
}
if (offer.leaseTime != 0) {
snprintf(msg, sizeof(msg), " Lease Time: %u seconds\n", offer.leaseTime);
zenith::print(msg);
}
zenith::exit(0);
}
+2 -2
View File
@@ -163,14 +163,14 @@ ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
# ---- Target ----
TARGET := $(BINDIR)/doom.elf
TARGET := $(BINDIR)/games/doom.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)
mkdir -p $(BINDIR)/games
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
# DOOM source files (from doomgeneric directory)
+1 -1
View File
@@ -222,7 +222,7 @@ void DG_SetWindowTitle(const char* title) {
/* ---- Entry point ---- */
void _start(void) {
char *argv[] = { "doom", "-iwad", "0:/doom1.wad", 0 };
char *argv[] = { "doom", "-iwad", "0:/games/doom1.wad", 0 };
doomgeneric_Create(3, argv);
for (;;) {
doomgeneric_Tick();
+832
View File
@@ -0,0 +1,832 @@
/*
* main.cpp
* edit - Text editor for ZenithOS
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::memcpy;
using zenith::memmove;
// ---- Integer to string ----
static int itoa(int val, char* buf) {
if (val == 0) { buf[0] = '0'; buf[1] = '\0'; return 1; }
char tmp[12];
int i = 0;
bool neg = false;
if (val < 0) { neg = true; val = -val; }
while (val > 0) { tmp[i++] = '0' + val % 10; val /= 10; }
int len = 0;
if (neg) buf[len++] = '-';
for (int j = i - 1; j >= 0; j--) buf[len++] = tmp[j];
buf[len] = '\0';
return len;
}
// ---- Terminal output helpers ----
static void print(const char* s) { zenith::print(s); }
static void putch(char c) { zenith::putchar(c); }
static void print_int(int v) {
char buf[12];
itoa(v, buf);
print(buf);
}
// ANSI escape helpers
static void esc(const char* seq) {
putch('\033');
putch('[');
print(seq);
}
static void cursor_to(int row, int col) {
putch('\033'); putch('[');
print_int(row); putch(';');
print_int(col); putch('H');
}
static void clear_line() { esc("2K"); }
static void hide_cursor() { esc("?25l"); }
static void show_cursor() { esc("?25h"); }
static void enter_alt_screen() { esc("?1049h"); }
static void exit_alt_screen() { esc("?1049l"); }
static void reset_attrs() { esc("0m"); }
static void reverse_video() { esc("7m"); }
static void dim_text() { esc("2m"); }
// ---- Line buffer ----
struct Line {
char* data;
int len;
int cap;
};
static constexpr int MAX_LINES = 10000;
static constexpr int INITIAL_LINE_CAP = 64;
static constexpr int TAB_WIDTH = 4;
static Line* lines = nullptr;
static int numLines = 0;
// Editor state
static int cursorRow = 0; // cursor position in document (0-indexed)
static int cursorCol = 0;
static int topLine = 0; // first visible line
static int leftCol = 0; // horizontal scroll offset
static int screenRows = 24;
static int screenCols = 80;
static int editorRows = 0; // screenRows - 2 (status + hint bars)
static int gutterWidth = 4; // line number gutter width
static bool modified = false;
static bool running = true;
static bool fullRedraw = true;
static char filename[256] = "";
static bool hasFilename = false;
// Search state
static char searchQuery[128] = "";
static int searchLen = 0;
// Status message
static char statusMsg[128] = "";
static uint64_t statusMsgTime = 0;
// ---- Line operations ----
static void line_init(Line* ln) {
ln->cap = INITIAL_LINE_CAP;
ln->data = (char*)zenith::malloc(ln->cap);
ln->len = 0;
ln->data[0] = '\0';
}
static void line_ensure(Line* ln, int needed) {
if (needed + 1 <= ln->cap) return;
int newCap = ln->cap;
while (newCap < needed + 1) newCap *= 2;
ln->data = (char*)zenith::realloc(ln->data, newCap);
ln->cap = newCap;
}
static void line_insert_char(Line* ln, int pos, char c) {
if (pos < 0) pos = 0;
if (pos > ln->len) pos = ln->len;
line_ensure(ln, ln->len + 1);
memmove(ln->data + pos + 1, ln->data + pos, ln->len - pos);
ln->data[pos] = c;
ln->len++;
ln->data[ln->len] = '\0';
}
static void line_delete_char(Line* ln, int pos) {
if (pos < 0 || pos >= ln->len) return;
memmove(ln->data + pos, ln->data + pos + 1, ln->len - pos - 1);
ln->len--;
ln->data[ln->len] = '\0';
}
static void line_append(Line* ln, const char* s, int slen) {
line_ensure(ln, ln->len + slen);
memcpy(ln->data + ln->len, s, slen);
ln->len += slen;
ln->data[ln->len] = '\0';
}
// ---- Document operations ----
static void insert_line(int at) {
if (numLines >= MAX_LINES) return;
if (at < 0) at = 0;
if (at > numLines) at = numLines;
// Shift lines down
for (int i = numLines; i > at; i--) {
lines[i] = lines[i - 1];
}
line_init(&lines[at]);
numLines++;
}
static void delete_line(int at) {
if (at < 0 || at >= numLines) return;
if (numLines <= 1) {
// Don't delete the last line, just clear it
lines[at].len = 0;
lines[at].data[0] = '\0';
return;
}
zenith::mfree(lines[at].data);
for (int i = at; i < numLines - 1; i++) {
lines[i] = lines[i + 1];
}
numLines--;
}
// ---- Compute gutter width from line count ----
static void update_gutter_width() {
int digits = 1;
int n = numLines;
while (n >= 10) { digits++; n /= 10; }
gutterWidth = digits + 2; // digits + space + separator
if (gutterWidth < 4) gutterWidth = 4;
}
// ---- File I/O ----
static void set_status(const char* msg) {
int i = 0;
while (msg[i] && i < 126) { statusMsg[i] = msg[i]; i++; }
statusMsg[i] = '\0';
statusMsgTime = zenith::get_milliseconds();
}
// Build VFS path from filename
static void build_path(const char* fname, char* out, int outMax) {
int i = 0;
// Check if already has drive prefix
bool hasPrefix = (fname[0] >= '0' && fname[0] <= '9' && fname[1] == ':');
if (!hasPrefix) {
out[i++] = '0'; out[i++] = ':'; out[i++] = '/';
}
int j = 0;
while (fname[j] && i < outMax - 1) {
out[i++] = fname[j++];
}
out[i] = '\0';
}
static void load_file(const char* fname) {
char path[256];
build_path(fname, path, sizeof(path));
int handle = zenith::open(path);
if (handle < 0) {
// New file
numLines = 1;
line_init(&lines[0]);
set_status("(New file)");
return;
}
uint64_t size = zenith::getsize(handle);
// Read entire file into a temp buffer
uint8_t* buf = nullptr;
if (size > 0) {
buf = (uint8_t*)zenith::malloc(size + 1);
uint64_t off = 0;
while (off < size) {
int r = zenith::read(handle, buf + off, off, size - off);
if (r <= 0) break;
off += r;
}
buf[size] = '\0';
}
zenith::close(handle);
// Parse into lines
numLines = 0;
if (buf && size > 0) {
uint64_t lineStart = 0;
for (uint64_t i = 0; i <= size; i++) {
if (i == size || buf[i] == '\n') {
if (numLines >= MAX_LINES) break;
line_init(&lines[numLines]);
int lineLen = (int)(i - lineStart);
if (lineLen > 0) {
line_append(&lines[numLines], (const char*)buf + lineStart, lineLen);
}
numLines++;
lineStart = i + 1;
}
}
}
if (buf) zenith::mfree(buf);
if (numLines == 0) {
numLines = 1;
line_init(&lines[0]);
}
set_status("File loaded");
}
static bool save_file() {
if (!hasFilename) {
set_status("No filename! Use Ctrl+S after setting a name");
return false;
}
char path[256];
build_path(filename, path, sizeof(path));
// Calculate total size
uint64_t totalSize = 0;
for (int i = 0; i < numLines; i++) {
totalSize += lines[i].len;
if (i < numLines - 1) totalSize++; // newline
}
// Try to open existing file first
int handle = zenith::open(path);
if (handle < 0) {
// Create new file
handle = zenith::fcreate(path);
if (handle < 0) {
set_status("Error: could not create file");
return false;
}
}
// Build content buffer and write
uint8_t* buf = (uint8_t*)zenith::malloc(totalSize + 1);
uint64_t off = 0;
for (int i = 0; i < numLines; i++) {
if (lines[i].len > 0) {
memcpy(buf + off, lines[i].data, lines[i].len);
off += lines[i].len;
}
if (i < numLines - 1) {
buf[off++] = '\n';
}
}
int result = zenith::fwrite(handle, buf, 0, totalSize);
zenith::close(handle);
zenith::mfree(buf);
if (result < 0) {
set_status("Error: write failed");
return false;
}
modified = false;
set_status("File saved");
return true;
}
// ---- Prompt for input in the hint bar area ----
static int prompt_input(const char* promptStr, char* out, int outMax) {
// Draw prompt on the last row
cursor_to(screenRows, 1);
reverse_video();
clear_line();
print(promptStr);
reset_attrs();
show_cursor();
int pos = 0;
out[0] = '\0';
while (true) {
if (!zenith::is_key_available()) {
zenith::yield();
continue;
}
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
if (ev.ascii == '\033' || (ev.ctrl && ev.ascii == 'q')) {
// Cancel
return -1;
}
if (ev.ascii == '\n') {
out[pos] = '\0';
return pos;
}
if (ev.ascii == '\b') {
if (pos > 0) {
pos--;
putch('\b'); putch(' '); putch('\b');
}
} else if (ev.ascii >= ' ' && pos < outMax - 1) {
out[pos++] = ev.ascii;
out[pos] = '\0';
putch(ev.ascii);
}
}
}
// ---- Rendering ----
static void draw_status_bar() {
cursor_to(1, 1);
reverse_video();
clear_line();
// Left side: "edit: filename [+]"
print(" edit: ");
if (hasFilename) {
print(filename);
} else {
print("[No Name]");
}
if (modified) print(" [+]");
// Right side: cursor position
// Calculate right-side text
char posBuf[32];
int p = 0;
posBuf[p++] = 'L';
posBuf[p++] = 'n';
posBuf[p++] = ' ';
p += itoa(cursorRow + 1, posBuf + p);
posBuf[p++] = ',';
posBuf[p++] = ' ';
posBuf[p++] = 'C';
posBuf[p++] = 'o';
posBuf[p++] = 'l';
posBuf[p++] = ' ';
p += itoa(cursorCol + 1, posBuf + p);
posBuf[p++] = ' ';
posBuf[p++] = ' ';
posBuf[p] = '\0';
// Pad to right-align
// We need to figure out how many chars we've printed on the left
int leftLen = 8 + (hasFilename ? slen(filename) : 9) + (modified ? 4 : 0);
int rightLen = p;
int padding = screenCols - leftLen - rightLen;
for (int i = 0; i < padding; i++) putch(' ');
print(posBuf);
reset_attrs();
}
static void draw_hint_bar() {
cursor_to(screenRows, 1);
reverse_video();
clear_line();
// Show status message if recent (within 3 seconds)
uint64_t now = zenith::get_milliseconds();
if (statusMsg[0] && (now - statusMsgTime) < 3000) {
print(" ");
print(statusMsg);
} else {
print(" ^S Save ^Q Quit ^F Find ^G Find Next");
}
// Pad rest of line
reset_attrs();
}
static void draw_line(int screenRow, int docLine) {
cursor_to(screenRow + 2, 1); // +2 because row 1 is status bar
clear_line();
if (docLine < numLines) {
// Line number gutter
dim_text();
char numBuf[12];
int numLen = itoa(docLine + 1, numBuf);
// Right-align the number in the gutter
int pad = gutterWidth - 2 - numLen; // -2 for trailing space+pipe
for (int i = 0; i < pad; i++) putch(' ');
print(numBuf);
putch(' ');
reset_attrs();
// Line content
Line* ln = &lines[docLine];
int startCol = leftCol;
int maxChars = screenCols - gutterWidth;
for (int c = 0; c < maxChars && startCol + c < ln->len; c++) {
putch(ln->data[startCol + c]);
}
} else {
// Past end of file
dim_text();
putch('~');
reset_attrs();
}
}
static void render() {
hide_cursor();
update_gutter_width();
draw_status_bar();
if (fullRedraw) {
for (int i = 0; i < editorRows; i++) {
draw_line(i, topLine + i);
}
fullRedraw = false;
}
draw_hint_bar();
// Position cursor
int screenY = cursorRow - topLine + 2; // +2 for status bar
int screenX = cursorCol - leftCol + gutterWidth;
cursor_to(screenY, screenX);
show_cursor();
}
// ---- Scrolling ----
static void scroll() {
// Vertical scrolling
if (cursorRow < topLine) {
topLine = cursorRow;
fullRedraw = true;
}
if (cursorRow >= topLine + editorRows) {
topLine = cursorRow - editorRows + 1;
fullRedraw = true;
}
// Horizontal scrolling
int textCols = screenCols - gutterWidth;
if (cursorCol < leftCol) {
leftCol = cursorCol;
fullRedraw = true;
}
if (cursorCol >= leftCol + textCols) {
leftCol = cursorCol - textCols + 1;
fullRedraw = true;
}
}
// ---- Editing operations ----
static void insert_char(char c) {
line_insert_char(&lines[cursorRow], cursorCol, c);
cursorCol++;
modified = true;
fullRedraw = true;
}
static void insert_tab() {
for (int i = 0; i < TAB_WIDTH; i++) {
insert_char(' ');
}
}
static void insert_newline() {
Line* current = &lines[cursorRow];
// Split current line at cursor position
insert_line(cursorRow + 1);
// Re-get pointer since insert_line shifted memory
current = &lines[cursorRow];
Line* newLine = &lines[cursorRow + 1];
// Move text after cursor to new line
if (cursorCol < current->len) {
line_append(newLine, current->data + cursorCol, current->len - cursorCol);
current->len = cursorCol;
current->data[current->len] = '\0';
}
cursorRow++;
cursorCol = 0;
modified = true;
fullRedraw = true;
}
static void delete_char_backspace() {
if (cursorCol > 0) {
line_delete_char(&lines[cursorRow], cursorCol - 1);
cursorCol--;
modified = true;
fullRedraw = true;
} else if (cursorRow > 0) {
// Join with previous line
int prevLen = lines[cursorRow - 1].len;
line_append(&lines[cursorRow - 1], lines[cursorRow].data, lines[cursorRow].len);
delete_line(cursorRow);
cursorRow--;
cursorCol = prevLen;
modified = true;
fullRedraw = true;
}
}
static void delete_char_forward() {
if (cursorCol < lines[cursorRow].len) {
line_delete_char(&lines[cursorRow], cursorCol);
modified = true;
fullRedraw = true;
} else if (cursorRow < numLines - 1) {
// Join with next line
line_append(&lines[cursorRow], lines[cursorRow + 1].data, lines[cursorRow + 1].len);
delete_line(cursorRow + 1);
modified = true;
fullRedraw = true;
}
}
// ---- Search ----
static void find_next(bool fromPrompt) {
if (searchLen == 0) return;
// Start search from cursor position + 1
int startRow = cursorRow;
int startCol = cursorCol + (fromPrompt ? 0 : 1);
for (int i = 0; i < numLines; i++) {
int row = (startRow + i) % numLines;
int colStart = (i == 0) ? startCol : 0;
Line* ln = &lines[row];
for (int c = colStart; c <= ln->len - searchLen; c++) {
bool match = true;
for (int k = 0; k < searchLen; k++) {
if (ln->data[c + k] != searchQuery[k]) {
match = false;
break;
}
}
if (match) {
cursorRow = row;
cursorCol = c;
fullRedraw = true;
set_status("Found");
return;
}
}
}
set_status("Not found");
}
static void do_search() {
char query[128];
int len = prompt_input("Search: ", query, sizeof(query));
if (len < 0) {
fullRedraw = true;
return;
}
if (len == 0) {
fullRedraw = true;
return;
}
// Copy query
for (int i = 0; i < len; i++) searchQuery[i] = query[i];
searchQuery[len] = '\0';
searchLen = len;
find_next(true);
fullRedraw = true;
}
// ---- Navigation scancodes ----
static constexpr uint8_t SC_UP = 0x48;
static constexpr uint8_t SC_DOWN = 0x50;
static constexpr uint8_t SC_LEFT = 0x4B;
static constexpr uint8_t SC_RIGHT = 0x4D;
static constexpr uint8_t SC_HOME = 0x47;
static constexpr uint8_t SC_END = 0x4F;
static constexpr uint8_t SC_PGUP = 0x49;
static constexpr uint8_t SC_PGDN = 0x51;
static constexpr uint8_t SC_DELETE = 0x53;
// ---- Input handling ----
static void handle_key(const Zenith::KeyEvent& ev) {
if (!ev.pressed) return;
// Ctrl key combinations
if (ev.ctrl) {
switch (ev.ascii) {
case 'q':
if (modified) {
set_status("Unsaved changes! Press Ctrl+Q again to quit");
// Set a flag so next Ctrl+Q quits
static bool warnedOnce = false;
if (warnedOnce) {
running = false;
}
warnedOnce = true;
return;
}
running = false;
return;
case 's':
if (!hasFilename) {
char nameBuf[256];
int len = prompt_input("Save as: ", nameBuf, sizeof(nameBuf));
if (len > 0) {
for (int i = 0; i <= len; i++) filename[i] = nameBuf[i];
hasFilename = true;
}
fullRedraw = true;
if (!hasFilename) return;
}
save_file();
fullRedraw = true;
return;
case 'f':
do_search();
return;
case 'g':
find_next(false);
return;
default:
break;
}
}
// Non-ASCII keys (scancode-based)
if (ev.ascii == 0) {
switch (ev.scancode) {
case SC_UP:
if (cursorRow > 0) {
cursorRow--;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
}
return;
case SC_DOWN:
if (cursorRow < numLines - 1) {
cursorRow++;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
}
return;
case SC_LEFT:
if (cursorCol > 0) {
cursorCol--;
} else if (cursorRow > 0) {
cursorRow--;
cursorCol = lines[cursorRow].len;
}
fullRedraw = true;
return;
case SC_RIGHT:
if (cursorCol < lines[cursorRow].len) {
cursorCol++;
} else if (cursorRow < numLines - 1) {
cursorRow++;
cursorCol = 0;
}
fullRedraw = true;
return;
case SC_HOME:
cursorCol = 0;
fullRedraw = true;
return;
case SC_END:
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_PGUP:
cursorRow -= editorRows;
if (cursorRow < 0) cursorRow = 0;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_PGDN:
cursorRow += editorRows;
if (cursorRow >= numLines) cursorRow = numLines - 1;
if (cursorCol > lines[cursorRow].len)
cursorCol = lines[cursorRow].len;
fullRedraw = true;
return;
case SC_DELETE:
delete_char_forward();
return;
default:
return;
}
}
// Regular keys
switch (ev.ascii) {
case '\n':
insert_newline();
break;
case '\b':
delete_char_backspace();
break;
case '\t':
insert_tab();
break;
default:
if (ev.ascii >= ' ') {
insert_char(ev.ascii);
}
break;
}
}
// ---- Entry point ----
extern "C" void _start() {
// Allocate line buffer
lines = (Line*)zenith::malloc(sizeof(Line) * MAX_LINES);
// Get terminal size
zenith::termsize(&screenCols, &screenRows);
editorRows = screenRows - 2;
// Parse arguments
char args[256];
int argLen = zenith::getargs(args, sizeof(args));
if (argLen > 0 && args[0] != '\0') {
// Copy filename
int i = 0;
while (args[i] && i < 255) { filename[i] = args[i]; i++; }
filename[i] = '\0';
hasFilename = true;
load_file(filename);
} else {
// New empty buffer
numLines = 1;
line_init(&lines[0]);
}
// Enter alternate screen
enter_alt_screen();
fullRedraw = true;
// Main loop
while (running) {
scroll();
render();
// Wait for input
while (!zenith::is_key_available()) {
zenith::yield();
}
Zenith::KeyEvent ev;
zenith::getkey(&ev);
handle_key(ev);
}
// Exit alternate screen
exit_alt_screen();
show_cursor();
reset_attrs();
zenith::exit(0);
}
+359
View File
@@ -0,0 +1,359 @@
/*
* main.cpp
* HTTP/1.0 client for ZenithOS
* Usage: run fetch.elf <server_ip> <port> <path>
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::skip_spaces;
using zenith::memcpy;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState st;
st.buf = buf;
st.pos = 0;
st.max = size > 0 ? size - 1 : 0;
while (*fmt) {
if (*fmt != '%') { pf_putc(&st, *fmt++); continue; }
fmt++;
char pad = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long val = va_arg(ap, int);
int neg = 0; unsigned long uval;
if (val < 0) { neg = 1; uval = (unsigned long)(-val); }
else uval = (unsigned long)val;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
break;
}
case 'c': { char c = (char)va_arg(ap, int); pf_putc(&st, c); break; }
case '%': pf_putc(&st, '%'); break;
default: pf_putc(&st, '%'); pf_putc(&st, *fmt); break;
}
if (*fmt) fmt++;
}
if (size > 0) {
if (st.pos < size) st.buf[st.pos] = '\0';
else st.buf[size - 1] = '\0';
}
return st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- IP/port parsing ----
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0; hasDigit = false;
if (c == '\0') break;
} else return false;
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
// ---- HTTP response parser ----
// Find "\r\n\r\n" boundary between headers and body
static int find_header_end(const char* buf, int len) {
for (int i = 0; i + 3 < len; i++) {
if (buf[i] == '\r' && buf[i+1] == '\n' && buf[i+2] == '\r' && buf[i+3] == '\n')
return i + 4;
}
return -1;
}
// Extract HTTP status code from first line: "HTTP/1.x NNN ..."
static int parse_status_code(const char* buf, int len) {
// Find first space
int i = 0;
while (i < len && buf[i] != ' ') i++;
if (i >= len) return -1;
i++; // skip space
// Parse 3-digit status
if (i + 2 >= len) return -1;
if (buf[i] < '0' || buf[i] > '9') return -1;
int code = (buf[i] - '0') * 100 + (buf[i+1] - '0') * 10 + (buf[i+2] - '0');
return code;
}
// Extract status text from first line: "HTTP/1.x NNN Status Text\r\n"
static void parse_status_text(const char* buf, int len, char* out, int outMax) {
// Skip "HTTP/1.x NNN "
int i = 0;
while (i < len && buf[i] != ' ') i++;
i++; // skip first space (after HTTP/1.x)
while (i < len && buf[i] != ' ') i++;
i++; // skip second space (after status code)
// Copy until \r or end
int j = 0;
while (i < len && buf[i] != '\r' && buf[i] != '\n' && j < outMax - 1) {
out[j++] = buf[i++];
}
out[j] = '\0';
}
// ---- Main ----
extern "C" void _start() {
// Parse arguments: <server_ip> <port> <path>
char argbuf[512];
zenith::getargs(argbuf, sizeof(argbuf));
const char* arg = skip_spaces(argbuf);
if (*arg == '\0') {
zenith::print("Usage: fetch.elf <server_ip> <port> <path>\n");
zenith::print("Example: run fetch.elf 10.0.68.1 80 /\n");
zenith::print(" run fetch.elf 93.184.216.34 80 /index.html\n");
zenith::exit(0);
}
// Parse IP
char ipStr[32];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 31) { ipStr[i] = arg[i]; i++; }
ipStr[i] = '\0';
arg = skip_spaces(arg + i);
uint32_t serverIp;
if (!parse_ip(ipStr, &serverIp)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse port
char portStr[16];
i = 0;
while (arg[i] && arg[i] != ' ' && i < 15) { portStr[i] = arg[i]; i++; }
portStr[i] = '\0';
arg = skip_spaces(arg + i);
uint16_t serverPort;
if (!parse_uint16(portStr, &serverPort)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse path (rest of args, default to "/" if empty)
char path[256];
if (*arg) {
i = 0;
while (arg[i] && i < 255) { path[i] = arg[i]; i++; }
path[i] = '\0';
} else {
path[0] = '/'; path[1] = '\0';
}
// Print connection info
char msg[256];
snprintf(msg, sizeof(msg), "Connecting to %s:%d...\n", ipStr, (int)serverPort);
zenith::print(msg);
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
// Connect
if (zenith::connect(fd, serverIp, serverPort) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
// Build and send HTTP request
char request[512];
int reqLen = snprintf(request, sizeof(request),
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"User-Agent: ZenithOS/1.0\r\n"
"Connection: close\r\n"
"\r\n",
path, ipStr);
snprintf(msg, sizeof(msg), "GET %s\n", path);
zenith::print(msg);
if (zenith::send(fd, request, reqLen) < 0) {
zenith::print("Error: failed to send request\n");
zenith::closesocket(fd);
zenith::exit(1);
}
// Receive response
// We accumulate the full response to parse headers, then print the body.
// Use a large static buffer (32 KB) since we can't save to files anyway.
static char respBuf[32768];
int respLen = 0;
bool aborted = false;
int idleCount = 0;
while (respLen < (int)sizeof(respBuf) - 1) {
// Check for Ctrl+Q to abort
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
aborted = true;
break;
}
}
int r = zenith::recv(fd, respBuf + respLen, sizeof(respBuf) - 1 - respLen);
if (r > 0) {
respLen += r;
idleCount = 0;
} else if (r == 0) {
// Connection closed by server — done
break;
} else {
// No data available or error
idleCount++;
if (idleCount > 2000) {
// Assume connection closed after extended idle
break;
}
zenith::yield();
}
}
respBuf[respLen] = '\0';
zenith::closesocket(fd);
if (aborted) {
zenith::print("\nAborted.\n");
zenith::exit(0);
}
if (respLen == 0) {
zenith::print("Error: empty response\n");
zenith::exit(1);
}
// Parse response headers
int headerEnd = find_header_end(respBuf, respLen);
if (headerEnd < 0) {
// No proper header/body separator — just print everything
zenith::print("Warning: malformed response (no header boundary)\n\n");
zenith::print(respBuf);
zenith::putchar('\n');
zenith::exit(0);
}
int statusCode = parse_status_code(respBuf, headerEnd);
char statusText[64];
parse_status_text(respBuf, headerEnd, statusText, sizeof(statusText));
int bodyLen = respLen - headerEnd;
// Print summary
snprintf(msg, sizeof(msg), "HTTP/1.0 %d %s (%d bytes)\n\n", statusCode, statusText, bodyLen);
zenith::print(msg);
// Print body — it may contain null bytes in binary content, but for text we
// can just print as a string. For binary, we print what we can.
if (bodyLen > 0) {
// Print body in chunks (print expects null-terminated)
const char* body = respBuf + headerEnd;
int printed = 0;
char chunk[512];
while (printed < bodyLen) {
int n = bodyLen - printed;
if (n > (int)sizeof(chunk) - 1) n = (int)sizeof(chunk) - 1;
memcpy(chunk, body + printed, n);
chunk[n] = '\0';
zenith::print(chunk);
printed += n;
}
zenith::putchar('\n');
}
zenith::exit(0);
}
+562
View File
@@ -0,0 +1,562 @@
/*
* main.cpp
* HTTP/1.0 server for ZenithOS
* Usage: run httpd.elf [port] (default: 80)
* Serves a built-in index page and files from the VFS
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
// ---- Minimal snprintf (no libc available) ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState {
char* buf;
int pos;
int max;
};
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24];
int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState st;
st.buf = buf;
st.pos = 0;
st.max = size > 0 ? size - 1 : 0;
while (*fmt) {
if (*fmt != '%') { pf_putc(&st, *fmt++); continue; }
fmt++;
char pad = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long val = va_arg(ap, int);
int neg = 0; unsigned long uval;
if (val < 0) { neg = 1; uval = (unsigned long)(-val); }
else uval = (unsigned long)val;
pf_putnum(&st, uval, 10, width, pad, neg);
break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*);
if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
break;
}
case 'c': { char c = (char)va_arg(ap, int); pf_putc(&st, c); break; }
case '%': pf_putc(&st, '%'); break;
default: pf_putc(&st, '%'); pf_putc(&st, *fmt); break;
}
if (*fmt) fmt++;
}
if (size > 0) {
if (st.pos < size) st.buf[st.pos] = '\0';
else st.buf[size - 1] = '\0';
}
return st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap);
return ret;
}
// ---- IP/port parsing ----
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
// ---- Content type detection ----
static bool ends_with(const char* str, const char* suffix) {
int sl = slen(str);
int xl = slen(suffix);
if (xl > sl) return false;
for (int i = 0; i < xl; i++) {
char a = str[sl - xl + i];
char b = suffix[i];
// Case-insensitive for file extensions
if (a >= 'A' && a <= 'Z') a += 32;
if (b >= 'A' && b <= 'Z') b += 32;
if (a != b) return false;
}
return true;
}
static const char* content_type_for(const char* path) {
if (ends_with(path, ".html") || ends_with(path, ".htm"))
return "text/html";
if (ends_with(path, ".txt"))
return "text/plain";
if (ends_with(path, ".css"))
return "text/css";
if (ends_with(path, ".js"))
return "application/javascript";
return "application/octet-stream";
}
// ---- HTTP response helpers ----
// Send a complete HTTP response with headers and body
static void send_response(int clientFd, int statusCode, const char* statusText,
const char* contentType, const char* body, int bodyLen) {
char header[512];
int hlen = snprintf(header, sizeof(header),
"HTTP/1.0 %d %s\r\n"
"Content-Type: %s\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"Server: ZenithOS/1.0\r\n"
"\r\n",
statusCode, statusText, contentType, bodyLen);
zenith::send(clientFd, header, hlen);
if (bodyLen > 0) {
zenith::send(clientFd, body, bodyLen);
}
}
// Send a file from the VFS
static int send_file_response(int clientFd, const char* vfsPath, const char* urlPath) {
int handle = zenith::open(vfsPath);
if (handle < 0) return -1;
uint64_t size = zenith::getsize(handle);
const char* ctype = content_type_for(urlPath);
// Send header
char header[512];
int hlen = snprintf(header, sizeof(header),
"HTTP/1.0 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %u\r\n"
"Connection: close\r\n"
"Server: ZenithOS/1.0\r\n"
"\r\n",
ctype, (unsigned)size);
zenith::send(clientFd, header, hlen);
// Send file body in chunks
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf)) chunk = sizeof(buf);
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
zenith::send(clientFd, buf, bytesRead);
offset += bytesRead;
}
zenith::close(handle);
return (int)size;
}
// ---- Request parsing ----
// Extract the request path from "GET /path HTTP/1.x\r\n..."
// Returns length of path written, or -1 on parse error
static int parse_request_path(const char* req, int reqLen, char* pathOut, int pathMax) {
// Find "GET "
if (reqLen < 4) return -1;
if (req[0] != 'G' || req[1] != 'E' || req[2] != 'T' || req[3] != ' ')
return -1;
int i = 4;
int j = 0;
while (i < reqLen && req[i] != ' ' && req[i] != '\r' && req[i] != '\n') {
if (j < pathMax - 1) pathOut[j++] = req[i];
i++;
}
pathOut[j] = '\0';
return j;
}
// ---- Logging ----
static void log_request(const char* method, const char* path, int status, int bodyLen) {
// Get timestamp
Zenith::DateTime dt;
zenith::gettime(&dt);
char msg[256];
snprintf(msg, sizeof(msg), "[%02d:%02d:%02d] %s %s -> %d (%d bytes)\n",
(int)dt.Hour, (int)dt.Minute, (int)dt.Second,
method, path, status, bodyLen);
zenith::print(msg);
}
// ---- Page generators ----
static int generate_index_page(char* buf, int bufSize) {
Zenith::SysInfo info;
zenith::get_info(&info);
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
uint64_t hours = mins / 60;
secs %= 60;
mins %= 60;
return snprintf(buf, bufSize,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>ZenithOS Web Server</title></head>\n"
"<body>\n"
"<h1>ZenithOS Web Server</h1>\n"
"<p>Welcome! This page is being served by <b>httpd</b> running on ZenithOS.</p>\n"
"<h2>System Information</h2>\n"
"<table>\n"
"<tr><td><b>OS:</b></td><td>%s</td></tr>\n"
"<tr><td><b>Version:</b></td><td>%s</td></tr>\n"
"<tr><td><b>Uptime:</b></td><td>%uh %um %us</td></tr>\n"
"</table>\n"
"<h2>Browse Files</h2>\n"
"<p><a href=\"/files/\">Browse VFS files</a></p>\n"
"</body>\n"
"</html>\n",
info.osName, info.osVersion,
(unsigned)hours, (unsigned)mins, (unsigned)secs);
}
static int generate_404_page(char* buf, int bufSize, const char* path) {
return snprintf(buf, bufSize,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>404 Not Found</title></head>\n"
"<body>\n"
"<h1>404 Not Found</h1>\n"
"<p>The requested path <code>%s</code> was not found on this server.</p>\n"
"<p><a href=\"/\">Back to home</a></p>\n"
"</body>\n"
"</html>\n",
path);
}
static int generate_dir_listing(char* buf, int bufSize, const char* urlPath, const char* vfsDir) {
int pos = 0;
pos += snprintf(buf + pos, bufSize - pos,
"<!DOCTYPE html>\n"
"<html>\n"
"<head><title>Index of %s</title></head>\n"
"<body>\n"
"<h1>Index of %s</h1>\n"
"<hr>\n"
"<ul>\n",
urlPath, urlPath);
// Add parent directory link if not at /files/
if (!streq(urlPath, "/files/")) {
pos += snprintf(buf + pos, bufSize - pos,
"<li><a href=\"..\">..</a></li>\n");
}
// List directory entries
const char* entries[64];
int count = zenith::readdir(vfsDir, entries, 64);
// Find the prefix to strip from entry names
// vfsDir is like "0:/" or "0:/subdir"
// entries come back as "subdir/file" for "0:/subdir"
// or "file" for "0:/"
// The prefix in entry names is the part after "0:/"
const char* dirRel = vfsDir + 3; // skip "0:/"
int dirRelLen = slen(dirRel);
for (int i = 0; i < count && pos < bufSize - 128; i++) {
// Extract just the filename portion
const char* name = entries[i];
// If directory is not root, entries have "dir/name" format — strip the dir prefix
if (dirRelLen > 0 && starts_with(name, dirRel)) {
name = name + dirRelLen;
if (*name == '/') name++;
}
if (*name == '\0') continue;
// Build the URL for this entry
pos += snprintf(buf + pos, bufSize - pos,
"<li><a href=\"%s%s\">%s</a></li>\n",
urlPath, name, name);
}
pos += snprintf(buf + pos, bufSize - pos,
"</ul>\n"
"<hr>\n"
"<p><i>ZenithOS httpd</i></p>\n"
"</body>\n"
"</html>\n");
return pos;
}
// ---- Request handler ----
static void handle_client(int clientFd) {
// Read request (HTTP requests are small, 4 KB is plenty)
char reqBuf[4096];
int reqLen = 0;
int idleCount = 0;
// Read until we get the full header (ends with \r\n\r\n)
while (reqLen < (int)sizeof(reqBuf) - 1) {
int r = zenith::recv(clientFd, reqBuf + reqLen, sizeof(reqBuf) - 1 - reqLen);
if (r > 0) {
reqLen += r;
idleCount = 0;
// Check if we have the full header
bool done = false;
for (int i = 0; i + 3 < reqLen; i++) {
if (reqBuf[i] == '\r' && reqBuf[i+1] == '\n' &&
reqBuf[i+2] == '\r' && reqBuf[i+3] == '\n') {
done = true;
break;
}
}
if (done) break;
} else if (r == 0) {
break; // Connection closed
} else {
idleCount++;
if (idleCount > 500) break; // Timeout
zenith::yield();
}
}
reqBuf[reqLen] = '\0';
if (reqLen == 0) {
zenith::closesocket(clientFd);
return;
}
// Parse request path
char path[256];
if (parse_request_path(reqBuf, reqLen, path, sizeof(path)) < 0) {
// Bad request
static char body[] = "<!DOCTYPE html><html><body><h1>400 Bad Request</h1></body></html>";
send_response(clientFd, 400, "Bad Request", "text/html", body, slen(body));
log_request("???", "???", 400, slen(body));
zenith::closesocket(clientFd);
return;
}
// Route the request
static char pageBuf[16384];
if (streq(path, "/")) {
// Try to serve 0:/www/index.html from disk first
int handle = zenith::open("0:/www/index.html");
if (handle >= 0) {
zenith::close(handle);
int bodyLen = send_file_response(clientFd, "0:/www/index.html", "/index.html");
log_request("GET", path, 200, bodyLen);
} else {
// Fall back to built-in index page
int bodyLen = generate_index_page(pageBuf, sizeof(pageBuf));
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
}
} else if (streq(path, "/files") || streq(path, "/files/")) {
// Root directory listing
int bodyLen = generate_dir_listing(pageBuf, sizeof(pageBuf), "/files/", "0:/");
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
} else if (starts_with(path, "/files/")) {
// Serve file or directory from VFS
const char* relPath = path + 7; // skip "/files/"
// Build VFS path: "0:/<relPath>"
char vfsPath[256];
int pi = 0;
vfsPath[pi++] = '0'; vfsPath[pi++] = ':'; vfsPath[pi++] = '/';
int ri = 0;
while (relPath[ri] && pi < (int)sizeof(vfsPath) - 1) {
vfsPath[pi++] = relPath[ri++];
}
// Strip trailing slash for VFS lookup
if (pi > 3 && vfsPath[pi-1] == '/') pi--;
vfsPath[pi] = '\0';
// Try to open as file first
int handle = zenith::open(vfsPath);
if (handle >= 0) {
// It's a file — serve it
zenith::close(handle);
int bodyLen = send_file_response(clientFd, vfsPath, path);
if (bodyLen >= 0) {
log_request("GET", path, 200, bodyLen);
} else {
static char body[] = "<!DOCTYPE html><html><body><h1>500 Internal Server Error</h1></body></html>";
send_response(clientFd, 500, "Internal Server Error", "text/html", body, slen(body));
log_request("GET", path, 500, slen(body));
}
} else {
// Try as directory
const char* entries[64];
int count = zenith::readdir(vfsPath, entries, 64);
if (count >= 0) {
// Make sure urlPath ends with /
char urlPath[256];
int up = 0;
int pp = 0;
while (path[pp] && up < (int)sizeof(urlPath) - 2) urlPath[up++] = path[pp++];
if (up > 0 && urlPath[up-1] != '/') urlPath[up++] = '/';
urlPath[up] = '\0';
int bodyLen = generate_dir_listing(pageBuf, sizeof(pageBuf), urlPath, vfsPath);
send_response(clientFd, 200, "OK", "text/html", pageBuf, bodyLen);
log_request("GET", path, 200, bodyLen);
} else {
// Not found
int bodyLen = generate_404_page(pageBuf, sizeof(pageBuf), path);
send_response(clientFd, 404, "Not Found", "text/html", pageBuf, bodyLen);
log_request("GET", path, 404, bodyLen);
}
}
} else {
// 404 for anything else
int bodyLen = generate_404_page(pageBuf, sizeof(pageBuf), path);
send_response(clientFd, 404, "Not Found", "text/html", pageBuf, bodyLen);
log_request("GET", path, 404, bodyLen);
}
zenith::closesocket(clientFd);
}
// ---- Entry point ----
extern "C" void _start() {
// Parse arguments: [port]
char argbuf[64];
zenith::getargs(argbuf, sizeof(argbuf));
const char* arg = skip_spaces(argbuf);
uint16_t port = 80;
if (*arg) {
if (!parse_uint16(arg, &port)) {
zenith::print("Invalid port: ");
zenith::print(arg);
zenith::putchar('\n');
zenith::exit(1);
}
}
// Create server socket
int listenFd = zenith::socket(Zenith::SOCK_TCP);
if (listenFd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
// Bind
if (zenith::bind(listenFd, port) < 0) {
zenith::print("Error: failed to bind to port ");
char tmp[8];
snprintf(tmp, sizeof(tmp), "%d", (int)port);
zenith::print(tmp);
zenith::putchar('\n');
zenith::closesocket(listenFd);
zenith::exit(1);
}
// Listen
if (zenith::listen(listenFd) < 0) {
zenith::print("Error: failed to listen\n");
zenith::closesocket(listenFd);
zenith::exit(1);
}
char msg[128];
snprintf(msg, sizeof(msg), "ZenithOS httpd listening on port %d\n", (int)port);
zenith::print(msg);
zenith::print("Press Ctrl+Q between requests to stop.\n\n");
bool running = true;
while (running) {
// Check for Ctrl+Q before blocking on accept
while (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
running = false;
break;
}
}
if (!running) break;
// Accept next client (blocks until a connection arrives)
int clientFd = zenith::accept(listenFd);
if (clientFd < 0) {
zenith::print("Warning: accept failed\n");
zenith::yield();
continue;
}
handle_client(clientFd);
// After serving, check for Ctrl+Q
while (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (ev.pressed && ev.ctrl && ev.ascii == 'q') {
running = false;
break;
}
}
}
zenith::print("\nShutting down httpd...\n");
zenith::closesocket(listenFd);
zenith::exit(0);
}
+149
View File
@@ -0,0 +1,149 @@
/*
* main.cpp
* ifconfig - Show or set network configuration
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::starts_with;
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
// Show current network configuration
Zenith::NetCfg cfg;
zenith::get_netcfg(&cfg);
zenith::print(" IP Address: ");
print_ip(cfg.ipAddress);
zenith::putchar('\n');
zenith::print(" Subnet Mask: ");
print_ip(cfg.subnetMask);
zenith::putchar('\n');
zenith::print(" Gateway: ");
print_ip(cfg.gateway);
zenith::putchar('\n');
zenith::exit(0);
}
if (!starts_with(args, "set ")) {
zenith::print("Usage: ifconfig Show network config\n");
zenith::print(" ifconfig set <ip> <mask> <gateway>\n");
zenith::exit(1);
}
// Parse: set <ip> <mask> <gateway>
const char* p = skip_spaces(args + 4);
// Parse IP
char tok[32];
int i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t ip;
if (!parse_ip(tok, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
p = skip_spaces(p + i);
// Parse subnet mask
i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t mask;
if (!parse_ip(tok, &mask)) {
zenith::print("Invalid subnet mask: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
p = skip_spaces(p + i);
// Parse gateway
i = 0;
while (p[i] && p[i] != ' ' && i < 31) { tok[i] = p[i]; i++; }
tok[i] = '\0';
uint32_t gw;
if (!parse_ip(tok, &gw)) {
zenith::print("Invalid gateway: ");
zenith::print(tok);
zenith::putchar('\n');
zenith::exit(1);
}
Zenith::NetCfg cfg;
cfg.ipAddress = ip;
cfg.subnetMask = mask;
cfg.gateway = gw;
if (zenith::set_netcfg(&cfg) < 0) {
zenith::print("Error: failed to set network config\n");
zenith::exit(1);
}
zenith::print("Network config updated:\n");
zenith::print(" IP Address: "); print_ip(ip); zenith::putchar('\n');
zenith::print(" Subnet Mask: "); print_ip(mask); zenith::putchar('\n');
zenith::print(" Gateway: "); print_ip(gw); zenith::putchar('\n');
zenith::exit(0);
}
+36
View File
@@ -0,0 +1,36 @@
/*
* main.cpp
* info - Show system information
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
extern "C" void _start() {
Zenith::SysInfo info;
zenith::get_info(&info);
zenith::print(info.osName);
zenith::print(" v");
zenith::print(info.osVersion);
zenith::putchar('\n');
zenith::print("Syscall API version: ");
print_int(info.apiVersion);
zenith::putchar('\n');
zenith::exit(0);
}
+164
View File
@@ -0,0 +1,164 @@
/*
* main.cpp
* Init system for ZenithOS (PID 0)
* Chains system services then launches the shell.
* Copyright (c) 2026 Daniel Hammer
*/
#include <zenith/syscall.h>
// ---- Minimal snprintf ----
using va_list = __builtin_va_list;
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
struct PfState { char* buf; int pos; int max; };
static void pf_putc(PfState* st, char c) {
if (st->pos < st->max) st->buf[st->pos] = c;
st->pos++;
}
static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) {
char tmp[24]; int i = 0;
const char* digits = "0123456789abcdef";
if (val == 0) { tmp[i++] = '0'; }
else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } }
int total = (neg ? 1 : 0) + i;
if (neg && pad == '0') pf_putc(st, '-');
for (int w = total; w < width; w++) pf_putc(st, pad);
if (neg && pad != '0') pf_putc(st, '-');
while (i > 0) pf_putc(st, tmp[--i]);
}
static int vsnprintf(char* buf, int size, const char* fmt, va_list ap) {
PfState st; st.buf = buf; st.pos = 0; st.max = size > 0 ? size - 1 : 0;
while (*fmt) {
if (*fmt != '%') { pf_putc(&st, *fmt++); continue; }
fmt++;
char pad = ' ';
if (*fmt == '0') { pad = '0'; fmt++; }
int width = 0;
while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; }
if (*fmt == 'l') fmt++;
switch (*fmt) {
case 'd': case 'i': {
long val = va_arg(ap, int);
int neg = 0; unsigned long uval;
if (val < 0) { neg = 1; uval = (unsigned long)(-val); } else uval = (unsigned long)val;
pf_putnum(&st, uval, 10, width, pad, neg); break;
}
case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; }
case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; }
case 's': {
const char* s = va_arg(ap, const char*); if (!s) s = "(null)";
int slen = 0; while (s[slen]) slen++;
for (int w = slen; w < width; w++) pf_putc(&st, ' ');
for (int j = 0; j < slen; j++) pf_putc(&st, s[j]);
break;
}
case 'c': { char c = (char)va_arg(ap, int); pf_putc(&st, c); break; }
case '%': pf_putc(&st, '%'); break;
default: pf_putc(&st, '%'); pf_putc(&st, *fmt); break;
}
if (*fmt) fmt++;
}
if (size > 0) { if (st.pos < size) st.buf[st.pos] = '\0'; else st.buf[size - 1] = '\0'; }
return st.pos;
}
static int snprintf(char* buf, int size, const char* fmt, ...) {
va_list ap; va_start(ap, fmt);
int ret = vsnprintf(buf, size, fmt, ap);
va_end(ap); return ret;
}
// ---- ANSI color codes ----
#define C_RESET "\033[0m"
#define C_BOLD "\033[1m"
#define C_DIM "\033[2m"
#define C_RED "\033[31m"
#define C_GREEN "\033[32m"
#define C_YELLOW "\033[33m"
#define C_BLUE "\033[34m"
#define C_CYAN "\033[36m"
#define C_WHITE "\033[37m"
// ---- Logging ----
static void log_timestamp(char* buf, int size) {
Zenith::DateTime dt;
zenith::gettime(&dt);
snprintf(buf, size, "%02d:%02d:%02d", dt.Hour, dt.Minute, dt.Second);
}
enum LogLevel { LOG_OK, LOG_INFO, LOG_WARN, LOG_ERR };
static void log(LogLevel level, const char* msg) {
char line[512];
char ts[16];
log_timestamp(ts, sizeof(ts));
const char* tag;
const char* color;
switch (level) {
case LOG_OK: tag = " OK "; color = C_GREEN; break;
case LOG_INFO: tag = " INFO "; color = C_CYAN; break;
case LOG_WARN: tag = " WARN "; color = C_YELLOW; break;
case LOG_ERR: tag = " FAIL "; color = C_RED; break;
}
snprintf(line, sizeof(line),
C_DIM "%s" C_RESET " %s%s" C_RESET " " C_BOLD "init" C_RESET " %s\n",
ts, color, tag, msg);
zenith::print(line);
}
static void log_ok(const char* msg) { log(LOG_OK, msg); }
static void log_info(const char* msg) { log(LOG_INFO, msg); }
static void log_warn(const char* msg) { log(LOG_WARN, msg); }
static void log_err(const char* msg) { log(LOG_ERR, msg); }
// ---- Service runner ----
static bool run_service(const char* path, const char* name) {
char msg[128];
snprintf(msg, sizeof(msg), "Starting %s", name);
log_info(msg);
int pid = zenith::spawn(path);
if (pid < 0) {
snprintf(msg, sizeof(msg), "Failed to start %s", name);
log_err(msg);
return false;
}
zenith::waitpid(pid);
snprintf(msg, sizeof(msg), "%s finished (pid %d)", name, pid);
log_ok(msg);
return true;
}
// ---- Main ----
extern "C" void _start() {
log_info("Init system starting (PID 0)");
// ---- Stage 1: Network configuration ----
run_service("0:/os/dhcp.elf", "dhcp");
// ---- Stage 2: Interactive shell ----
run_service("0:/os/shell.elf", "shell");
log_warn("All services exited");
for (;;) {
zenith::yield();
}
}
+10 -45
View File
@@ -6,6 +6,16 @@
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
using zenith::strcpy;
using zenith::strncpy;
using zenith::memcpy;
using zenith::memmove;
// ---- Minimal snprintf (no libc available) ----
@@ -91,51 +101,6 @@ static int snprintf(char* buf, int size, const char* fmt, ...) {
return ret;
}
// ---- String utilities ----
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
static bool streq(const char* a, const char* b) {
while (*a && *b) { if (*a != *b) return false; a++; b++; }
return *a == *b;
}
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) { if (*str != *prefix) return false; str++; prefix++; }
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static void strcpy(char* dst, const char* src) {
while (*src) *dst++ = *src++;
*dst = '\0';
}
static void strncpy(char* dst, const char* src, int max) {
int i = 0;
while (src[i] && i < max - 1) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
static void memcpy(void* dst, const void* src, int n) {
auto d = (char*)dst; auto s = (const char*)src;
for (int i = 0; i < n; i++) d[i] = s[i];
}
static void memmove(void* dst, const void* src, int n) {
auto d = (char*)dst; auto s = (const char*)src;
if (d < s) { for (int i = 0; i < n; i++) d[i] = s[i]; }
else { for (int i = n - 1; i >= 0; i--) d[i] = s[i]; }
}
// Case-insensitive comparison for IRC commands
static bool streqi(const char* a, const char* b) {
while (*a && *b) {
+4 -20
View File
@@ -7,27 +7,11 @@
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
// ---- Utility functions ----
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) {
if (*str != *prefix) return false;
str++; prefix++;
}
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
}
using zenith::slen;
using zenith::starts_with;
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
+101
View File
@@ -0,0 +1,101 @@
/*
* main.cpp
* ping - Send ICMP echo requests
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: ping <ip address>\n");
zenith::exit(1);
}
uint32_t ip;
if (!parse_ip(args, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(args);
zenith::putchar('\n');
zenith::exit(1);
}
zenith::print("PING ");
print_ip(ip);
zenith::putchar('\n');
for (int i = 0; i < 4; i++) {
int32_t rtt = zenith::ping(ip, 3000);
if (rtt < 0) {
zenith::print(" Request timed out\n");
} else {
zenith::print(" Reply from ");
print_ip(ip);
zenith::print(": time=");
print_int((uint64_t)rtt);
zenith::print("ms\n");
}
if (i < 3) {
zenith::sleep_ms(1000);
}
}
zenith::exit(0);
}
+12
View File
@@ -0,0 +1,12 @@
/*
* main.cpp
* reset - Reboot the system
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
zenith::print("Rebooting...\n");
zenith::reset();
}
+285 -450
View File
@@ -5,52 +5,33 @@
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
static bool streq(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
using zenith::slen;
using zenith::streq;
using zenith::starts_with;
using zenith::skip_spaces;
static void scopy(char* dst, const char* src, int maxLen) {
int i = 0;
while (src[i] && i < maxLen - 1) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
static void scat(char* dst, const char* src, int maxLen) {
int dLen = slen(dst);
int i = 0;
while (src[i] && dLen + i < maxLen - 1) {
dst[dLen + i] = src[i];
i++;
}
return *a == *b;
}
static bool starts_with(const char* str, const char* prefix) {
while (*prefix) {
if (*str != *prefix) return false;
str++; prefix++;
}
return true;
}
static const char* skip_spaces(const char* s) {
while (*s == ' ') s++;
return s;
}
static int slen(const char* s) {
int n = 0;
while (s[n]) n++;
return n;
dst[dLen + i] = '\0';
}
// Current working directory (relative to 0:/).
// "" = root, "man" = 0:/man, "man/sub" = 0:/man/sub
static char cwd[128] = "";
// Build full VFS path: "0:/" + cwd + "/" + name
static void resolve_path(const char* name, char* out, int outMax) {
int i = 0;
out[i++] = '0'; out[i++] = ':'; out[i++] = '/';
if (cwd[0]) {
int j = 0;
while (cwd[j] && i < outMax - 2) out[i++] = cwd[j++];
out[i++] = '/';
}
int j = 0;
while (name[j] && i < outMax - 1) out[i++] = name[j++];
out[i] = '\0';
}
// Build VFS directory path: "0:/" or "0:/<dir>"
static void build_dir_path(const char* dir, char* out, int outMax) {
int i = 0;
@@ -62,58 +43,100 @@ static void build_dir_path(const char* dir, char* out, int outMax) {
out[i] = '\0';
}
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
// ---- Command history ----
static constexpr int HISTORY_MAX = 32;
static char history[HISTORY_MAX][256];
static int history_count = 0;
static int history_next = 0; // ring-buffer write index
static void history_add(const char* line) {
if (line[0] == '\0') return;
// Don't add duplicate of last entry
if (history_count > 0) {
int prev = (history_next + HISTORY_MAX - 1) % HISTORY_MAX;
if (streq(history[prev], line)) return;
}
scopy(history[history_next], line, 256);
history_next = (history_next + 1) % HISTORY_MAX;
if (history_count < HISTORY_MAX) history_count++;
}
// Get history entry by index (0 = most recent, 1 = one before that, ...)
static const char* history_get(int idx) {
if (idx < 0 || idx >= history_count) return nullptr;
int pos = (history_next + HISTORY_MAX - 1 - idx) % HISTORY_MAX;
return history[pos];
}
// ---- Prompt ----
static void prompt() {
zenith::print("0:/");
if (cwd[0]) zenith::print(cwd);
zenith::print("> ");
}
// ---- Erase current input line on screen ----
static void erase_input(int len) {
// Move cursor to start of input and overwrite with spaces
for (int i = 0; i < len; i++) zenith::putchar('\b');
for (int i = 0; i < len; i++) zenith::putchar(' ');
for (int i = 0; i < len; i++) zenith::putchar('\b');
}
// ---- Replace visible line with new content ----
static void replace_line(char* line, int* pos, const char* newContent) {
int oldLen = *pos;
erase_input(oldLen);
int newLen = slen(newContent);
if (newLen > 255) newLen = 255;
for (int i = 0; i < newLen; i++) {
line[i] = newContent[i];
zenith::putchar(newContent[i]);
}
line[newLen] = '\0';
*pos = newLen;
}
// ---- Builtin: help ----
static void cmd_help() {
zenith::print("Available commands:\n");
zenith::print("Shell builtins:\n");
zenith::print(" help Show this help message\n");
zenith::print(" info Show system information\n");
zenith::print(" man <topic> View manual pages\n");
zenith::print(" ls [dir] List files in directory\n");
zenith::print(" cd [dir] Change working directory\n");
zenith::print(" exit Exit the shell\n");
zenith::print("\n");
zenith::print("System commands:\n");
zenith::print(" man <topic> View manual pages\n");
zenith::print(" cat <file> Display file contents\n");
zenith::print(" run <file> Spawn a new process from an ELF file\n");
zenith::print(" ping <ip> Send ICMP echo requests\n");
zenith::print(" tcpconnect <ip> <port> Connect to a TCP server\n");
zenith::print(" edit [file] Text editor\n");
zenith::print(" info Show system information\n");
zenith::print(" date Show current date and time\n");
zenith::print(" uptime Show uptime in milliseconds\n");
zenith::print(" uptime Show uptime\n");
zenith::print(" clear Clear the screen\n");
zenith::print(" reset Reboot the system\n");
zenith::print(" shutdown Shut down the system\n");
zenith::print(" exit Exit the shell\n");
zenith::print("\n");
zenith::print("Network commands:\n");
zenith::print(" ping <ip> Send ICMP echo requests\n");
zenith::print(" ifconfig Show/set network configuration\n");
zenith::print(" tcpconnect Connect to a TCP server\n");
zenith::print(" irc IRC client\n");
zenith::print(" dhcp DHCP client\n");
zenith::print(" fetch <url> HTTP client\n");
zenith::print(" httpd HTTP server\n");
zenith::print("\n");
zenith::print("Games:\n");
zenith::print(" doom DOOM\n");
zenith::print("\n");
zenith::print("Any .elf on the ramdisk is executable.\n");
}
static void cmd_info() {
Zenith::SysInfo info;
zenith::get_info(&info);
zenith::print(info.osName);
zenith::print(" v");
zenith::print(info.osVersion);
zenith::print("\n");
zenith::print("Syscall API version: ");
print_int(info.apiVersion);
zenith::putchar('\n');
}
// ---- Builtin: ls ----
static void cmd_ls(const char* arg) {
arg = skip_spaces(arg);
@@ -121,7 +144,7 @@ static void cmd_ls(const char* arg) {
// Build the target directory (relative path from root)
char dir[128];
if (*arg) {
// ls <dir> combine cwd and arg
// ls <dir> -- combine cwd and arg
if (cwd[0]) {
int i = 0, j = 0;
while (cwd[j] && i < 126) dir[i++] = cwd[j++];
@@ -135,7 +158,7 @@ static void cmd_ls(const char* arg) {
dir[i] = '\0';
}
} else {
// ls with no arg use cwd
// ls with no arg -- use cwd
int i = 0;
while (cwd[i] && i < 126) { dir[i] = cwd[i]; i++; }
dir[i] = '\0';
@@ -166,310 +189,26 @@ static void cmd_ls(const char* arg) {
}
}
static void cmd_cat(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: cat <filename>\n");
return;
}
char path[128];
resolve_path(arg, path, sizeof(path));
int handle = zenith::open(path);
if (handle < 0) {
zenith::print("Error: cannot open '");
zenith::print(arg);
zenith::print("'\n");
return;
}
uint64_t size = zenith::getsize(handle);
if (size == 0) {
zenith::close(handle);
return;
}
// Read in chunks
uint8_t buf[512];
uint64_t offset = 0;
while (offset < size) {
uint64_t chunk = size - offset;
if (chunk > sizeof(buf) - 1) chunk = sizeof(buf) - 1;
int bytesRead = zenith::read(handle, buf, offset, chunk);
if (bytesRead <= 0) break;
buf[bytesRead] = '\0';
zenith::print((const char*)buf);
offset += bytesRead;
}
zenith::close(handle);
zenith::putchar('\n');
}
static void cmd_uptime() {
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
secs %= 60;
ms %= 1000;
zenith::print("Uptime: ");
print_int(mins);
zenith::print("m ");
print_int(secs);
zenith::print("s ");
print_int(ms);
zenith::print("ms\n");
}
static bool parse_ip(const char* s, uint32_t* out) {
// Parse "a.b.c.d" into a uint32_t in network byte order (little-endian stored)
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static void cmd_ping(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: ping <ip address>\n");
return;
}
uint32_t ip;
if (!parse_ip(arg, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(arg);
zenith::putchar('\n');
return;
}
zenith::print("PING ");
print_ip(ip);
zenith::putchar('\n');
for (int i = 0; i < 4; i++) {
int32_t rtt = zenith::ping(ip, 3000);
if (rtt < 0) {
zenith::print(" Request timed out\n");
} else {
zenith::print(" Reply from ");
print_ip(ip);
zenith::print(": time=");
print_int((uint64_t)rtt);
zenith::print("ms\n");
}
if (i < 3) {
zenith::sleep_ms(1000);
}
}
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
static void cmd_tcpconnect(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
return;
}
// Parse IP address (up to first space)
char ipStr[32];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 31) {
ipStr[i] = arg[i];
i++;
}
ipStr[i] = '\0';
uint32_t ip;
if (!parse_ip(ipStr, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
return;
}
// Parse port
const char* portStr = skip_spaces(arg + i);
if (*portStr == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
return;
}
uint16_t port;
if (!parse_uint16(portStr, &port)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
return;
}
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
return;
}
zenith::print("Connecting to ");
print_ip(ip);
zenith::putchar(':');
print_int(port);
zenith::print("...\n");
if (zenith::connect(fd, ip, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
return;
}
zenith::print("Connected! Type to send, Ctrl+Q to disconnect.\n");
// Interactive send/receive loop
char sendBuf[256];
int sendPos = 0;
uint8_t recvBuf[512];
while (true) {
// Poll for received data (non-blocking)
int r = zenith::recv(fd, recvBuf, sizeof(recvBuf) - 1);
if (r < 0) {
zenith::print("\nConnection closed by remote.\n");
break;
}
if (r > 0) {
recvBuf[r] = '\0';
zenith::print((const char*)recvBuf);
}
// Poll keyboard
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Ctrl+Q to quit
if (ev.ctrl && (ev.ascii == 'q' || ev.ascii == 'Q')) {
zenith::print("\nDisconnecting...\n");
break;
}
if (ev.ascii == '\n') {
sendBuf[sendPos++] = '\n';
zenith::putchar('\n');
zenith::send(fd, sendBuf, sendPos);
sendPos = 0;
} else if (ev.ascii == '\b') {
if (sendPos > 0) {
sendPos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (ev.ascii >= ' ' && sendPos < 254) {
sendBuf[sendPos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
} else {
// No key and no data — yield to avoid busy-spinning
zenith::yield();
}
}
zenith::closesocket(fd);
}
static void cmd_run(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
zenith::print("Usage: run <filename> [args...]\n");
return;
}
// Split filename from arguments at first space
char filename[128];
int i = 0;
while (arg[i] && arg[i] != ' ' && i < 127) {
filename[i] = arg[i];
i++;
}
filename[i] = '\0';
const char* args = nullptr;
if (arg[i] == ' ') {
args = skip_spaces(arg + i);
if (*args == '\0') args = nullptr;
}
char path[128];
resolve_path(filename, path, sizeof(path));
int pid = zenith::spawn(path, args);
if (pid < 0) {
zenith::print("Error: failed to spawn '");
zenith::print(filename);
zenith::print("'\n");
} else {
zenith::waitpid(pid);
}
}
// ---- Builtin: cd ----
static void cmd_cd(const char* arg) {
arg = skip_spaces(arg);
// cd or cd / → go to root
// Strip trailing slashes from argument (ls shows dirs as "www/", user may type that)
static char argBuf[128];
int aLen = 0;
while (arg[aLen] && aLen < 127) { argBuf[aLen] = arg[aLen]; aLen++; }
argBuf[aLen] = '\0';
while (aLen > 0 && argBuf[aLen - 1] == '/') argBuf[--aLen] = '\0';
arg = argBuf;
// cd or cd / -> go to root
if (*arg == '\0' || streq(arg, "/")) {
cwd[0] = '\0';
return;
}
// cd .. go up one level
// cd .. -> go up one level
if (streq(arg, "..")) {
int len = slen(cwd);
int last = -1;
@@ -517,6 +256,8 @@ static void cmd_cd(const char* arg) {
cwd[i] = '\0';
}
// ---- Builtin: man ----
static void cmd_man(const char* arg) {
arg = skip_spaces(arg);
if (*arg == '\0') {
@@ -526,7 +267,7 @@ static void cmd_man(const char* arg) {
return;
}
int pid = zenith::spawn("0:/man.elf", arg);
int pid = zenith::spawn("0:/os/man.elf", arg);
if (pid < 0) {
zenith::print("Error: failed to start man viewer\n");
} else {
@@ -534,102 +275,155 @@ static void cmd_man(const char* arg) {
}
}
static void print_int_padded(uint64_t n) {
if (n < 10) zenith::putchar('0');
print_int(n);
// ---- External command execution ----
// Try to spawn an ELF at the given path. Returns true on success.
static bool try_exec(const char* path, const char* args) {
// Check if the file exists before asking the kernel to load it
int h = zenith::open(path);
if (h < 0) return false;
zenith::close(h);
int pid = zenith::spawn(path, args);
if (pid < 0) return false;
zenith::waitpid(pid);
return true;
}
static const char* month_name(int m) {
static const char* months[] = {
"", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
if (m >= 1 && m <= 12) return months[m];
return "?";
// Check if a string already has a VFS drive prefix (e.g. "0:/")
static bool has_drive_prefix(const char* s) {
return s[0] >= '0' && s[0] <= '9' && s[1] == ':';
}
static void cmd_date() {
Zenith::DateTime dt;
zenith::gettime(&dt);
// Resolve arguments: expand relative file paths against CWD.
// Tokens that already have a drive prefix (e.g. "0:/foo") are left as-is.
// Everything before the first space-delimited token that looks like a path
// option (starts with '-') is also left as-is.
static void resolve_args(const char* args, char* out, int outMax) {
if (!args || !args[0]) { out[0] = '\0'; return; }
print_int(dt.Day);
zenith::putchar(' ');
zenith::print(month_name(dt.Month));
zenith::putchar(' ');
print_int(dt.Year);
zenith::print(", ");
print_int(dt.Hour);
zenith::putchar(':');
print_int_padded(dt.Minute);
zenith::putchar(':');
print_int_padded(dt.Second);
zenith::print(" UTC\n");
int o = 0;
const char* p = args;
while (*p && o < outMax - 1) {
// Skip spaces, copy them through
while (*p == ' ' && o < outMax - 1) { out[o++] = *p++; }
if (!*p) break;
// Extract the token
const char* tokStart = p;
int tokLen = 0;
while (p[tokLen] && p[tokLen] != ' ') tokLen++;
// Decide whether to resolve this token as a path.
// Don't resolve if it already has a drive prefix, or starts with '-'
bool resolve = cwd[0] && !has_drive_prefix(tokStart) && tokStart[0] != '-';
if (resolve) {
// Write "0:/<cwd>/<token>"
if (o + 3 < outMax) { out[o++] = '0'; out[o++] = ':'; out[o++] = '/'; }
int j = 0;
while (cwd[j] && o < outMax - 1) out[o++] = cwd[j++];
if (o < outMax - 1) out[o++] = '/';
for (int k = 0; k < tokLen && o < outMax - 1; k++) out[o++] = tokStart[k];
} else {
for (int k = 0; k < tokLen && o < outMax - 1; k++) out[o++] = tokStart[k];
}
p = tokStart + tokLen;
}
out[o] = '\0';
}
static void cmd_clear() {
zenith::print("\033[2J"); // Clear entire screen
zenith::print("\033[H"); // Move cursor to top-left
static void exec_external(const char* cmd, const char* args) {
char path[256];
// Resolve arguments against CWD so external programs get full VFS paths
char resolvedArgs[512];
resolve_args(args, resolvedArgs, sizeof(resolvedArgs));
const char* finalArgs = resolvedArgs[0] ? resolvedArgs : nullptr;
// 1. Try 0:/os/<cmd>.elf
scopy(path, "0:/os/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// 2. Try 0:/games/<cmd>.elf
scopy(path, "0:/games/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// 3. Try 0:/<cwd>/<cmd>.elf (if cwd is set)
if (cwd[0]) {
scopy(path, "0:/", sizeof(path));
scat(path, cwd, sizeof(path));
scat(path, "/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
}
// 4. Try 0:/<cmd>.elf
scopy(path, "0:/", sizeof(path));
scat(path, cmd, sizeof(path));
scat(path, ".elf", sizeof(path));
if (try_exec(path, finalArgs)) return;
// Not found
zenith::print(cmd);
zenith::print(": command not found\n");
}
// ---- Command dispatch ----
static void process_command(const char* line) {
// Skip leading spaces
line = skip_spaces(line);
if (*line == '\0') return;
if (streq(line, "help")) {
// Parse command name and arguments
char cmd[128];
int i = 0;
while (line[i] && line[i] != ' ' && i < 127) {
cmd[i] = line[i];
i++;
}
cmd[i] = '\0';
const char* args = nullptr;
if (line[i] == ' ') {
args = skip_spaces(line + i);
if (*args == '\0') args = nullptr;
}
// Builtins
if (streq(cmd, "help")) {
cmd_help();
} else if (streq(line, "info")) {
cmd_info();
} else if (starts_with(line, "ls ")) {
cmd_ls(line + 3);
} else if (streq(line, "ls")) {
cmd_ls("");
} else if (starts_with(line, "cd ")) {
cmd_cd(line + 3);
} else if (streq(line, "cd")) {
cmd_cd("");
} else if (starts_with(line, "man ")) {
cmd_man(line + 4);
} else if (streq(line, "man")) {
cmd_man("");
} else if (starts_with(line, "cat ")) {
cmd_cat(line + 4);
} else if (streq(line, "cat")) {
cmd_cat("");
} else if (starts_with(line, "run ")) {
cmd_run(line + 4);
} else if (streq(line, "run")) {
cmd_run("");
} else if (starts_with(line, "ping ")) {
cmd_ping(line + 5);
} else if (streq(line, "ping")) {
cmd_ping("");
} else if (starts_with(line, "tcpconnect ")) {
cmd_tcpconnect(line + 11);
} else if (streq(line, "tcpconnect")) {
cmd_tcpconnect("");
} else if (streq(line, "date")) {
cmd_date();
} else if (streq(line, "uptime")) {
cmd_uptime();
} else if (streq(line, "clear")) {
cmd_clear();
} else if (streq(line, "reset")) {
zenith::print("Rebooting...\n");
zenith::reset();
} else if (streq(line, "shutdown")) {
zenith::print("Shutting down...\n");
zenith::shutdown();
} else if (streq(line, "exit")) {
} else if (streq(cmd, "ls")) {
cmd_ls(args ? args : "");
} else if (streq(cmd, "cd")) {
cmd_cd(args ? args : "");
} else if (streq(cmd, "man")) {
cmd_man(args ? args : "");
} else if (streq(cmd, "exit")) {
zenith::print("Goodbye.\n");
zenith::exit(0);
} else {
zenith::print("Unknown command: ");
zenith::print(line);
zenith::print("\nType 'help' for available commands.\n");
// External command -- pass full argument string
exec_external(cmd, args);
}
}
// ---- Arrow key scancodes ----
static constexpr uint8_t SC_UP = 0x48;
static constexpr uint8_t SC_DOWN = 0x50;
static constexpr uint8_t SC_LEFT = 0x4B;
static constexpr uint8_t SC_RIGHT = 0x4D;
// ---- Entry point ----
extern "C" void _start() {
zenith::print("\n");
zenith::print(" ZenithOS\n");
@@ -641,28 +435,69 @@ extern "C" void _start() {
char line[256];
int pos = 0;
int hist_nav = -1; // -1 = not navigating history
prompt();
while (true) {
char c = zenith::getchar();
if (!zenith::is_key_available()) {
zenith::yield();
continue;
}
if (c == '\n') {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Arrow keys: ascii == 0, check scancode
if (ev.ascii == 0) {
if (ev.scancode == SC_UP) {
// Navigate to older history entry
int next = hist_nav + 1;
const char* entry = history_get(next);
if (entry) {
hist_nav = next;
replace_line(line, &pos, entry);
}
} else if (ev.scancode == SC_DOWN) {
// Navigate to newer history entry
if (hist_nav > 0) {
hist_nav--;
const char* entry = history_get(hist_nav);
if (entry) {
replace_line(line, &pos, entry);
}
} else if (hist_nav == 0) {
// Back to empty line
hist_nav = -1;
erase_input(pos);
pos = 0;
line[0] = '\0';
}
}
// Left/Right arrows: ignore for now (no cursor movement within line)
continue;
}
if (ev.ascii == '\n') {
zenith::putchar('\n');
line[pos] = '\0';
history_add(line);
process_command(line);
pos = 0;
hist_nav = -1;
prompt();
} else if (c == '\b') {
} else if (ev.ascii == '\b') {
if (pos > 0) {
pos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (c >= ' ' && pos < 255) {
line[pos++] = c;
zenith::putchar(c);
} else if (ev.ascii >= ' ' && pos < 255) {
line[pos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
/*
* main.cpp
* shutdown - Shut down the system
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
extern "C" void _start() {
zenith::print("Shutting down...\n");
zenith::shutdown();
}
+194
View File
@@ -0,0 +1,194 @@
/*
* main.cpp
* tcpconnect - Interactive TCP client
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
#include <zenith/string.h>
using zenith::skip_spaces;
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
static bool parse_ip(const char* s, uint32_t* out) {
uint32_t octets[4];
int idx = 0;
uint32_t val = 0;
bool hasDigit = false;
for (int i = 0; ; i++) {
char c = s[i];
if (c >= '0' && c <= '9') {
val = val * 10 + (c - '0');
if (val > 255) return false;
hasDigit = true;
} else if (c == '.' || c == '\0') {
if (!hasDigit || idx >= 4) return false;
octets[idx++] = val;
val = 0;
hasDigit = false;
if (c == '\0') break;
} else {
return false;
}
}
if (idx != 4) return false;
*out = octets[0] | (octets[1] << 8) | (octets[2] << 16) | (octets[3] << 24);
return true;
}
static void print_ip(uint32_t ip) {
print_int(ip & 0xFF);
zenith::putchar('.');
print_int((ip >> 8) & 0xFF);
zenith::putchar('.');
print_int((ip >> 16) & 0xFF);
zenith::putchar('.');
print_int((ip >> 24) & 0xFF);
}
static bool parse_uint16(const char* s, uint16_t* out) {
uint32_t val = 0;
if (*s == '\0') return false;
while (*s) {
if (*s < '0' || *s > '9') return false;
val = val * 10 + (*s - '0');
if (val > 65535) return false;
s++;
}
*out = (uint16_t)val;
return true;
}
extern "C" void _start() {
char args[256];
int len = zenith::getargs(args, sizeof(args));
if (len <= 0 || args[0] == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
zenith::exit(1);
}
// Parse IP address (up to first space)
char ipStr[32];
int i = 0;
while (args[i] && args[i] != ' ' && i < 31) {
ipStr[i] = args[i];
i++;
}
ipStr[i] = '\0';
uint32_t ip;
if (!parse_ip(ipStr, &ip)) {
zenith::print("Invalid IP address: ");
zenith::print(ipStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Parse port
const char* portStr = skip_spaces(args + i);
if (*portStr == '\0') {
zenith::print("Usage: tcpconnect <ip> <port>\n");
zenith::exit(1);
}
uint16_t port;
if (!parse_uint16(portStr, &port)) {
zenith::print("Invalid port: ");
zenith::print(portStr);
zenith::putchar('\n');
zenith::exit(1);
}
// Create socket
int fd = zenith::socket(Zenith::SOCK_TCP);
if (fd < 0) {
zenith::print("Error: failed to create socket\n");
zenith::exit(1);
}
zenith::print("Connecting to ");
print_ip(ip);
zenith::putchar(':');
print_int(port);
zenith::print("...\n");
if (zenith::connect(fd, ip, port) < 0) {
zenith::print("Error: connection failed\n");
zenith::closesocket(fd);
zenith::exit(1);
}
zenith::print("Connected! Type to send, Ctrl+Q to disconnect.\n");
// Interactive send/receive loop
char sendBuf[256];
int sendPos = 0;
uint8_t recvBuf[512];
while (true) {
// Poll for received data (non-blocking)
int r = zenith::recv(fd, recvBuf, sizeof(recvBuf) - 1);
if (r < 0) {
zenith::print("\nConnection closed by remote.\n");
break;
}
if (r > 0) {
recvBuf[r] = '\0';
zenith::print((const char*)recvBuf);
}
// Poll keyboard
if (zenith::is_key_available()) {
Zenith::KeyEvent ev;
zenith::getkey(&ev);
if (!ev.pressed) continue;
// Ctrl+Q to quit
if (ev.ctrl && (ev.ascii == 'q' || ev.ascii == 'Q')) {
zenith::print("\nDisconnecting...\n");
break;
}
if (ev.ascii == '\n') {
sendBuf[sendPos++] = '\n';
zenith::putchar('\n');
zenith::send(fd, sendBuf, sendPos);
sendPos = 0;
} else if (ev.ascii == '\b') {
if (sendPos > 0) {
sendPos--;
zenith::putchar('\b');
zenith::putchar(' ');
zenith::putchar('\b');
}
} else if (ev.ascii >= ' ' && sendPos < 254) {
sendBuf[sendPos++] = ev.ascii;
zenith::putchar(ev.ascii);
}
} else {
// No key and no data -- yield to avoid busy-spinning
zenith::yield();
}
}
zenith::closesocket(fd);
zenith::exit(0);
}
+40
View File
@@ -0,0 +1,40 @@
/*
* main.cpp
* uptime - Show system uptime
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include <zenith/syscall.h>
static void print_int(uint64_t n) {
if (n == 0) {
zenith::putchar('0');
return;
}
char buf[20];
int i = 0;
while (n > 0) {
buf[i++] = '0' + (n % 10);
n /= 10;
}
for (int j = i - 1; j >= 0; j--) {
zenith::putchar(buf[j]);
}
}
extern "C" void _start() {
uint64_t ms = zenith::get_milliseconds();
uint64_t secs = ms / 1000;
uint64_t mins = secs / 60;
secs %= 60;
ms %= 1000;
zenith::print("Uptime: ");
print_int(mins);
zenith::print("m ");
print_int(secs);
zenith::print("s ");
print_int(ms);
zenith::print("ms\n");
zenith::exit(0);
}