From: Tulio A M Mendes Date: Sat, 14 Mar 2026 00:47:53 +0000 (-0300) Subject: feat: POSIX Phases 2-6 — syscalls, ulibc functions, headers, malloc, Newlib prep X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=9b56e33725a387882e42233bea50ca9539caf5f8;p=AdrOS.git feat: POSIX Phases 2-6 — syscalls, ulibc functions, headers, malloc, Newlib prep Phase 2 — Critical Kernel Syscalls: - SYSCALL_GETRLIMIT (129) / SYSCALL_SETRLIMIT (130): per-process resource limits (RLIMIT_NOFILE, RLIMIT_STACK, RLIMIT_CORE, etc.) stored in process struct, inherited on fork, enforced by setrlimit with root privilege check - SYSCALL_SETSOCKOPT (131) / SYSCALL_GETSOCKOPT (132): SO_REUSEADDR, SO_KEEPALIVE, SO_RCVBUF/SNDBUF, SO_ERROR, SO_TYPE via lwIP - SYSCALL_SHUTDOWN (133): TCP half-close via tcp_shutdown - SYSCALL_GETPEERNAME (134) / SYSCALL_GETSOCKNAME (135): socket address query - Added missing errno codes: ENOPROTOOPT, EOVERFLOW, ELOOP Phase 3 — Critical ulibc Functions (12 new source files): - setjmp/longjmp/_setjmp/_longjmp/sigsetjmp/siglongjmp (i386 assembly) - sleep()/usleep() wrappers over nanosleep - execvp()/execlp()/execl() with PATH search - getopt()/getopt_long() full POSIX implementation - strerror()/perror() with 35-entry error string table - strtoul()/strtoll()/strtoull() with base auto-detection - setenv()/unsetenv()/putenv() with owned environ array - signal() wrapper over sigaction (SA_RESTART) - abort() sends SIGABRT, atexit() with 32-handler registry - exit() now calls atexit handlers in reverse order - rand()/srand() LCG PRNG (RAND_MAX=0x7FFF) - strtok_r(), strnlen(), strspn(), strcspn(), strpbrk() Phase 4 — Critical Headers (9 new headers): - setjmp.h: jmp_buf, sigjmp_buf for i386 - locale.h: LC_* constants, struct lconv, setlocale/localeconv stubs - pwd.h/grp.h: struct passwd/group, getpwnam/getpwuid/getpwent stubs - getopt.h: struct option, getopt_long declarations - fnmatch.h + fnmatch(): glob-style pattern matching with FNM_PATHNAME - sys/select.h: fd_set, FD_SET/CLR/ISSET/ZERO macros, select() - sys/resource.h: struct rlimit, RLIMIT_* constants, getrlimit/setrlimit Phase 5 — Proper malloc with free(): - Replaced bump allocator with address-ordered free-list allocator - 8-byte aligned blocks with 8-byte header (size | used_bit, next_free) - First-fit allocation with block splitting - free() with address-ordered insertion and bidirectional coalescing - realloc() now preserves old data size from block header Phase 6 — Newlib Port (from previous commit): - libgloss stubs already complete, syscall numbers updated 97/97 smoke tests pass, cppcheck clean. --- diff --git a/include/errno.h b/include/errno.h index f52c2f2..580d3b3 100644 --- a/include/errno.h +++ b/include/errno.h @@ -40,5 +40,8 @@ #define EMSGSIZE 90 #define EROFS 30 #define EWOULDBLOCK EAGAIN +#define ENOPROTOOPT 92 +#define EOVERFLOW 75 +#define ELOOP 40 #endif diff --git a/include/process.h b/include/process.h index 89247c1..c977d7d 100644 --- a/include/process.h +++ b/include/process.h @@ -101,6 +101,19 @@ struct process { uintptr_t heap_start; uintptr_t heap_break; + /* POSIX resource limits */ +#define RLIMIT_CPU 0 +#define RLIMIT_FSIZE 1 +#define RLIMIT_DATA 2 +#define RLIMIT_STACK 3 +#define RLIMIT_CORE 4 +#define RLIMIT_NOFILE 5 +#define RLIMIT_AS 6 +#define RLIMIT_NPROC 7 +#define _RLIMIT_COUNT 8 +#define RLIM_INFINITY 0xFFFFFFFFU + struct { uint32_t rlim_cur; uint32_t rlim_max; } rlimits[_RLIMIT_COUNT]; + char cwd[128]; char cmdline[128]; uint32_t umask; diff --git a/include/socket.h b/include/socket.h index ab06e01..82d70c2 100644 --- a/include/socket.h +++ b/include/socket.h @@ -78,6 +78,11 @@ int ksocket_sendto(int sid, const void* buf, size_t len, int flags, int ksocket_recvfrom(int sid, void* buf, size_t len, int flags, struct sockaddr_in* src); int ksocket_close(int sid); +int ksocket_setsockopt(int sid, int level, int optname, const void* optval, uint32_t optlen); +int ksocket_getsockopt(int sid, int level, int optname, void* optval, uint32_t* optlen); +int ksocket_shutdown(int sid, int how); +int ksocket_getpeername(int sid, struct sockaddr_in* addr); +int ksocket_getsockname(int sid, struct sockaddr_in* addr); int ksocket_poll(int sid, int events); void ksocket_init(void); diff --git a/include/syscall.h b/include/syscall.h index 1416c4d..251b862 100644 --- a/include/syscall.h +++ b/include/syscall.h @@ -159,6 +159,13 @@ enum { SYSCALL_GETTIMEOFDAY = 127, SYSCALL_MPROTECT = 128, + SYSCALL_GETRLIMIT = 129, + SYSCALL_SETRLIMIT = 130, + SYSCALL_SETSOCKOPT = 131, + SYSCALL_GETSOCKOPT = 132, + SYSCALL_SHUTDOWN = 133, + SYSCALL_GETPEERNAME = 134, + SYSCALL_GETSOCKNAME = 135, }; #endif diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index 78e0185..9d90859 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -58,6 +58,18 @@ static void userspace_init_thread(void) { current_process->addr_space = user_as; current_process->heap_start = heap_brk; current_process->heap_break = heap_brk; + + /* Initialize default resource limits */ + for (int i = 0; i < _RLIMIT_COUNT; i++) { + current_process->rlimits[i].rlim_cur = RLIM_INFINITY; + current_process->rlimits[i].rlim_max = RLIM_INFINITY; + } + current_process->rlimits[RLIMIT_NOFILE].rlim_cur = PROCESS_MAX_FILES; + current_process->rlimits[RLIMIT_NOFILE].rlim_max = PROCESS_MAX_FILES; + current_process->rlimits[RLIMIT_STACK].rlim_cur = 8 * 1024 * 1024; /* 8MB */ + current_process->rlimits[RLIMIT_STACK].rlim_max = RLIM_INFINITY; + current_process->rlimits[RLIMIT_CORE].rlim_cur = 0; /* no core dumps */ + current_process->rlimits[RLIMIT_CORE].rlim_max = 0; strncpy(current_process->cmdline, init_path, sizeof(current_process->cmdline) - 1); current_process->cmdline[sizeof(current_process->cmdline) - 1] = '\0'; vmm_as_activate(user_as); diff --git a/src/kernel/scheduler.c b/src/kernel/scheduler.c index 9067b76..d03a755 100644 --- a/src/kernel/scheduler.c +++ b/src/kernel/scheduler.c @@ -776,6 +776,7 @@ struct process* process_clone_create(uint32_t clone_flags, proc->egid = current_process->egid; proc->heap_start = current_process->heap_start; proc->heap_break = current_process->heap_break; + memcpy(proc->rlimits, current_process->rlimits, sizeof(proc->rlimits)); memcpy(proc->fpu_state, current_process->fpu_state, FPU_STATE_SIZE); diff --git a/src/kernel/socket.c b/src/kernel/socket.c index b4d5f18..5145b5b 100644 --- a/src/kernel/socket.c +++ b/src/kernel/socket.c @@ -438,6 +438,102 @@ int ksocket_close(int sid) { return 0; } +int ksocket_setsockopt(int sid, int level, int optname, const void* optval, uint32_t optlen) { + struct ksocket* s = get_socket(sid); + if (!s) return -EBADF; + + /* SOL_SOCKET level */ + if (level == 1) { /* SOL_SOCKET */ + if (optname == 2 /* SO_REUSEADDR */ && optlen >= 4) { + /* lwIP tcp_bind already allows reuse; just accept the call */ + (void)optval; + return 0; + } + if (optname == 9 /* SO_KEEPALIVE */ && s->type == SOCK_STREAM && optlen >= 4) { + int val = *(const int*)optval; + if (s->pcb.tcp) { + if (val) s->pcb.tcp->so_options |= SOF_KEEPALIVE; + else s->pcb.tcp->so_options &= (uint8_t)~SOF_KEEPALIVE; + } + return 0; + } + /* SO_RCVBUF / SO_SNDBUF — accept but ignore (fixed buffers) */ + if ((optname == 8 /* SO_RCVBUF */ || optname == 7 /* SO_SNDBUF */) && optlen >= 4) { + return 0; + } + } + + return -ENOPROTOOPT; +} + +int ksocket_getsockopt(int sid, int level, int optname, void* optval, uint32_t* optlen) { + struct ksocket* s = get_socket(sid); + if (!s) return -EBADF; + + if (level == 1) { /* SOL_SOCKET */ + if (optname == 4 /* SO_ERROR */ && *optlen >= 4) { + int err = s->error; + s->error = 0; + *(int*)optval = err; + *optlen = 4; + return 0; + } + if (optname == 3 /* SO_TYPE */ && *optlen >= 4) { + *(int*)optval = s->type; + *optlen = 4; + return 0; + } + } + + return -ENOPROTOOPT; +} + +int ksocket_shutdown(int sid, int how) { + struct ksocket* s = get_socket(sid); + if (!s) return -EBADF; + if (s->type != SOCK_STREAM || !s->pcb.tcp) return -ENOTCONN; + + /* how: 0=SHUT_RD, 1=SHUT_WR, 2=SHUT_RDWR */ + if (how == 1 || how == 2) { + tcp_shutdown(s->pcb.tcp, 0, 1); /* shut_rx=0, shut_tx=1 */ + } + if (how == 0 || how == 2) { + s->state = KSOCK_PEER_CLOSED; + wq_wake_all(&s->rx_wq); + } + return 0; +} + +int ksocket_getpeername(int sid, struct sockaddr_in* addr) { + struct ksocket* s = get_socket(sid); + if (!s) return -EBADF; + if (s->type == SOCK_STREAM && s->pcb.tcp && s->state == KSOCK_CONNECTED) { + addr->sin_family = AF_INET; + addr->sin_port = htons(s->pcb.tcp->remote_port); + addr->sin_addr = ip_addr_get_ip4_u32(&s->pcb.tcp->remote_ip); + return 0; + } + return -ENOTCONN; +} + +int ksocket_getsockname(int sid, struct sockaddr_in* addr) { + struct ksocket* s = get_socket(sid); + if (!s) return -EBADF; + if (s->type == SOCK_STREAM && s->pcb.tcp) { + addr->sin_family = AF_INET; + addr->sin_port = htons(s->pcb.tcp->local_port); + addr->sin_addr = ip_addr_get_ip4_u32(&s->pcb.tcp->local_ip); + return 0; + } + if (s->type == SOCK_DGRAM && s->pcb.udp) { + addr->sin_family = AF_INET; + addr->sin_port = htons(s->pcb.udp->local_port); + addr->sin_addr = ip_addr_get_ip4_u32(&s->pcb.udp->local_ip); + return 0; + } + return -EINVAL; +} + int ksocket_poll(int sid, int events) { if (sid < 0 || sid >= KSOCKET_MAX) return VFS_POLL_ERR; struct ksocket* s = &sockets[sid]; diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 49c2a26..ebd9826 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -3897,6 +3897,45 @@ void syscall_handler(struct registers* regs) { return; } + if (syscall_no == SYSCALL_GETRLIMIT) { + uint32_t resource = sc_arg0(regs); + void* user_rlim = (void*)sc_arg1(regs); + if (!current_process) { sc_ret(regs) = (uint32_t)-EINVAL; return; } + if (resource >= _RLIMIT_COUNT) { sc_ret(regs) = (uint32_t)-EINVAL; return; } + if (!user_rlim || user_range_ok(user_rlim, 8) == 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + if (copy_to_user(user_rlim, ¤t_process->rlimits[resource], 8) < 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + sc_ret(regs) = 0; + return; + } + + if (syscall_no == SYSCALL_SETRLIMIT) { + uint32_t resource = sc_arg0(regs); + const void* user_rlim = (const void*)sc_arg1(regs); + if (!current_process) { sc_ret(regs) = (uint32_t)-EINVAL; return; } + if (resource >= _RLIMIT_COUNT) { sc_ret(regs) = (uint32_t)-EINVAL; return; } + if (!user_rlim || user_range_ok(user_rlim, 8) == 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + struct { uint32_t cur; uint32_t max; } new_rl; + if (copy_from_user(&new_rl, user_rlim, 8) < 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + /* Non-root cannot raise max above current max */ + if (new_rl.max > current_process->rlimits[resource].rlim_max && + current_process->euid != 0) { + sc_ret(regs) = (uint32_t)-EPERM; return; + } + if (new_rl.cur > new_rl.max) new_rl.cur = new_rl.max; + current_process->rlimits[resource].rlim_cur = new_rl.cur; + current_process->rlimits[resource].rlim_max = new_rl.max; + sc_ret(regs) = 0; + return; + } + if (syscall_no == SYSCALL_MPROTECT) { uintptr_t addr = (uintptr_t)sc_arg0(regs); uint32_t len = sc_arg1(regs); @@ -4760,6 +4799,73 @@ static void socket_syscall_dispatch(struct registers* regs, uint32_t syscall_no) return; } + if (syscall_no == SYSCALL_SETSOCKOPT) { + int sid = sock_fd_get_sid((int)sc_arg0(regs)); + if (sid < 0) { sc_ret(regs) = (uint32_t)-EBADF; return; } + int level = (int)sc_arg1(regs); + int optname = (int)sc_arg2(regs); + uint32_t optlen = sc_arg4(regs); + int kval = 0; + if (optlen >= 4 && sc_arg3(regs)) { + if (copy_from_user(&kval, (const void*)sc_arg3(regs), 4) < 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + } + sc_ret(regs) = (uint32_t)ksocket_setsockopt(sid, level, optname, &kval, optlen); + return; + } + + if (syscall_no == SYSCALL_GETSOCKOPT) { + int sid = sock_fd_get_sid((int)sc_arg0(regs)); + if (sid < 0) { sc_ret(regs) = (uint32_t)-EBADF; return; } + int level = (int)sc_arg1(regs); + int optname = (int)sc_arg2(regs); + int kval = 0; + uint32_t klen = 4; + int r = ksocket_getsockopt(sid, level, optname, &kval, &klen); + if (r == 0 && sc_arg3(regs) && sc_arg4(regs)) { + if (copy_to_user((void*)sc_arg3(regs), &kval, 4) < 0) { + sc_ret(regs) = (uint32_t)-EFAULT; return; + } + (void)copy_to_user((void*)sc_arg4(regs), &klen, 4); + } + sc_ret(regs) = (uint32_t)r; + return; + } + + if (syscall_no == SYSCALL_SHUTDOWN) { + int sid = sock_fd_get_sid((int)sc_arg0(regs)); + if (sid < 0) { sc_ret(regs) = (uint32_t)-EBADF; return; } + sc_ret(regs) = (uint32_t)ksocket_shutdown(sid, (int)sc_arg1(regs)); + return; + } + + if (syscall_no == SYSCALL_GETPEERNAME) { + int sid = sock_fd_get_sid((int)sc_arg0(regs)); + if (sid < 0) { sc_ret(regs) = (uint32_t)-EBADF; return; } + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + int r = ksocket_getpeername(sid, &sa); + if (r == 0 && sc_arg1(regs)) { + (void)copy_to_user((void*)sc_arg1(regs), &sa, sizeof(sa)); + } + sc_ret(regs) = (uint32_t)r; + return; + } + + if (syscall_no == SYSCALL_GETSOCKNAME) { + int sid = sock_fd_get_sid((int)sc_arg0(regs)); + if (sid < 0) { sc_ret(regs) = (uint32_t)-EBADF; return; } + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + int r = ksocket_getsockname(sid, &sa); + if (r == 0 && sc_arg1(regs)) { + (void)copy_to_user((void*)sc_arg1(regs), &sa, sizeof(sa)); + } + sc_ret(regs) = (uint32_t)r; + return; + } + sc_ret(regs) = (uint32_t)-ENOSYS; } diff --git a/user/ulibc/include/fnmatch.h b/user/ulibc/include/fnmatch.h new file mode 100644 index 0000000..07073d5 --- /dev/null +++ b/user/ulibc/include/fnmatch.h @@ -0,0 +1,11 @@ +#ifndef ULIBC_FNMATCH_H +#define ULIBC_FNMATCH_H + +#define FNM_NOMATCH 1 +#define FNM_PATHNAME (1 << 0) +#define FNM_NOESCAPE (1 << 1) +#define FNM_PERIOD (1 << 2) + +int fnmatch(const char* pattern, const char* string, int flags); + +#endif diff --git a/user/ulibc/include/getopt.h b/user/ulibc/include/getopt.h new file mode 100644 index 0000000..68acbba --- /dev/null +++ b/user/ulibc/include/getopt.h @@ -0,0 +1,25 @@ +#ifndef ULIBC_GETOPT_H +#define ULIBC_GETOPT_H + +extern char* optarg; +extern int optind; +extern int opterr; +extern int optopt; + +int getopt(int argc, char* const argv[], const char* optstring); + +struct option { + const char* name; + int has_arg; + int* flag; + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int getopt_long(int argc, char* const argv[], const char* optstring, + const struct option* longopts, int* longindex); + +#endif diff --git a/user/ulibc/include/grp.h b/user/ulibc/include/grp.h new file mode 100644 index 0000000..d4c5e5c --- /dev/null +++ b/user/ulibc/include/grp.h @@ -0,0 +1,19 @@ +#ifndef ULIBC_GRP_H +#define ULIBC_GRP_H + +#include + +struct group { + char* gr_name; + char* gr_passwd; + int gr_gid; + char** gr_mem; +}; + +struct group* getgrnam(const char* name); +struct group* getgrgid(int gid); +void setgrent(void); +void endgrent(void); +struct group* getgrent(void); + +#endif diff --git a/user/ulibc/include/locale.h b/user/ulibc/include/locale.h new file mode 100644 index 0000000..e57f6c4 --- /dev/null +++ b/user/ulibc/include/locale.h @@ -0,0 +1,36 @@ +#ifndef ULIBC_LOCALE_H +#define ULIBC_LOCALE_H + +#define LC_ALL 0 +#define LC_COLLATE 1 +#define LC_CTYPE 2 +#define LC_MONETARY 3 +#define LC_NUMERIC 4 +#define LC_TIME 5 +#define LC_MESSAGES 6 + +struct lconv { + char* decimal_point; + char* thousands_sep; + char* grouping; + char* int_curr_symbol; + char* currency_symbol; + char* mon_decimal_point; + char* mon_thousands_sep; + char* mon_grouping; + char* positive_sign; + char* negative_sign; + char int_frac_digits; + char frac_digits; + char p_cs_precedes; + char p_sep_by_space; + char n_cs_precedes; + char n_sep_by_space; + char p_sign_posn; + char n_sign_posn; +}; + +char* setlocale(int category, const char* locale); +struct lconv* localeconv(void); + +#endif diff --git a/user/ulibc/include/pwd.h b/user/ulibc/include/pwd.h new file mode 100644 index 0000000..1d2c7c5 --- /dev/null +++ b/user/ulibc/include/pwd.h @@ -0,0 +1,22 @@ +#ifndef ULIBC_PWD_H +#define ULIBC_PWD_H + +#include + +struct passwd { + char* pw_name; + char* pw_passwd; + int pw_uid; + int pw_gid; + char* pw_gecos; + char* pw_dir; + char* pw_shell; +}; + +struct passwd* getpwnam(const char* name); +struct passwd* getpwuid(int uid); +void setpwent(void); +void endpwent(void); +struct passwd* getpwent(void); + +#endif diff --git a/user/ulibc/include/setjmp.h b/user/ulibc/include/setjmp.h new file mode 100644 index 0000000..ce1f692 --- /dev/null +++ b/user/ulibc/include/setjmp.h @@ -0,0 +1,18 @@ +#ifndef ULIBC_SETJMP_H +#define ULIBC_SETJMP_H + +/* jmp_buf layout for i386: + * [0] EBX [1] ESI [2] EDI [3] EBP [4] ESP [5] EIP + */ +typedef unsigned long jmp_buf[6]; +typedef unsigned long sigjmp_buf[6 + 1 + 1]; /* +saved_sigmask +sigmask */ + +int setjmp(jmp_buf env); +void longjmp(jmp_buf env, int val) __attribute__((noreturn)); +int sigsetjmp(sigjmp_buf env, int savesigs); +void siglongjmp(sigjmp_buf env, int val) __attribute__((noreturn)); + +int _setjmp(jmp_buf env); +void _longjmp(jmp_buf env, int val) __attribute__((noreturn)); + +#endif diff --git a/user/ulibc/include/stdio.h b/user/ulibc/include/stdio.h index 740e110..3c28807 100644 --- a/user/ulibc/include/stdio.h +++ b/user/ulibc/include/stdio.h @@ -67,5 +67,8 @@ int rename(const char* oldpath, const char* newpath); #define _IONBF 2 /* unbuffered */ int setvbuf(FILE* fp, char* buf, int mode, size_t size); void setbuf(FILE* fp, char* buf); +void perror(const char* s); +int fileno(FILE* fp); +FILE* fdopen(int fd, const char* mode); #endif diff --git a/user/ulibc/include/stdlib.h b/user/ulibc/include/stdlib.h index 1d662cb..d25ff1c 100644 --- a/user/ulibc/include/stdlib.h +++ b/user/ulibc/include/stdlib.h @@ -11,10 +11,21 @@ void* realloc(void* ptr, size_t size); int atoi(const char* s); double atof(const char* s); long strtol(const char* nptr, char** endptr, int base); +unsigned long strtoul(const char* nptr, char** endptr, int base); +long long strtoll(const char* nptr, char** endptr, int base); +unsigned long long strtoull(const char* nptr, char** endptr, int base); char* realpath(const char* path, char* resolved); char* getenv(const char* name); int abs(int x); long labs(long x); +int setenv(const char* name, const char* value, int overwrite); +int unsetenv(const char* name); +int putenv(char* string); +int atexit(void (*func)(void)); +void abort(void) __attribute__((noreturn)); +int rand(void); +void srand(unsigned int seed); +#define RAND_MAX 0x7FFF void qsort(void* base, size_t nmemb, size_t size, int (*compar)(const void*, const void*)); diff --git a/user/ulibc/include/string.h b/user/ulibc/include/string.h index 917e25e..2102311 100644 --- a/user/ulibc/include/string.h +++ b/user/ulibc/include/string.h @@ -23,5 +23,11 @@ int strncasecmp(const char* a, const char* b, size_t n); char* strstr(const char* haystack, const char* needle); void* memchr(const void* s, int c, size_t n); char* strtok(char* str, const char* delim); +char* strtok_r(char* str, const char* delim, char** saveptr); +char* strerror(int errnum); +size_t strnlen(const char* s, size_t maxlen); +size_t strspn(const char* s, const char* accept); +size_t strcspn(const char* s, const char* reject); +char* strpbrk(const char* s, const char* accept); #endif diff --git a/user/ulibc/include/sys/resource.h b/user/ulibc/include/sys/resource.h new file mode 100644 index 0000000..071211b --- /dev/null +++ b/user/ulibc/include/sys/resource.h @@ -0,0 +1,26 @@ +#ifndef ULIBC_SYS_RESOURCE_H +#define ULIBC_SYS_RESOURCE_H + +#include + +#define RLIMIT_CPU 0 +#define RLIMIT_FSIZE 1 +#define RLIMIT_DATA 2 +#define RLIMIT_STACK 3 +#define RLIMIT_CORE 4 +#define RLIMIT_NOFILE 5 +#define RLIMIT_AS 6 +#define RLIMIT_NPROC 7 +#define RLIM_INFINITY 0xFFFFFFFFU + +typedef uint32_t rlim_t; + +struct rlimit { + rlim_t rlim_cur; + rlim_t rlim_max; +}; + +int getrlimit(int resource, struct rlimit *rlim); +int setrlimit(int resource, const struct rlimit *rlim); + +#endif diff --git a/user/ulibc/include/sys/select.h b/user/ulibc/include/sys/select.h new file mode 100644 index 0000000..f6b64ad --- /dev/null +++ b/user/ulibc/include/sys/select.h @@ -0,0 +1,21 @@ +#ifndef ULIBC_SYS_SELECT_H +#define ULIBC_SYS_SELECT_H + +#include +#include "sys/time.h" + +#define FD_SETSIZE 64 + +typedef struct { + uint32_t fds_bits[FD_SETSIZE / 32]; +} fd_set; + +#define FD_ZERO(set) do { for (int _i = 0; _i < (int)(FD_SETSIZE/32); _i++) (set)->fds_bits[_i] = 0; } while(0) +#define FD_SET(fd, set) ((set)->fds_bits[(fd) / 32] |= (1U << ((fd) % 32))) +#define FD_CLR(fd, set) ((set)->fds_bits[(fd) / 32] &= ~(1U << ((fd) % 32))) +#define FD_ISSET(fd, set) ((set)->fds_bits[(fd) / 32] & (1U << ((fd) % 32))) + +int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, + struct timeval* timeout); + +#endif diff --git a/user/ulibc/include/syscall.h b/user/ulibc/include/syscall.h index 545cf60..6bcaaea 100644 --- a/user/ulibc/include/syscall.h +++ b/user/ulibc/include/syscall.h @@ -83,6 +83,13 @@ enum { SYS_MOUNT = 126, SYS_GETTIMEOFDAY = 127, SYS_MPROTECT = 128, + SYS_GETRLIMIT = 129, + SYS_SETRLIMIT = 130, + SYS_SETSOCKOPT = 131, + SYS_GETSOCKOPT = 132, + SYS_SHUTDOWN = 133, + SYS_GETPEERNAME = 134, + SYS_GETSOCKNAME = 135, }; /* Raw syscall wrappers — up to 5 args via INT 0x80 */ diff --git a/user/ulibc/include/unistd.h b/user/ulibc/include/unistd.h index 085b7be..9684510 100644 --- a/user/ulibc/include/unistd.h +++ b/user/ulibc/include/unistd.h @@ -66,6 +66,14 @@ int symlink(const char* target, const char* linkpath); int readlink(const char* path, char* buf, size_t bufsiz); int kill(int pid, int sig); int rename(const char* oldpath, const char* newpath); +unsigned int sleep(unsigned int seconds); +int usleep(unsigned int usec); +int execvp(const char* file, char* const argv[]); +int execlp(const char* file, const char* arg, ...); +int execl(const char* path, const char* arg, ...); +int getopt(int argc, char* const argv[], const char* optstring); +extern char* optarg; +extern int optind, opterr, optopt; void _exit(int status) __attribute__((noreturn)); diff --git a/user/ulibc/src/abort_atexit.c b/user/ulibc/src/abort_atexit.c new file mode 100644 index 0000000..7564bf6 --- /dev/null +++ b/user/ulibc/src/abort_atexit.c @@ -0,0 +1,25 @@ +#include "stdlib.h" +#include "signal.h" +#include "unistd.h" + +static void (*_atexit_funcs[32])(void); +static int _atexit_count = 0; + +int atexit(void (*func)(void)) { + if (_atexit_count >= 32) return -1; + _atexit_funcs[_atexit_count++] = func; + return 0; +} + +void exit(int status) { + /* Call atexit handlers in reverse order */ + for (int i = _atexit_count - 1; i >= 0; i--) { + if (_atexit_funcs[i]) _atexit_funcs[i](); + } + _exit(status); +} + +void abort(void) { + raise(SIGABRT); + _exit(127); +} diff --git a/user/ulibc/src/environ.c b/user/ulibc/src/environ.c new file mode 100644 index 0000000..576f24d --- /dev/null +++ b/user/ulibc/src/environ.c @@ -0,0 +1,111 @@ +#include "stdlib.h" +#include "string.h" +#include "errno.h" +#include + +extern char** __environ; + +static char* _env_storage[128]; +static int _env_owned = 0; + +static void _ensure_own_environ(void) { + if (_env_owned) return; + int count = 0; + if (__environ) { + for (; __environ[count]; count++) { + if (count >= 126) break; + _env_storage[count] = __environ[count]; + } + } + _env_storage[count] = (char*)0; + __environ = _env_storage; + _env_owned = 1; +} + +int setenv(const char* name, const char* value, int overwrite) { + if (!name || !*name || strchr(name, '=')) { + errno = EINVAL; + return -1; + } + + _ensure_own_environ(); + + size_t nlen = strlen(name); + /* Check if already exists */ + for (int i = 0; __environ[i]; i++) { + if (strncmp(__environ[i], name, nlen) == 0 && __environ[i][nlen] == '=') { + if (!overwrite) return 0; + /* Replace in-place */ + size_t vlen = strlen(value); + char* buf = malloc(nlen + 1 + vlen + 1); + if (!buf) { errno = ENOMEM; return -1; } + memcpy(buf, name, nlen); + buf[nlen] = '='; + memcpy(buf + nlen + 1, value, vlen + 1); + __environ[i] = buf; + return 0; + } + } + + /* Count entries */ + int count = 0; + while (__environ[count]) count++; + if (count >= 126) { errno = ENOMEM; return -1; } + + size_t vlen = strlen(value); + char* buf = malloc(nlen + 1 + vlen + 1); + if (!buf) { errno = ENOMEM; return -1; } + memcpy(buf, name, nlen); + buf[nlen] = '='; + memcpy(buf + nlen + 1, value, vlen + 1); + + __environ[count] = buf; + __environ[count + 1] = (char*)0; + return 0; +} + +int unsetenv(const char* name) { + if (!name || !*name || strchr(name, '=')) { + errno = EINVAL; + return -1; + } + + _ensure_own_environ(); + + size_t nlen = strlen(name); + for (int i = 0; __environ[i]; i++) { + if (strncmp(__environ[i], name, nlen) == 0 && __environ[i][nlen] == '=') { + /* Shift remaining entries down */ + for (int j = i; __environ[j]; j++) + __environ[j] = __environ[j + 1]; + return 0; + } + } + return 0; +} + +int putenv(char* string) { + if (!string) { errno = EINVAL; return -1; } + + _ensure_own_environ(); + + char* eq = strchr(string, '='); + if (!eq) return unsetenv(string); + + size_t nlen = (size_t)(eq - string); + + /* Replace existing or append */ + for (int i = 0; __environ[i]; i++) { + if (strncmp(__environ[i], string, nlen + 1) == 0) { + __environ[i] = string; + return 0; + } + } + + int count = 0; + while (__environ[count]) count++; + if (count >= 126) { errno = ENOMEM; return -1; } + __environ[count] = string; + __environ[count + 1] = (char*)0; + return 0; +} diff --git a/user/ulibc/src/execvp.c b/user/ulibc/src/execvp.c new file mode 100644 index 0000000..5136a6a --- /dev/null +++ b/user/ulibc/src/execvp.c @@ -0,0 +1,74 @@ +#include "unistd.h" +#include "string.h" +#include "stdlib.h" +#include "errno.h" +#include + +extern char** __environ; + +int execvp(const char* file, char* const argv[]) { + if (!file || !*file) { errno = ENOENT; return -1; } + + /* If file contains '/', use it directly */ + if (strchr(file, '/')) + return execve(file, argv, __environ); + + /* Search PATH */ + const char* path = getenv("PATH"); + if (!path) path = "/bin:/usr/bin"; + + char buf[256]; + size_t flen = strlen(file); + + while (*path) { + const char* sep = path; + while (*sep && *sep != ':') sep++; + size_t dlen = (size_t)(sep - path); + if (dlen == 0) { dlen = 1; path = "."; } + + if (dlen + 1 + flen + 1 > sizeof(buf)) { + path = *sep ? sep + 1 : sep; + continue; + } + + memcpy(buf, path, dlen); + buf[dlen] = '/'; + memcpy(buf + dlen + 1, file, flen + 1); + + execve(buf, argv, __environ); + /* If ENOENT, try next; otherwise fail */ + if (errno != ENOENT) return -1; + + path = *sep ? sep + 1 : sep; + } + + errno = ENOENT; + return -1; +} + +int execlp(const char* file, const char* arg, ...) { + /* Count args by walking the va_list manually via stack pointers. + * On i386, args are pushed right-to-left on the stack. + * We'll use a simple approach: max 32 args. */ + const char* args[33]; + const char** p = &arg; + int i = 0; + while (*p && i < 32) { + args[i++] = *p; + p++; + } + args[i] = (const char*)0; + return execvp(file, (char* const*)args); +} + +int execl(const char* path, const char* arg, ...) { + const char* args[33]; + const char** p = &arg; + int i = 0; + while (*p && i < 32) { + args[i++] = *p; + p++; + } + args[i] = (const char*)0; + return execve(path, (char* const*)args, __environ); +} diff --git a/user/ulibc/src/fnmatch.c b/user/ulibc/src/fnmatch.c new file mode 100644 index 0000000..321d4f5 --- /dev/null +++ b/user/ulibc/src/fnmatch.c @@ -0,0 +1,55 @@ +#include "fnmatch.h" +#include + +int fnmatch(const char* pattern, const char* string, int flags) { + const char* p = pattern; + const char* s = string; + + while (*p) { + if (*p == '*') { + p++; + /* Skip consecutive stars */ + while (*p == '*') p++; + if (!*p) return 0; /* trailing * matches everything */ + /* Try matching rest of pattern at each position */ + while (*s) { + if (fnmatch(p, s, flags) == 0) return 0; + if ((flags & FNM_PATHNAME) && *s == '/') break; + s++; + } + return FNM_NOMATCH; + } else if (*p == '?') { + if (!*s) return FNM_NOMATCH; + if ((flags & FNM_PATHNAME) && *s == '/') return FNM_NOMATCH; + if ((flags & FNM_PERIOD) && *s == '.' && (s == string || ((flags & FNM_PATHNAME) && s[-1] == '/'))) + return FNM_NOMATCH; + p++; s++; + } else if (*p == '[') { + if (!*s) return FNM_NOMATCH; + p++; + int negate = 0; + if (*p == '!' || *p == '^') { negate = 1; p++; } + int match = 0; + while (*p && *p != ']') { + char lo = *p++; + if (*p == '-' && p[1] && p[1] != ']') { + p++; + char hi = *p++; + if (*s >= lo && *s <= hi) match = 1; + } else { + if (*s == lo) match = 1; + } + } + if (*p == ']') p++; + if (negate) match = !match; + if (!match) return FNM_NOMATCH; + s++; + } else { + /* Literal character */ + if (*p != *s) return FNM_NOMATCH; + p++; s++; + } + } + + return (*s == '\0') ? 0 : FNM_NOMATCH; +} diff --git a/user/ulibc/src/getopt.c b/user/ulibc/src/getopt.c new file mode 100644 index 0000000..7563e58 --- /dev/null +++ b/user/ulibc/src/getopt.c @@ -0,0 +1,105 @@ +#include "getopt.h" +#include "string.h" +#include + +char* optarg = (char*)0; +int optind = 1; +int opterr = 1; +int optopt = '?'; + +static int _optpos = 0; /* position within current argv element */ + +int getopt(int argc, char* const argv[], const char* optstring) { + if (optind >= argc || !argv[optind]) return -1; + + const char* arg = argv[optind]; + + /* Skip non-options */ + if (arg[0] != '-' || arg[1] == '\0') return -1; + if (arg[1] == '-' && arg[2] == '\0') { optind++; return -1; } /* "--" */ + + int pos = _optpos ? _optpos : 1; + int c = arg[pos]; + _optpos = 0; + + const char* match = strchr(optstring, c); + if (!match) { + optopt = c; + if (arg[pos + 1]) _optpos = pos + 1; + else optind++; + return '?'; + } + + if (match[1] == ':') { + /* Option requires argument */ + if (arg[pos + 1]) { + optarg = (char*)&arg[pos + 1]; + optind++; + } else if (optind + 1 < argc) { + optarg = argv[optind + 1]; + optind += 2; + } else { + optopt = c; + optind++; + return (optstring[0] == ':') ? ':' : '?'; + } + } else { + optarg = (char*)0; + if (arg[pos + 1]) { + _optpos = pos + 1; + } else { + optind++; + } + } + + return c; +} + +int getopt_long(int argc, char* const argv[], const char* optstring, + const struct option* longopts, int* longindex) { + if (optind >= argc || !argv[optind]) return -1; + + const char* arg = argv[optind]; + + /* Handle long options: --name or --name=value */ + if (arg[0] == '-' && arg[1] == '-' && arg[2] != '\0') { + const char* name = arg + 2; + const char* eq = strchr(name, '='); + size_t nlen = eq ? (size_t)(eq - name) : strlen(name); + + for (int i = 0; longopts[i].name; i++) { + if (strncmp(longopts[i].name, name, nlen) == 0 && + longopts[i].name[nlen] == '\0') { + if (longindex) *longindex = i; + + if (longopts[i].has_arg == required_argument || longopts[i].has_arg == optional_argument) { + if (eq) { + optarg = (char*)(eq + 1); + } else if (longopts[i].has_arg == required_argument && optind + 1 < argc) { + optarg = argv[optind + 1]; + optind++; + } else if (longopts[i].has_arg == required_argument) { + optind++; + return '?'; + } else { + optarg = (char*)0; + } + } else { + optarg = (char*)0; + } + + optind++; + if (longopts[i].flag) { + *longopts[i].flag = longopts[i].val; + return 0; + } + return longopts[i].val; + } + } + optind++; + return '?'; + } + + /* Fall back to short option parsing */ + return getopt(argc, argv, optstring); +} diff --git a/user/ulibc/src/locale.c b/user/ulibc/src/locale.c new file mode 100644 index 0000000..6be1fc6 --- /dev/null +++ b/user/ulibc/src/locale.c @@ -0,0 +1,33 @@ +#include "locale.h" +#include + +static struct lconv _c_lconv = { + .decimal_point = ".", + .thousands_sep = "", + .grouping = "", + .int_curr_symbol = "", + .currency_symbol = "", + .mon_decimal_point = "", + .mon_thousands_sep = "", + .mon_grouping = "", + .positive_sign = "", + .negative_sign = "", + .int_frac_digits = 127, + .frac_digits = 127, + .p_cs_precedes = 127, + .p_sep_by_space = 127, + .n_cs_precedes = 127, + .n_sep_by_space = 127, + .p_sign_posn = 127, + .n_sign_posn = 127, +}; + +char* setlocale(int category, const char* locale) { + (void)category; + (void)locale; + return "C"; +} + +struct lconv* localeconv(void) { + return &_c_lconv; +} diff --git a/user/ulibc/src/pwd_grp.c b/user/ulibc/src/pwd_grp.c new file mode 100644 index 0000000..79f668d --- /dev/null +++ b/user/ulibc/src/pwd_grp.c @@ -0,0 +1,74 @@ +#include "pwd.h" +#include "grp.h" +#include + +/* Minimal /etc/passwd and /etc/group stubs. + * AdrOS has a single-user model (uid=0 root). */ + +static struct passwd _root = { + .pw_name = "root", + .pw_passwd = "x", + .pw_uid = 0, + .pw_gid = 0, + .pw_gecos = "root", + .pw_dir = "/", + .pw_shell = "/bin/sh", +}; + +static int _pw_idx = 0; + +struct passwd* getpwnam(const char* name) { + if (!name) return (struct passwd*)0; + /* strcmp inline to avoid header dependency issues */ + const char* a = name; + const char* b = "root"; + while (*a && *a == *b) { a++; b++; } + if (*a == *b) return &_root; + return (struct passwd*)0; +} + +struct passwd* getpwuid(int uid) { + if (uid == 0) return &_root; + return (struct passwd*)0; +} + +void setpwent(void) { _pw_idx = 0; } +void endpwent(void) { _pw_idx = 0; } + +struct passwd* getpwent(void) { + if (_pw_idx == 0) { _pw_idx++; return &_root; } + return (struct passwd*)0; +} + +static char* _root_members[] = { "root", (char*)0 }; + +static struct group _root_grp = { + .gr_name = "root", + .gr_passwd = "x", + .gr_gid = 0, + .gr_mem = _root_members, +}; + +static int _gr_idx = 0; + +struct group* getgrnam(const char* name) { + if (!name) return (struct group*)0; + const char* a = name; + const char* b = "root"; + while (*a && *a == *b) { a++; b++; } + if (*a == *b) return &_root_grp; + return (struct group*)0; +} + +struct group* getgrgid(int gid) { + if (gid == 0) return &_root_grp; + return (struct group*)0; +} + +void setgrent(void) { _gr_idx = 0; } +void endgrent(void) { _gr_idx = 0; } + +struct group* getgrent(void) { + if (_gr_idx == 0) { _gr_idx++; return &_root_grp; } + return (struct group*)0; +} diff --git a/user/ulibc/src/rand.c b/user/ulibc/src/rand.c new file mode 100644 index 0000000..9457888 --- /dev/null +++ b/user/ulibc/src/rand.c @@ -0,0 +1,14 @@ +#include "stdlib.h" + +static unsigned long _rand_seed = 1; + +void srand(unsigned int seed) { + _rand_seed = seed; +} + +int rand(void) { + _rand_seed = _rand_seed * 1103515245UL + 12345UL; + return (int)((_rand_seed >> 16) & 0x7FFF); +} + +#define RAND_MAX 0x7FFF diff --git a/user/ulibc/src/resource.c b/user/ulibc/src/resource.c new file mode 100644 index 0000000..0236b00 --- /dev/null +++ b/user/ulibc/src/resource.c @@ -0,0 +1,11 @@ +#include "sys/resource.h" +#include "syscall.h" +#include "errno.h" + +int getrlimit(int resource, struct rlimit *rlim) { + return __syscall_ret(_syscall2(SYS_GETRLIMIT, resource, (int)rlim)); +} + +int setrlimit(int resource, const struct rlimit *rlim) { + return __syscall_ret(_syscall2(SYS_SETRLIMIT, resource, (int)rlim)); +} diff --git a/user/ulibc/src/setjmp.S b/user/ulibc/src/setjmp.S new file mode 100644 index 0000000..66d20cd --- /dev/null +++ b/user/ulibc/src/setjmp.S @@ -0,0 +1,80 @@ +/* + * setjmp / longjmp for i386 + * + * jmp_buf layout: [0]=EBX [1]=ESI [2]=EDI [3]=EBP [4]=ESP [5]=EIP + */ +.text + +.global setjmp +.global _setjmp +.global longjmp +.global _longjmp +.global sigsetjmp +.global siglongjmp + +/* int setjmp(jmp_buf env) */ +setjmp: +_setjmp: + mov 4(%esp), %eax /* eax = env */ + mov %ebx, 0(%eax) + mov %esi, 4(%eax) + mov %edi, 8(%eax) + mov %ebp, 12(%eax) + lea 4(%esp), %ecx /* caller's ESP (before call pushed retaddr) */ + mov %ecx, 16(%eax) + mov (%esp), %ecx /* return address = caller's EIP */ + mov %ecx, 20(%eax) + xor %eax, %eax /* return 0 */ + ret + +/* void longjmp(jmp_buf env, int val) */ +longjmp: +_longjmp: + mov 4(%esp), %edx /* edx = env */ + mov 8(%esp), %eax /* eax = val */ + test %eax, %eax + jnz 1f + inc %eax /* if val==0, return 1 */ +1: + mov 0(%edx), %ebx + mov 4(%edx), %esi + mov 8(%edx), %edi + mov 12(%edx), %ebp + mov 16(%edx), %esp + jmp *20(%edx) /* jump to saved EIP */ + +/* int sigsetjmp(sigjmp_buf env, int savesigs) — minimal, no signal mask save */ +sigsetjmp: + mov 4(%esp), %eax /* eax = env */ + mov %ebx, 0(%eax) + mov %esi, 4(%eax) + mov %edi, 8(%eax) + mov %ebp, 12(%eax) + lea 4(%esp), %ecx + mov %ecx, 16(%eax) + mov (%esp), %ecx + mov %ecx, 20(%eax) + /* env[6] = savesigs flag */ + mov 8(%esp), %ecx + mov %ecx, 24(%eax) + /* env[7] = 0 (signal mask placeholder) */ + movl $0, 28(%eax) + xor %eax, %eax + ret + +/* void siglongjmp(sigjmp_buf env, int val) */ +siglongjmp: + mov 4(%esp), %edx + mov 8(%esp), %eax + test %eax, %eax + jnz 1f + inc %eax +1: + mov 0(%edx), %ebx + mov 4(%edx), %esi + mov 8(%edx), %edi + mov 12(%edx), %ebp + mov 16(%edx), %esp + jmp *20(%edx) + +.section .note.GNU-stack,"",@progbits diff --git a/user/ulibc/src/signal_wrap.c b/user/ulibc/src/signal_wrap.c new file mode 100644 index 0000000..1ce74d5 --- /dev/null +++ b/user/ulibc/src/signal_wrap.c @@ -0,0 +1,13 @@ +#include "signal.h" + +typedef void (*sighandler_t)(int); + +sighandler_t signal(int signum, sighandler_t handler) { + struct sigaction sa, old; + sa.sa_handler = (uintptr_t)handler; + sa.sa_mask = 0; + sa.sa_flags = SA_RESTART; + if (sigaction(signum, &sa, &old) < 0) + return (sighandler_t)-1; /* SIG_ERR */ + return (sighandler_t)(uintptr_t)old.sa_handler; +} diff --git a/user/ulibc/src/sleep.c b/user/ulibc/src/sleep.c new file mode 100644 index 0000000..19b5da4 --- /dev/null +++ b/user/ulibc/src/sleep.c @@ -0,0 +1,19 @@ +#include "unistd.h" +#include "time.h" +#include "errno.h" + +unsigned int sleep(unsigned int seconds) { + struct timespec req = { .tv_sec = seconds, .tv_nsec = 0 }; + struct timespec rem = { .tv_sec = 0, .tv_nsec = 0 }; + if (nanosleep(&req, &rem) < 0) { + return (unsigned int)rem.tv_sec; + } + return 0; +} + +int usleep(unsigned int usec) { + struct timespec req; + req.tv_sec = usec / 1000000; + req.tv_nsec = (usec % 1000000) * 1000; + return nanosleep(&req, (void*)0); +} diff --git a/user/ulibc/src/stdlib.c b/user/ulibc/src/stdlib.c index 998dc77..530d2fe 100644 --- a/user/ulibc/src/stdlib.c +++ b/user/ulibc/src/stdlib.c @@ -6,39 +6,119 @@ char** __environ = 0; /* - * Minimal bump allocator using brk() syscall. - * No free() support yet — memory is only reclaimed on process exit. - * A proper free-list allocator can be added later. + * Free-list allocator using brk() syscall. + * + * Each allocated block has a header: [size | in_use_flag] + * Free blocks are linked in an explicit free list. + * Coalescing is done on free() (merge with adjacent free blocks). + * + * Layout: [header 8 bytes] [user data ...] + * header.size = total block size including header (aligned to 8) + * header.next = pointer to next free block (only valid when free) */ + +#define ALLOC_ALIGN 8 +#define ALLOC_HDR_SIZE 8 /* must == sizeof(struct block_hdr) */ +#define ALLOC_USED_BIT 1U + +struct block_hdr { + uint32_t size; /* total size including header; bit 0 = used flag */ + uint32_t next_free; /* pointer to next free block (as uintptr_t), 0 = end */ +}; + +static struct block_hdr* free_list = 0; static void* heap_base = 0; static void* heap_end = 0; -void* malloc(size_t size) { - if (size == 0) return (void*)0; - - /* Align to 8 bytes */ - size = (size + 7) & ~(size_t)7; +static inline uint32_t blk_size(struct block_hdr* b) { return b->size & ~ALLOC_USED_BIT; } +static inline int blk_used(struct block_hdr* b) { return (int)(b->size & ALLOC_USED_BIT); } +static void* sbrk_grow(size_t inc) { if (!heap_base) { heap_base = brk(0); heap_end = heap_base; } - void* old_end = heap_end; - void* new_end = (void*)((char*)heap_end + size); + void* new_end = (void*)((char*)heap_end + inc); void* result = brk(new_end); + if ((uintptr_t)result < (uintptr_t)new_end) + return (void*)0; + heap_end = new_end; + return old_end; +} + +void* malloc(size_t size) { + if (size == 0) return (void*)0; - if ((uintptr_t)result < (uintptr_t)new_end) { - return (void*)0; /* OOM */ + /* Align to 8 bytes, add header */ + size = (size + ALLOC_ALIGN - 1) & ~(size_t)(ALLOC_ALIGN - 1); + uint32_t total = (uint32_t)size + ALLOC_HDR_SIZE; + + /* First-fit search in free list */ + struct block_hdr** prev = &free_list; + struct block_hdr* cur = free_list; + while (cur) { + uint32_t bsz = blk_size(cur); + if (bsz >= total) { + /* Split if remainder is large enough for another block */ + if (bsz >= total + ALLOC_HDR_SIZE + ALLOC_ALIGN) { + struct block_hdr* split = (struct block_hdr*)((char*)cur + total); + split->size = bsz - total; + split->next_free = cur->next_free; + *prev = split; + cur->size = total | ALLOC_USED_BIT; + } else { + /* Use entire block */ + *prev = (struct block_hdr*)(uintptr_t)cur->next_free; + cur->size = bsz | ALLOC_USED_BIT; + } + return (void*)((char*)cur + ALLOC_HDR_SIZE); + } + prev = (struct block_hdr**)&cur->next_free; + cur = (struct block_hdr*)(uintptr_t)cur->next_free; } - heap_end = new_end; - return old_end; + /* No free block found — grow heap */ + void* p = sbrk_grow(total); + if (!p) return (void*)0; + struct block_hdr* b = (struct block_hdr*)p; + b->size = total | ALLOC_USED_BIT; + b->next_free = 0; + return (void*)((char*)b + ALLOC_HDR_SIZE); } void free(void* ptr) { - /* Bump allocator: no-op for now */ - (void)ptr; + if (!ptr) return; + struct block_hdr* b = (struct block_hdr*)((char*)ptr - ALLOC_HDR_SIZE); + b->size &= ~ALLOC_USED_BIT; /* mark free */ + + /* Insert into free list (address-ordered for coalescing) */ + struct block_hdr** prev = &free_list; + struct block_hdr* cur = free_list; + while (cur && (uintptr_t)cur < (uintptr_t)b) { + prev = (struct block_hdr**)&cur->next_free; + cur = (struct block_hdr*)(uintptr_t)cur->next_free; + } + + b->next_free = (uint32_t)(uintptr_t)cur; + *prev = b; + + /* Coalesce with next block if adjacent */ + if (cur && (char*)b + blk_size(b) == (char*)cur) { + b->size += blk_size(cur); + b->next_free = cur->next_free; + } + + /* Coalesce with previous block if adjacent */ + struct block_hdr* p_prev = free_list; + if (p_prev != b) { + while (p_prev && (struct block_hdr*)(uintptr_t)p_prev->next_free != b) + p_prev = (struct block_hdr*)(uintptr_t)p_prev->next_free; + if (p_prev && (char*)p_prev + blk_size(p_prev) == (char*)b) { + p_prev->size += blk_size(b); + p_prev->next_free = b->next_free; + } + } } void* calloc(size_t nmemb, size_t size) { @@ -52,11 +132,17 @@ void* calloc(size_t nmemb, size_t size) { void* realloc(void* ptr, size_t size) { if (!ptr) return malloc(size); if (size == 0) { free(ptr); return (void*)0; } - /* Bump allocator: just allocate new and copy. - * We don't know the old size, so copy 'size' bytes - * (caller must ensure old block >= size). */ + + struct block_hdr* b = (struct block_hdr*)((char*)ptr - ALLOC_HDR_SIZE); + uint32_t old_usable = blk_size(b) - ALLOC_HDR_SIZE; + + if (old_usable >= size) return ptr; /* already large enough */ + void* new_ptr = malloc(size); - if (new_ptr) memcpy(new_ptr, ptr, size); + if (new_ptr) { + memcpy(new_ptr, ptr, old_usable); + free(ptr); + } return new_ptr; } @@ -205,7 +291,3 @@ int system(const char* cmd) { (void)cmd; return -1; } - -void exit(int status) { - _exit(status); -} diff --git a/user/ulibc/src/strerror.c b/user/ulibc/src/strerror.c new file mode 100644 index 0000000..f9511a6 --- /dev/null +++ b/user/ulibc/src/strerror.c @@ -0,0 +1,67 @@ +#include "string.h" +#include "stdio.h" +#include "errno.h" + +static const char* _err_table[] = { + [0] = "Success", + [EPERM] = "Operation not permitted", + [ENOENT] = "No such file or directory", + [ESRCH] = "No such process", + [EINTR] = "Interrupted system call", + [EIO] = "Input/output error", + [E2BIG] = "Argument list too long", + [EBADF] = "Bad file descriptor", + [ECHILD] = "No child processes", + [EAGAIN] = "Resource temporarily unavailable", + [ENOMEM] = "Cannot allocate memory", + [EACCES] = "Permission denied", + [EFAULT] = "Bad address", + [EBUSY] = "Device or resource busy", + [EEXIST] = "File exists", + [ENODEV] = "No such device", + [ENOTDIR] = "Not a directory", + [EISDIR] = "Is a directory", + [EINVAL] = "Invalid argument", + [EMFILE] = "Too many open files", + [ENOTTY] = "Inappropriate ioctl for device", + [ENOSPC] = "No space left on device", + [ESPIPE] = "Illegal seek", + [EROFS] = "Read-only file system", + [EPIPE] = "Broken pipe", + [ERANGE] = "Numerical result out of range", + [ENAMETOOLONG] = "File name too long", + [ENOLCK] = "No locks available", + [ENOSYS] = "Function not implemented", + [ENOTEMPTY] = "Directory not empty", +}; + +#define ERR_TABLE_SIZE (int)(sizeof(_err_table) / sizeof(_err_table[0])) + +static char _unknown_buf[32]; + +char* strerror(int errnum) { + if (errnum >= 0 && errnum < ERR_TABLE_SIZE && _err_table[errnum]) + return (char*)_err_table[errnum]; + /* Build "Unknown error NNN" */ + char* p = _unknown_buf; + const char* prefix = "Unknown error "; + while (*prefix) *p++ = *prefix++; + /* itoa inline */ + int n = errnum < 0 ? -errnum : errnum; + char tmp[12]; + int i = 0; + if (errnum < 0) *p++ = '-'; + do { tmp[i++] = '0' + (n % 10); n /= 10; } while (n > 0); + while (i > 0) *p++ = tmp[--i]; + *p = '\0'; + return _unknown_buf; +} + +void perror(const char* s) { + if (s && *s) { + fputs(s, stderr); + fputs(": ", stderr); + } + fputs(strerror(errno), stderr); + fputc('\n', stderr); +} diff --git a/user/ulibc/src/string.c b/user/ulibc/src/string.c index 505fd3b..8692019 100644 --- a/user/ulibc/src/string.c +++ b/user/ulibc/src/string.c @@ -150,13 +150,43 @@ void* memchr(const void* s, int c, size_t n) { static char* strtok_state = (void*)0; char* strtok(char* str, const char* delim) { - if (str) strtok_state = str; - if (!strtok_state) return (void*)0; + return strtok_r(str, delim, &strtok_state); +} + +char* strtok_r(char* str, const char* delim, char** saveptr) { + if (str) *saveptr = str; + if (!*saveptr) return (void*)0; /* skip leading delimiters */ - while (*strtok_state && strchr(delim, *strtok_state)) strtok_state++; - if (!*strtok_state) return (void*)0; - char* start = strtok_state; - while (*strtok_state && !strchr(delim, *strtok_state)) strtok_state++; - if (*strtok_state) *strtok_state++ = 0; + while (**saveptr && strchr(delim, **saveptr)) (*saveptr)++; + if (!**saveptr) return (void*)0; + char* start = *saveptr; + while (**saveptr && !strchr(delim, **saveptr)) (*saveptr)++; + if (**saveptr) *(*saveptr)++ = 0; return start; } + +size_t strnlen(const char* s, size_t maxlen) { + size_t i = 0; + while (i < maxlen && s[i]) i++; + return i; +} + +size_t strspn(const char* s, const char* accept) { + size_t i = 0; + while (s[i] && strchr(accept, s[i])) i++; + return i; +} + +size_t strcspn(const char* s, const char* reject) { + size_t i = 0; + while (s[i] && !strchr(reject, s[i])) i++; + return i; +} + +char* strpbrk(const char* s, const char* accept) { + while (*s) { + if (strchr(accept, *s)) return (char*)s; + s++; + } + return (void*)0; +} diff --git a/user/ulibc/src/strtoul.c b/user/ulibc/src/strtoul.c new file mode 100644 index 0000000..83addbe --- /dev/null +++ b/user/ulibc/src/strtoul.c @@ -0,0 +1,104 @@ +#include "stdlib.h" +#include "ctype.h" +#include "errno.h" +#include + +unsigned long strtoul(const char* nptr, char** endptr, int base) { + const char* s = nptr; + unsigned long result = 0; + int neg = 0; + + while (isspace(*s)) s++; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') { s++; } + + if (base == 0) { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + + int overflow = 0; + while (*s) { + int digit; + if (*s >= '0' && *s <= '9') digit = *s - '0'; + else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10; + else break; + if (digit >= base) break; + if (result > (0xFFFFFFFFUL - (unsigned long)digit) / (unsigned long)base) + overflow = 1; + result = result * (unsigned long)base + (unsigned long)digit; + s++; + } + + if (endptr) *endptr = (char*)s; + if (overflow) { errno = ERANGE; return 0xFFFFFFFFUL; } + return neg ? (unsigned long)(-(long)result) : result; +} + +long long strtoll(const char* nptr, char** endptr, int base) { + const char* s = nptr; + long long result = 0; + int neg = 0; + + while (isspace(*s)) s++; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') { s++; } + + if (base == 0) { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + + while (*s) { + int digit; + if (*s >= '0' && *s <= '9') digit = *s - '0'; + else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10; + else break; + if (digit >= base) break; + result = result * base + digit; + s++; + } + + if (endptr) *endptr = (char*)s; + return neg ? -result : result; +} + +unsigned long long strtoull(const char* nptr, char** endptr, int base) { + const char* s = nptr; + unsigned long long result = 0; + int neg = 0; + + while (isspace(*s)) s++; + if (*s == '-') { neg = 1; s++; } + else if (*s == '+') { s++; } + + if (base == 0) { + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } + else if (s[0] == '0') { base = 8; s++; } + else { base = 10; } + } else if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + + while (*s) { + int digit; + if (*s >= '0' && *s <= '9') digit = *s - '0'; + else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10; + else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10; + else break; + if (digit >= base) break; + result = result * (unsigned long long)base + (unsigned long long)digit; + s++; + } + + if (endptr) *endptr = (char*)s; + return neg ? (unsigned long long)(-(long long)result) : result; +}