Attempt 2 #2
@@ -75,7 +75,7 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys`
|
- [x] Create a ARP subsystem. Create the `arp` command that shows current ARP tables. Again, this info should be found in `/sys`
|
||||||
- [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
|
- [x] Create a DHCP subsystem. Create the `dhcp` command to show current DHCP status information.
|
||||||
- [x] Create a UDP and TCP stack.
|
- [x] Create a UDP and TCP stack.
|
||||||
- [ ] Implement a simple version of `ftp` and `wget`.
|
- [x] Implement a simple version of `ftp` and `wget`.
|
||||||
- [ ] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
|
- [ ] Create a graphics subsystem. It should provide functionality to switch between the normal text mode, and a graphics mode.
|
||||||
- [ ] Create a simple game of pool. It should use graphics mode to render the game.
|
- [ ] Create a simple game of pool. It should use graphics mode to render the game.
|
||||||
- [ ] Create a simple game of minigolf.
|
- [ ] Create a simple game of minigolf.
|
||||||
|
|||||||
302
apps/ftp/ftp.c
Normal file
302
apps/ftp/ftp.c
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/**
|
||||||
|
* @file ftp.c
|
||||||
|
* @brief Simple FTP client for ClaudeOS.
|
||||||
|
*
|
||||||
|
* Connects to an FTP server and provides a minimal interactive
|
||||||
|
* interface for sending FTP commands and viewing responses.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ftp <ip>[:<port>]
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* ftp 10.0.2.2
|
||||||
|
* ftp 192.168.1.1:2121
|
||||||
|
*
|
||||||
|
* Once connected, type FTP commands directly:
|
||||||
|
* USER anonymous
|
||||||
|
* PASS user@
|
||||||
|
* LIST
|
||||||
|
* QUIT
|
||||||
|
*
|
||||||
|
* The client handles the control connection. PASV data connections
|
||||||
|
* are not supported in this minimal version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a decimal number from a string.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_uint(const char **s) {
|
||||||
|
uint32_t val = 0;
|
||||||
|
while (**s >= '0' && **s <= '9') {
|
||||||
|
val = val * 10 + (uint32_t)(**s - '0');
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an IPv4 address.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_ip(const char **s) {
|
||||||
|
uint32_t ip = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uint32_t octet = parse_uint(s);
|
||||||
|
if (octet > 255) return 0;
|
||||||
|
ip = (ip << 8) | octet;
|
||||||
|
if (i < 3) {
|
||||||
|
if (**s != '.') return 0;
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a decimal number.
|
||||||
|
*/
|
||||||
|
static void print_dec(uint32_t val) {
|
||||||
|
char buf[12];
|
||||||
|
int i = 0;
|
||||||
|
if (val == 0) { putchar('0'); return; }
|
||||||
|
while (val > 0) {
|
||||||
|
buf[i++] = '0' + (char)(val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
while (i > 0) putchar(buf[--i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format IP address.
|
||||||
|
*/
|
||||||
|
static void ip_to_str(uint32_t ip, char *buf) {
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 3; i >= 0; i--) {
|
||||||
|
uint32_t octet = (ip >> (i * 8)) & 0xFF;
|
||||||
|
char tmp[4]; int ti = 0;
|
||||||
|
if (octet == 0) { tmp[ti++] = '0'; }
|
||||||
|
else { while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; } }
|
||||||
|
while (ti > 0) buf[pos++] = tmp[--ti];
|
||||||
|
if (i > 0) buf[pos++] = '.';
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a line from stdin with echo.
|
||||||
|
* Returns length (excluding newline).
|
||||||
|
*/
|
||||||
|
static int readline(char *buf, int maxlen) {
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < maxlen - 1) {
|
||||||
|
char c;
|
||||||
|
int32_t n = read(0, &c, 1);
|
||||||
|
if (n <= 0) { yield(); continue; }
|
||||||
|
if (c == '\n' || c == '\r') { putchar('\n'); break; }
|
||||||
|
if (c == '\b' || c == 127) {
|
||||||
|
if (pos > 0) { pos--; puts("\b \b"); }
|
||||||
|
} else if (c >= 32) {
|
||||||
|
buf[pos++] = c;
|
||||||
|
putchar(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive and print FTP server response.
|
||||||
|
* Reads until we get a line starting with a 3-digit code followed by space.
|
||||||
|
* Returns the response code, or -1 on error.
|
||||||
|
*/
|
||||||
|
static int32_t recv_response(int32_t sockfd) {
|
||||||
|
char buf[512];
|
||||||
|
int total = 0;
|
||||||
|
int timeout = 500;
|
||||||
|
int code = -1;
|
||||||
|
|
||||||
|
while (timeout > 0) {
|
||||||
|
int32_t n = net_recv(sockfd, buf + total,
|
||||||
|
(uint32_t)(sizeof(buf) - 1 - (uint32_t)total));
|
||||||
|
if (n > 0) {
|
||||||
|
total += n;
|
||||||
|
buf[total] = '\0';
|
||||||
|
|
||||||
|
/* Print received data */
|
||||||
|
write(1, buf + total - n, (uint32_t)n);
|
||||||
|
|
||||||
|
/* Check if we have a complete response.
|
||||||
|
* FTP response ends when we get "NNN " (3 digits + space) at start of line. */
|
||||||
|
int got_complete = 0;
|
||||||
|
for (int i = 0; i < total; i++) {
|
||||||
|
/* Check for line start */
|
||||||
|
if (i == 0 || (i > 0 && buf[i-1] == '\n')) {
|
||||||
|
/* Check for "NNN " pattern */
|
||||||
|
if (i + 3 < total &&
|
||||||
|
buf[i] >= '0' && buf[i] <= '9' &&
|
||||||
|
buf[i+1] >= '0' && buf[i+1] <= '9' &&
|
||||||
|
buf[i+2] >= '0' && buf[i+2] <= '9' &&
|
||||||
|
buf[i+3] == ' ') {
|
||||||
|
code = (buf[i] - '0') * 100 +
|
||||||
|
(buf[i+1] - '0') * 10 +
|
||||||
|
(buf[i+2] - '0');
|
||||||
|
got_complete = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (got_complete) break;
|
||||||
|
timeout = 200;
|
||||||
|
} else if (n < 0) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
yield();
|
||||||
|
timeout--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an FTP command (appends \r\n).
|
||||||
|
*/
|
||||||
|
static int32_t send_cmd(int32_t sockfd, const char *cmd) {
|
||||||
|
char buf[256];
|
||||||
|
uint32_t len = 0;
|
||||||
|
while (cmd[len] && len < sizeof(buf) - 3) {
|
||||||
|
buf[len] = cmd[len];
|
||||||
|
len++;
|
||||||
|
}
|
||||||
|
buf[len++] = '\r';
|
||||||
|
buf[len++] = '\n';
|
||||||
|
return net_send(sockfd, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char arg[256];
|
||||||
|
|
||||||
|
if (getenv("ARG1", arg, sizeof(arg)) < 0 || arg[0] == '\0') {
|
||||||
|
puts("Usage: ftp <ip>[:<port>]\n");
|
||||||
|
puts(" e.g. ftp 10.0.2.2\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse IP and port */
|
||||||
|
const char *p = arg;
|
||||||
|
uint32_t ip = parse_ip(&p);
|
||||||
|
if (ip == 0) {
|
||||||
|
puts("ftp: invalid IP address\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t port = 21;
|
||||||
|
if (*p == ':') {
|
||||||
|
p++;
|
||||||
|
port = parse_uint(&p);
|
||||||
|
if (port == 0 || port > 65535) {
|
||||||
|
puts("ftp: invalid port\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char ip_str[64];
|
||||||
|
ip_to_str(ip, ip_str);
|
||||||
|
|
||||||
|
puts("Connecting to ");
|
||||||
|
puts(ip_str);
|
||||||
|
putchar(':');
|
||||||
|
print_dec(port);
|
||||||
|
puts("...\n");
|
||||||
|
|
||||||
|
/* Create TCP socket */
|
||||||
|
int32_t sockfd = socket(SOCK_TCP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
puts("ftp: failed to create socket\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect */
|
||||||
|
if (connect(sockfd, ip, port) < 0) {
|
||||||
|
puts("ftp: connect failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for connection */
|
||||||
|
int timeout = 500;
|
||||||
|
while (timeout > 0) {
|
||||||
|
int32_t state = sockstate(sockfd);
|
||||||
|
if (state == TCP_STATE_ESTABLISHED) break;
|
||||||
|
if (state == TCP_STATE_CLOSED) {
|
||||||
|
puts("ftp: connection refused\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
timeout--;
|
||||||
|
}
|
||||||
|
if (timeout <= 0) {
|
||||||
|
puts("ftp: connection timed out\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
puts("Connected to ");
|
||||||
|
puts(ip_str);
|
||||||
|
puts("\n");
|
||||||
|
|
||||||
|
/* Read server welcome banner */
|
||||||
|
int32_t code = recv_response(sockfd);
|
||||||
|
if (code < 0 || code >= 400) {
|
||||||
|
puts("ftp: server rejected connection\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive command loop */
|
||||||
|
char cmd[256];
|
||||||
|
for (;;) {
|
||||||
|
puts("ftp> ");
|
||||||
|
int len = readline(cmd, sizeof(cmd));
|
||||||
|
if (len == 0) continue;
|
||||||
|
|
||||||
|
/* Check for local quit command */
|
||||||
|
if (strcmp(cmd, "quit") == 0 || strcmp(cmd, "exit") == 0 ||
|
||||||
|
strcmp(cmd, "bye") == 0) {
|
||||||
|
send_cmd(sockfd, "QUIT");
|
||||||
|
recv_response(sockfd);
|
||||||
|
puts("Goodbye.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for help */
|
||||||
|
if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
|
||||||
|
puts("ClaudeOS FTP Client\n");
|
||||||
|
puts("Send raw FTP commands:\n");
|
||||||
|
puts(" USER <username> - specify username\n");
|
||||||
|
puts(" PASS <password> - specify password\n");
|
||||||
|
puts(" PWD - print working directory\n");
|
||||||
|
puts(" CWD <path> - change directory\n");
|
||||||
|
puts(" LIST - list files (control channel only)\n");
|
||||||
|
puts(" SYST - show system type\n");
|
||||||
|
puts(" STAT - show server status\n");
|
||||||
|
puts(" QUIT - disconnect\n");
|
||||||
|
puts(" quit/exit/bye - disconnect and exit\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Send the command to server */
|
||||||
|
if (send_cmd(sockfd, cmd) < 0) {
|
||||||
|
puts("ftp: send failed\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Receive response */
|
||||||
|
code = recv_response(sockfd);
|
||||||
|
if (code < 0) {
|
||||||
|
puts("ftp: connection lost\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -26,6 +26,11 @@ typedef int int32_t;
|
|||||||
#define SYS_READDIR 10
|
#define SYS_READDIR 10
|
||||||
#define SYS_OPEN 11
|
#define SYS_OPEN 11
|
||||||
#define SYS_CLOSE 12
|
#define SYS_CLOSE 12
|
||||||
|
#define SYS_SOCKET 13
|
||||||
|
#define SYS_CONNECT 14
|
||||||
|
#define SYS_SEND 15
|
||||||
|
#define SYS_RECV 16
|
||||||
|
#define SYS_SOCKSTATE 17
|
||||||
|
|
||||||
static inline int32_t syscall0(int num) {
|
static inline int32_t syscall0(int num) {
|
||||||
int32_t ret;
|
int32_t ret;
|
||||||
@@ -124,6 +129,71 @@ static inline int32_t close(int32_t fd) {
|
|||||||
return syscall1(SYS_CLOSE, (uint32_t)fd);
|
return syscall1(SYS_CLOSE, (uint32_t)fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Networking system calls
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Socket type constants. */
|
||||||
|
#define SOCK_TCP 0
|
||||||
|
#define SOCK_UDP 1
|
||||||
|
|
||||||
|
/** TCP state constants (match kernel tcp.h). */
|
||||||
|
#define TCP_STATE_CLOSED 0
|
||||||
|
#define TCP_STATE_SYN_SENT 2
|
||||||
|
#define TCP_STATE_ESTABLISHED 4
|
||||||
|
#define TCP_STATE_CLOSE_WAIT 7
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a network socket.
|
||||||
|
* @param type SOCK_TCP (0) or SOCK_UDP (1).
|
||||||
|
* @return Socket descriptor (>= 0) or -1 on failure.
|
||||||
|
*/
|
||||||
|
static inline int32_t socket(uint32_t type) {
|
||||||
|
return syscall1(SYS_SOCKET, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect a TCP socket to a remote host.
|
||||||
|
* @param sockfd Socket descriptor.
|
||||||
|
* @param ip Remote IP address (host byte order).
|
||||||
|
* @param port Remote port (host byte order).
|
||||||
|
* @return 0 on success (SYN sent), -1 on failure.
|
||||||
|
*/
|
||||||
|
static inline int32_t connect(int32_t sockfd, uint32_t ip, uint32_t port) {
|
||||||
|
return syscall3(SYS_CONNECT, (uint32_t)sockfd, ip, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send data on a connected socket.
|
||||||
|
* @param sockfd Socket descriptor.
|
||||||
|
* @param buf Data buffer.
|
||||||
|
* @param len Data length.
|
||||||
|
* @return Bytes sent, or -1 on failure.
|
||||||
|
*/
|
||||||
|
static inline int32_t net_send(int32_t sockfd, const void *buf, uint32_t len) {
|
||||||
|
return syscall3(SYS_SEND, (uint32_t)sockfd, (uint32_t)buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive data from a connected socket (non-blocking).
|
||||||
|
* @param sockfd Socket descriptor.
|
||||||
|
* @param buf Buffer.
|
||||||
|
* @param bufsize Buffer size.
|
||||||
|
* @return Bytes received, 0 if no data, -1 on error/closed.
|
||||||
|
*/
|
||||||
|
static inline int32_t net_recv(int32_t sockfd, void *buf, uint32_t bufsize) {
|
||||||
|
return syscall3(SYS_RECV, (uint32_t)sockfd, (uint32_t)buf, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the state of a TCP socket.
|
||||||
|
* @param sockfd Socket descriptor.
|
||||||
|
* @return TCP state constant, or -1.
|
||||||
|
*/
|
||||||
|
static inline int32_t sockstate(int32_t sockfd) {
|
||||||
|
return syscall1(SYS_SOCKSTATE, (uint32_t)sockfd);
|
||||||
|
}
|
||||||
|
|
||||||
/* Basic string operations for user-space */
|
/* Basic string operations for user-space */
|
||||||
static inline uint32_t strlen(const char *s) {
|
static inline uint32_t strlen(const char *s) {
|
||||||
uint32_t len = 0;
|
uint32_t len = 0;
|
||||||
|
|||||||
236
apps/wget/wget.c
Normal file
236
apps/wget/wget.c
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
/**
|
||||||
|
* @file wget.c
|
||||||
|
* @brief Simple HTTP client for ClaudeOS.
|
||||||
|
*
|
||||||
|
* Downloads a resource from an HTTP server using a TCP connection.
|
||||||
|
* Only supports HTTP/1.0 GET with IP addresses (no DNS).
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* wget <ip>[:<port>]/<path>
|
||||||
|
* wget <ip> - fetches /
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* wget 10.0.2.2/index.html
|
||||||
|
* wget 10.0.2.2:8080/api/data
|
||||||
|
* wget 192.168.1.1
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "syscalls.h"
|
||||||
|
|
||||||
|
typedef unsigned char uint8_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a decimal number from a string.
|
||||||
|
* Advances *s past the digits.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_uint(const char **s) {
|
||||||
|
uint32_t val = 0;
|
||||||
|
while (**s >= '0' && **s <= '9') {
|
||||||
|
val = val * 10 + (uint32_t)(**s - '0');
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an IPv4 dotted-decimal address into a 32-bit host-order integer.
|
||||||
|
* Returns 0 on failure.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_ip(const char **s) {
|
||||||
|
uint32_t ip = 0;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
uint32_t octet = parse_uint(s);
|
||||||
|
if (octet > 255) return 0;
|
||||||
|
ip = (ip << 8) | octet;
|
||||||
|
if (i < 3) {
|
||||||
|
if (**s != '.') return 0;
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a decimal number.
|
||||||
|
*/
|
||||||
|
static void print_dec(uint32_t val) {
|
||||||
|
char buf[12];
|
||||||
|
int i = 0;
|
||||||
|
if (val == 0) { putchar('0'); return; }
|
||||||
|
while (val > 0) {
|
||||||
|
buf[i++] = '0' + (char)(val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
while (i > 0) putchar(buf[--i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the HTTP GET request.
|
||||||
|
* Returns length of the request string.
|
||||||
|
*/
|
||||||
|
static uint32_t build_request(char *buf, uint32_t bufsize,
|
||||||
|
const char *host, const char *path) {
|
||||||
|
uint32_t pos = 0;
|
||||||
|
|
||||||
|
/* "GET <path> HTTP/1.0\r\nHost: <host>\r\nConnection: close\r\n\r\n" */
|
||||||
|
const char *parts[] = {
|
||||||
|
"GET ", path, " HTTP/1.0\r\nHost: ", host,
|
||||||
|
"\r\nConnection: close\r\n\r\n", (const char *)0
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; parts[i]; i++) {
|
||||||
|
const char *s = parts[i];
|
||||||
|
while (*s && pos < bufsize - 1) {
|
||||||
|
buf[pos++] = *s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format an IP address as a dotted-decimal string.
|
||||||
|
*/
|
||||||
|
static void ip_to_str(uint32_t ip, char *buf) {
|
||||||
|
int pos = 0;
|
||||||
|
for (int i = 3; i >= 0; i--) {
|
||||||
|
uint32_t octet = (ip >> (i * 8)) & 0xFF;
|
||||||
|
char tmp[4];
|
||||||
|
int ti = 0;
|
||||||
|
if (octet == 0) { tmp[ti++] = '0'; }
|
||||||
|
else {
|
||||||
|
while (octet > 0) { tmp[ti++] = '0' + (char)(octet % 10); octet /= 10; }
|
||||||
|
}
|
||||||
|
while (ti > 0) buf[pos++] = tmp[--ti];
|
||||||
|
if (i > 0) buf[pos++] = '.';
|
||||||
|
}
|
||||||
|
buf[pos] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
char url[256];
|
||||||
|
|
||||||
|
/* Get URL from ARG1 */
|
||||||
|
if (getenv("ARG1", url, sizeof(url)) < 0 || url[0] == '\0') {
|
||||||
|
puts("Usage: wget <ip>[:<port>]/<path>\n");
|
||||||
|
puts(" e.g. wget 10.0.2.2/index.html\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse: skip optional "http://" prefix */
|
||||||
|
const char *p = url;
|
||||||
|
if (strncmp(p, "http://", 7) == 0) {
|
||||||
|
p += 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse IP address */
|
||||||
|
uint32_t ip = parse_ip(&p);
|
||||||
|
if (ip == 0) {
|
||||||
|
puts("wget: invalid IP address\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse optional port */
|
||||||
|
uint32_t port = 80;
|
||||||
|
if (*p == ':') {
|
||||||
|
p++;
|
||||||
|
port = parse_uint(&p);
|
||||||
|
if (port == 0 || port > 65535) {
|
||||||
|
puts("wget: invalid port\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse path (default to /) */
|
||||||
|
const char *path = "/";
|
||||||
|
if (*p == '/') {
|
||||||
|
path = p;
|
||||||
|
} else if (*p != '\0') {
|
||||||
|
puts("wget: invalid URL format\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build host string for Host header */
|
||||||
|
char host_str[64];
|
||||||
|
ip_to_str(ip, host_str);
|
||||||
|
|
||||||
|
/* Print what we're doing */
|
||||||
|
puts("Connecting to ");
|
||||||
|
puts(host_str);
|
||||||
|
putchar(':');
|
||||||
|
print_dec(port);
|
||||||
|
puts(path);
|
||||||
|
puts("...\n");
|
||||||
|
|
||||||
|
/* Create TCP socket */
|
||||||
|
int32_t sockfd = socket(SOCK_TCP);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
puts("wget: failed to create socket\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect */
|
||||||
|
if (connect(sockfd, ip, port) < 0) {
|
||||||
|
puts("wget: connect failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for connection to establish (poll with yield) */
|
||||||
|
int timeout = 500; /* ~5 seconds at ~100 yields/sec */
|
||||||
|
while (timeout > 0) {
|
||||||
|
int32_t state = sockstate(sockfd);
|
||||||
|
if (state == TCP_STATE_ESTABLISHED) break;
|
||||||
|
if (state == TCP_STATE_CLOSED) {
|
||||||
|
puts("wget: connection refused\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
yield();
|
||||||
|
timeout--;
|
||||||
|
}
|
||||||
|
if (timeout <= 0) {
|
||||||
|
puts("wget: connection timed out\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
puts("Connected.\n");
|
||||||
|
|
||||||
|
/* Build and send HTTP request */
|
||||||
|
char req[512];
|
||||||
|
uint32_t req_len = build_request(req, sizeof(req), host_str, path);
|
||||||
|
|
||||||
|
int32_t sent = net_send(sockfd, req, req_len);
|
||||||
|
if (sent < 0) {
|
||||||
|
puts("wget: send failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Receive response */
|
||||||
|
puts("--- Response ---\n");
|
||||||
|
char buf[512];
|
||||||
|
int done = 0;
|
||||||
|
int recv_timeout = 1000;
|
||||||
|
|
||||||
|
while (!done && recv_timeout > 0) {
|
||||||
|
int32_t n = net_recv(sockfd, buf, sizeof(buf) - 1);
|
||||||
|
if (n > 0) {
|
||||||
|
buf[n] = '\0';
|
||||||
|
write(1, buf, (uint32_t)n);
|
||||||
|
recv_timeout = 200; /* Reset timeout on data */
|
||||||
|
} else if (n < 0) {
|
||||||
|
/* Connection closed or error */
|
||||||
|
done = 1;
|
||||||
|
} else {
|
||||||
|
/* No data yet */
|
||||||
|
yield();
|
||||||
|
recv_timeout--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
puts("\n--- End ---\n");
|
||||||
|
|
||||||
|
/* Close socket - we use close() which goes through SYS_CLOSE.
|
||||||
|
* For sockets, we should ideally have a socket-specific close,
|
||||||
|
* but for simplicity, the socket will be cleaned up on process exit. */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
14
build.log
14
build.log
@@ -13,6 +13,8 @@ Building app: env-test
|
|||||||
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes)
|
||||||
Building app: fork-test
|
Building app: fork-test
|
||||||
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/fork-test (132 bytes)
|
||||||
|
Building app: ftp
|
||||||
|
Built: /workspaces/claude-os/build/apps_bin/ftp (3406 bytes)
|
||||||
Building app: hello-world
|
Building app: hello-world
|
||||||
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes)
|
||||||
Building app: ip
|
Building app: ip
|
||||||
@@ -34,9 +36,11 @@ Building app: sh
|
|||||||
| ^~~~
|
| ^~~~
|
||||||
1 warning generated.
|
1 warning generated.
|
||||||
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes)
|
||||||
|
Building app: wget
|
||||||
|
Built: /workspaces/claude-os/build/apps_bin/wget (2193 bytes)
|
||||||
[ 2%] Built target apps
|
[ 2%] Built target apps
|
||||||
[ 5%] Built target initrd
|
[ 5%] Built target initrd
|
||||||
[ 7%] Building C object src/CMakeFiles/kernel.dir/tcp.c.o
|
[ 7%] Building C object src/CMakeFiles/kernel.dir/syscall.c.o
|
||||||
[ 10%] Linking C executable ../bin/kernel
|
[ 10%] Linking C executable ../bin/kernel
|
||||||
[ 97%] Built target kernel
|
[ 97%] Built target kernel
|
||||||
[100%] Generating bootable ISO image
|
[100%] Generating bootable ISO image
|
||||||
@@ -46,14 +50,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso'
|
|||||||
Media current: stdio file, overwriteable
|
Media current: stdio file, overwriteable
|
||||||
Media status : is blank
|
Media status : is blank
|
||||||
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
Media summary: 0 sessions, 0 data blocks, 0 data, 126g free
|
||||||
Added to ISO image: directory '/'='/tmp/grub.onhBjG'
|
Added to ISO image: directory '/'='/tmp/grub.GnJedF'
|
||||||
xorriso : UPDATE : 581 files added in 1 seconds
|
xorriso : UPDATE : 581 files added in 1 seconds
|
||||||
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir'
|
||||||
xorriso : UPDATE : 586 files added in 1 seconds
|
xorriso : UPDATE : 586 files added in 1 seconds
|
||||||
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img'
|
||||||
xorriso : UPDATE : Thank you for being patient. Working since 0 seconds.
|
xorriso : UPDATE : 65.27% done
|
||||||
ISO image produced: 6027 sectors
|
ISO image produced: 6030 sectors
|
||||||
Written to medium : 6027 sectors at LBA 0
|
Written to medium : 6030 sectors at LBA 0
|
||||||
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully.
|
||||||
|
|
||||||
[100%] Built target iso
|
[100%] Built target iso
|
||||||
|
|||||||
131
src/syscall.c
131
src/syscall.c
@@ -17,6 +17,8 @@
|
|||||||
#include "cpio.h"
|
#include "cpio.h"
|
||||||
#include "paging.h"
|
#include "paging.h"
|
||||||
#include "pmm.h"
|
#include "pmm.h"
|
||||||
|
#include "tcp.h"
|
||||||
|
#include "udp.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -339,6 +341,130 @@ static int32_t sys_readdir(registers_t *regs) {
|
|||||||
return (int32_t)entry.type;
|
return (int32_t)entry.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
* Networking system calls
|
||||||
|
* ================================================================ */
|
||||||
|
|
||||||
|
/** Socket type constants (kernel side). */
|
||||||
|
#define SOCK_TYPE_TCP 0
|
||||||
|
#define SOCK_TYPE_UDP 1
|
||||||
|
|
||||||
|
/** Per-process socket tracking (simple: global table). */
|
||||||
|
#define MAX_USER_SOCKETS 16
|
||||||
|
static struct {
|
||||||
|
uint8_t active;
|
||||||
|
uint8_t type; /* SOCK_TYPE_TCP or SOCK_TYPE_UDP */
|
||||||
|
int kern_sockfd; /* Kernel-side socket index */
|
||||||
|
} user_sockets[MAX_USER_SOCKETS];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_SOCKET: create a network socket.
|
||||||
|
* EBX = type (0=TCP, 1=UDP).
|
||||||
|
* Returns user sockfd (>= 0) or -1.
|
||||||
|
*/
|
||||||
|
static int32_t sys_socket(registers_t *regs) {
|
||||||
|
uint32_t type = regs->ebx;
|
||||||
|
int kern_fd;
|
||||||
|
|
||||||
|
if (type == SOCK_TYPE_TCP) {
|
||||||
|
kern_fd = tcp_socket_create();
|
||||||
|
} else if (type == SOCK_TYPE_UDP) {
|
||||||
|
kern_fd = udp_socket_create();
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kern_fd < 0) return -1;
|
||||||
|
|
||||||
|
/* Find a free user socket slot */
|
||||||
|
for (int i = 0; i < MAX_USER_SOCKETS; i++) {
|
||||||
|
if (!user_sockets[i].active) {
|
||||||
|
user_sockets[i].active = 1;
|
||||||
|
user_sockets[i].type = (uint8_t)type;
|
||||||
|
user_sockets[i].kern_sockfd = kern_fd;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No free slots — close the kernel socket */
|
||||||
|
if (type == SOCK_TYPE_TCP) tcp_close(kern_fd);
|
||||||
|
else udp_close(kern_fd);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_CONNECT: connect a TCP socket to a remote host.
|
||||||
|
* EBX = user sockfd, ECX = remote IP (host byte order), EDX = remote port.
|
||||||
|
* Returns 0 on success (SYN sent), -1 on failure.
|
||||||
|
*/
|
||||||
|
static int32_t sys_connect(registers_t *regs) {
|
||||||
|
int ufd = (int)regs->ebx;
|
||||||
|
uint32_t remote_ip = regs->ecx;
|
||||||
|
uint16_t remote_port = (uint16_t)regs->edx;
|
||||||
|
|
||||||
|
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
|
||||||
|
return -1;
|
||||||
|
if (user_sockets[ufd].type != SOCK_TYPE_TCP)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return tcp_connect(user_sockets[ufd].kern_sockfd, remote_ip, remote_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_SEND: send data on a socket.
|
||||||
|
* EBX = user sockfd, ECX = buffer pointer, EDX = length.
|
||||||
|
* Returns bytes sent or -1.
|
||||||
|
*/
|
||||||
|
static int32_t sys_send(registers_t *regs) {
|
||||||
|
int ufd = (int)regs->ebx;
|
||||||
|
const void *buf = (const void *)regs->ecx;
|
||||||
|
uint32_t len = regs->edx;
|
||||||
|
|
||||||
|
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
|
||||||
|
return tcp_send(user_sockets[ufd].kern_sockfd, buf, len);
|
||||||
|
else
|
||||||
|
return -1; /* UDP sendto requires address — not supported via SYS_SEND */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_RECV: receive data from a socket (non-blocking).
|
||||||
|
* EBX = user sockfd, ECX = buffer pointer, EDX = buffer size.
|
||||||
|
* Returns bytes received, 0 if no data, -1 on error/closed.
|
||||||
|
*/
|
||||||
|
static int32_t sys_recv(registers_t *regs) {
|
||||||
|
int ufd = (int)regs->ebx;
|
||||||
|
void *buf = (void *)regs->ecx;
|
||||||
|
uint32_t bufsize = regs->edx;
|
||||||
|
|
||||||
|
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
|
||||||
|
return tcp_recv(user_sockets[ufd].kern_sockfd, buf, bufsize);
|
||||||
|
else
|
||||||
|
return -1; /* UDP recvfrom requires address — not supported via SYS_RECV */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle SYS_SOCKSTATE: get the state of a TCP socket.
|
||||||
|
* EBX = user sockfd.
|
||||||
|
* Returns TCP state constant, or -1 for invalid/UDP sockets.
|
||||||
|
*/
|
||||||
|
static int32_t sys_sockstate(registers_t *regs) {
|
||||||
|
int ufd = (int)regs->ebx;
|
||||||
|
|
||||||
|
if (ufd < 0 || ufd >= MAX_USER_SOCKETS || !user_sockets[ufd].active)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (user_sockets[ufd].type == SOCK_TYPE_TCP)
|
||||||
|
return (int32_t)tcp_get_state(user_sockets[ufd].kern_sockfd);
|
||||||
|
else
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
/** System call dispatch table. */
|
/** System call dispatch table. */
|
||||||
typedef int32_t (*syscall_fn)(registers_t *);
|
typedef int32_t (*syscall_fn)(registers_t *);
|
||||||
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
||||||
@@ -355,6 +481,11 @@ static syscall_fn syscall_table[NUM_SYSCALLS] = {
|
|||||||
[SYS_READDIR] = sys_readdir,
|
[SYS_READDIR] = sys_readdir,
|
||||||
[SYS_OPEN] = sys_open,
|
[SYS_OPEN] = sys_open,
|
||||||
[SYS_CLOSE] = sys_close,
|
[SYS_CLOSE] = sys_close,
|
||||||
|
[SYS_SOCKET] = sys_socket,
|
||||||
|
[SYS_CONNECT] = sys_connect,
|
||||||
|
[SYS_SEND] = sys_send,
|
||||||
|
[SYS_RECV] = sys_recv,
|
||||||
|
[SYS_SOCKSTATE] = sys_sockstate,
|
||||||
};
|
};
|
||||||
|
|
||||||
void syscall_handler(registers_t *regs) {
|
void syscall_handler(registers_t *regs) {
|
||||||
|
|||||||
@@ -27,9 +27,14 @@
|
|||||||
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
|
#define SYS_READDIR 10 /**< Read directory entry. path=EBX, idx=ECX, buf=EDX. Returns type or -1. */
|
||||||
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
|
#define SYS_OPEN 11 /**< Open a file. path=EBX, flags=ECX. Returns fd or -1. */
|
||||||
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
|
#define SYS_CLOSE 12 /**< Close a file descriptor. fd=EBX. Returns 0 or -1. */
|
||||||
|
#define SYS_SOCKET 13 /**< Create a network socket. type=EBX (0=TCP, 1=UDP). Returns sockfd. */
|
||||||
|
#define SYS_CONNECT 14 /**< Connect TCP socket. sockfd=EBX, ip=ECX (host order), port=EDX. */
|
||||||
|
#define SYS_SEND 15 /**< Send data on socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes sent. */
|
||||||
|
#define SYS_RECV 16 /**< Receive data from socket. sockfd=EBX, buf=ECX, len=EDX. Returns bytes. */
|
||||||
|
#define SYS_SOCKSTATE 17 /**< Get socket state. sockfd=EBX. Returns state constant. */
|
||||||
|
|
||||||
/** Total number of system calls. */
|
/** Total number of system calls. */
|
||||||
#define NUM_SYSCALLS 13
|
#define NUM_SYSCALLS 18
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the system call handler.
|
* Initialize the system call handler.
|
||||||
|
|||||||
Reference in New Issue
Block a user