]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: POSIX Phases 2-6 — syscalls, ulibc functions, headers, malloc, Newlib prep
authorTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 00:47:53 +0000 (21:47 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 14 Mar 2026 00:47:53 +0000 (21:47 -0300)
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.

37 files changed:
include/errno.h
include/process.h
include/socket.h
include/syscall.h
src/arch/x86/arch_platform.c
src/kernel/scheduler.c
src/kernel/socket.c
src/kernel/syscall.c
user/ulibc/include/fnmatch.h [new file with mode: 0644]
user/ulibc/include/getopt.h [new file with mode: 0644]
user/ulibc/include/grp.h [new file with mode: 0644]
user/ulibc/include/locale.h [new file with mode: 0644]
user/ulibc/include/pwd.h [new file with mode: 0644]
user/ulibc/include/setjmp.h [new file with mode: 0644]
user/ulibc/include/stdio.h
user/ulibc/include/stdlib.h
user/ulibc/include/string.h
user/ulibc/include/sys/resource.h [new file with mode: 0644]
user/ulibc/include/sys/select.h [new file with mode: 0644]
user/ulibc/include/syscall.h
user/ulibc/include/unistd.h
user/ulibc/src/abort_atexit.c [new file with mode: 0644]
user/ulibc/src/environ.c [new file with mode: 0644]
user/ulibc/src/execvp.c [new file with mode: 0644]
user/ulibc/src/fnmatch.c [new file with mode: 0644]
user/ulibc/src/getopt.c [new file with mode: 0644]
user/ulibc/src/locale.c [new file with mode: 0644]
user/ulibc/src/pwd_grp.c [new file with mode: 0644]
user/ulibc/src/rand.c [new file with mode: 0644]
user/ulibc/src/resource.c [new file with mode: 0644]
user/ulibc/src/setjmp.S [new file with mode: 0644]
user/ulibc/src/signal_wrap.c [new file with mode: 0644]
user/ulibc/src/sleep.c [new file with mode: 0644]
user/ulibc/src/stdlib.c
user/ulibc/src/strerror.c [new file with mode: 0644]
user/ulibc/src/string.c
user/ulibc/src/strtoul.c [new file with mode: 0644]

index f52c2f20e0ddbd14651435812b63dcca15c93599..580d3b301b96ef0b8365263ba92a96219a6387f7 100644 (file)
@@ -40,5 +40,8 @@
 #define EMSGSIZE 90
 #define EROFS 30
 #define EWOULDBLOCK EAGAIN
+#define ENOPROTOOPT 92
+#define EOVERFLOW 75
+#define ELOOP 40
 
 #endif
index 89247c138a499d0bf90cf08311bc180ad7e6b970..c977d7d8bf4b65cfc7fdce6008dcda7cb454328c 100644 (file)
@@ -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;
index ab06e01e5f38c3c10570ae6e96b9e382b2e02084..82d70c2f89fff5a93fd58f3bd617e6d66ff4ec15 100644 (file)
@@ -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);
 
index 1416c4d81064724a53773a7c15ce1a2a87de6121..251b86288417c735e122d638900e66d8825409b2 100644 (file)
@@ -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
index 78e01855a6bd6e6fd1d870a1474f88417db31c26..9d90859958ca086dddf5819da955a4189b474f2e 100644 (file)
@@ -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);
index 9067b76475cfced60ed463361aabb1211f7db577..d03a75506c842c91f4141401b6b6578e1916953e 100644 (file)
@@ -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);
 
index b4d5f18d57cb234bf636afd983abc4ac33725eb0..5145b5b01e55cd231364d88641e0ce544048eb52 100644 (file)
@@ -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];
index 49c2a26187b1a0239c5c2720b487227119717e97..ebd9826c2e566cdc7b89fbf99b507df741214c13 100644 (file)
@@ -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, &current_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 (file)
index 0000000..07073d5
--- /dev/null
@@ -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 (file)
index 0000000..68acbba
--- /dev/null
@@ -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 (file)
index 0000000..d4c5e5c
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef ULIBC_GRP_H
+#define ULIBC_GRP_H
+
+#include <stddef.h>
+
+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 (file)
index 0000000..e57f6c4
--- /dev/null
@@ -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 (file)
index 0000000..1d2c7c5
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef ULIBC_PWD_H
+#define ULIBC_PWD_H
+
+#include <stddef.h>
+
+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 (file)
index 0000000..ce1f692
--- /dev/null
@@ -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
index 740e1101e725b6c4b2a1ddbf2648ebbb20e422e0..3c2880703b693c11483537555a62398668befa6f 100644 (file)
@@ -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
index 1d662cbe1da2a29b6b70fe9254092bf04990725d..d25ff1c47f6548f5a1989cad2af22f369ddb8772 100644 (file)
@@ -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*));
index 917e25e0e9832028e3158c90c8d6ddd1f5d4e8ee..21023116efd5fe52d6b66dd857e380246021a57e 100644 (file)
@@ -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 (file)
index 0000000..071211b
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef ULIBC_SYS_RESOURCE_H
+#define ULIBC_SYS_RESOURCE_H
+
+#include <stdint.h>
+
+#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 (file)
index 0000000..f6b64ad
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef ULIBC_SYS_SELECT_H
+#define ULIBC_SYS_SELECT_H
+
+#include <stdint.h>
+#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
index 545cf608d5370022e4000cd3ea3e30297c607c5f..6bcaaea41816876837b23f428a3d3467107ab06d 100644 (file)
@@ -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 */
index 085b7be5022abf4800e855cdb3308c9073863384..96845101b4de38343e2c124a41523c8d2378aec9 100644 (file)
@@ -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 (file)
index 0000000..7564bf6
--- /dev/null
@@ -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 (file)
index 0000000..576f24d
--- /dev/null
@@ -0,0 +1,111 @@
+#include "stdlib.h"
+#include "string.h"
+#include "errno.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..5136a6a
--- /dev/null
@@ -0,0 +1,74 @@
+#include "unistd.h"
+#include "string.h"
+#include "stdlib.h"
+#include "errno.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..321d4f5
--- /dev/null
@@ -0,0 +1,55 @@
+#include "fnmatch.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..7563e58
--- /dev/null
@@ -0,0 +1,105 @@
+#include "getopt.h"
+#include "string.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..6be1fc6
--- /dev/null
@@ -0,0 +1,33 @@
+#include "locale.h"
+#include <stddef.h>
+
+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 (file)
index 0000000..79f668d
--- /dev/null
@@ -0,0 +1,74 @@
+#include "pwd.h"
+#include "grp.h"
+#include <stddef.h>
+
+/* 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 (file)
index 0000000..9457888
--- /dev/null
@@ -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 (file)
index 0000000..0236b00
--- /dev/null
@@ -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 (file)
index 0000000..66d20cd
--- /dev/null
@@ -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 (file)
index 0000000..1ce74d5
--- /dev/null
@@ -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 (file)
index 0000000..19b5da4
--- /dev/null
@@ -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);
+}
index 998dc77b4c5521ca6e72e9e89afb7d724b857fdb..530d2fe662f4d35a05a482c32ca701f224fc6250 100644 (file)
 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 (file)
index 0000000..f9511a6
--- /dev/null
@@ -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);
+}
index 505fd3b7f4eca3f3ae68fb2b03375e96d325d353..86920194397b38a4025ef89d08e12b4c24cef39f 100644 (file)
@@ -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 (file)
index 0000000..83addbe
--- /dev/null
@@ -0,0 +1,104 @@
+#include "stdlib.h"
+#include "ctype.h"
+#include "errno.h"
+#include <stdint.h>
+
+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;
+}