Implement networking syscalls, ftp and wget apps (AI)

This commit is contained in:
AI
2026-02-24 07:51:33 +00:00
parent e6929438a0
commit 57b2751a81
7 changed files with 768 additions and 20 deletions

302
apps/ftp/ftp.c Normal file
View 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;
}

View File

@@ -26,6 +26,11 @@ typedef int int32_t;
#define SYS_READDIR 10
#define SYS_OPEN 11
#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) {
int32_t ret;
@@ -124,6 +129,71 @@ static inline int32_t close(int32_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 */
static inline uint32_t strlen(const char *s) {
uint32_t len = 0;

236
apps/wget/wget.c Normal file
View 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;
}