]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: add ulibc — minimal userspace C library
authorTulio A M Mendes <[email protected]>
Tue, 10 Feb 2026 07:19:46 +0000 (04:19 -0300)
committerTulio A M Mendes <[email protected]>
Fri, 13 Feb 2026 02:19:39 +0000 (23:19 -0300)
Create user/ulibc/ with headers and implementations for:
- crt0.S: _start entry point that calls main() then exit()
- syscall.h: raw INT 0x80 wrappers (_syscall0..5)
- unistd.c/h: POSIX wrappers (read, write, open, close, fork,
  execve, pipe, dup2, brk, getpid, chdir, mkdir, etc)
- string.c/h: memcpy, memset, memmove, strlen, strcmp, strcpy,
  strchr, strcat, etc
- stdlib.c/h: malloc/free (bump allocator via brk), calloc,
  realloc, atoi, exit
- stdio.c/h: printf, puts, putchar, snprintf, vsnprintf with
  format specifiers: %d %i %u %x %X %s %c %p %%
- errno.c/h: errno variable + error codes + __syscall_ret helper

Build system: user/ulibc/Makefile produces libulibc.a static lib.
Main Makefile updated with ULIBC_LIB target.

Future user programs can link against libulibc.a instead of
duplicating syscall wrappers inline.

Passes: make (kernel), ulibc builds clean, QEMU smoke test OK.

15 files changed:
.gitignore
Makefile
user/ulibc/Makefile [new file with mode: 0644]
user/ulibc/include/errno.h [new file with mode: 0644]
user/ulibc/include/stdio.h [new file with mode: 0644]
user/ulibc/include/stdlib.h [new file with mode: 0644]
user/ulibc/include/string.h [new file with mode: 0644]
user/ulibc/include/syscall.h [new file with mode: 0644]
user/ulibc/include/unistd.h [new file with mode: 0644]
user/ulibc/src/crt0.S [new file with mode: 0644]
user/ulibc/src/errno.c [new file with mode: 0644]
user/ulibc/src/stdio.c [new file with mode: 0644]
user/ulibc/src/stdlib.c [new file with mode: 0644]
user/ulibc/src/string.c [new file with mode: 0644]
user/ulibc/src/unistd.c [new file with mode: 0644]

index 6143acd6b4fe1e5a040245836db2a00c75f93ba7..9a387983a9d3702aee3fd714a7c6e89c2135f7b6 100644 (file)
@@ -6,6 +6,7 @@ build/
 *.img
 *.log
 *.elf
+*.a
 
 # ISO staging: keep config, ignore generated kernel binaries
 iso/boot/*.bin
index 582d1aaad4b53bb9248117f73ef5f17fc48f6d62..f7fc27bd9798a77ff79567cb4f48d64e4839c119 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -118,6 +118,12 @@ iso: $(KERNEL_NAME) $(INITRD_IMG)
 $(MKINITRD): tools/mkinitrd.c
        @gcc tools/mkinitrd.c -o $(MKINITRD)
 
+ULIBC_DIR := user/ulibc
+ULIBC_LIB := $(ULIBC_DIR)/libulibc.a
+
+$(ULIBC_LIB):
+       @$(MAKE) -C $(ULIBC_DIR) --no-print-directory
+
 $(USER_ELF): user/init.c user/linker.ld
        @i686-elf-gcc -m32 -I include -ffreestanding -fno-pie -no-pie -nostdlib -Wl,-T,user/linker.ld -o $(USER_ELF) user/init.c user/errno.c
 
diff --git a/user/ulibc/Makefile b/user/ulibc/Makefile
new file mode 100644 (file)
index 0000000..8924596
--- /dev/null
@@ -0,0 +1,30 @@
+# ulibc — Minimal C library for AdrOS userspace
+CC := i686-elf-gcc
+AS := i686-elf-as
+AR := i686-elf-ar
+
+CFLAGS := -m32 -ffreestanding -fno-pie -no-pie -nostdlib -O2 -Wall -Wextra -Iinclude
+ASFLAGS := --32
+
+SRC_C := $(wildcard src/*.c)
+SRC_S := $(wildcard src/*.S)
+OBJ := $(SRC_C:.c=.o) $(SRC_S:.S=.o)
+
+all: libulibc.a
+
+libulibc.a: $(OBJ)
+       @$(AR) rcs $@ $^
+       @echo "  AR      $@"
+
+src/%.o: src/%.c
+       @$(CC) $(CFLAGS) -c $< -o $@
+       @echo "  CC      $<"
+
+src/%.o: src/%.S
+       @$(AS) $(ASFLAGS) $< -o $@
+       @echo "  AS      $<"
+
+clean:
+       rm -f src/*.o libulibc.a
+
+.PHONY: all clean
diff --git a/user/ulibc/include/errno.h b/user/ulibc/include/errno.h
new file mode 100644 (file)
index 0000000..a567956
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef ULIBC_ERRNO_H
+#define ULIBC_ERRNO_H
+
+extern int errno;
+
+#define EPERM    1
+#define ENOENT   2
+#define ESRCH    3
+#define EINTR    4
+#define EIO      5
+#define ENXIO    6
+#define EBADF    9
+#define ECHILD  10
+#define EAGAIN  11
+#define ENOMEM  12
+#define EACCES  13
+#define EFAULT  14
+#define EEXIST  17
+#define ENOTDIR 20
+#define EISDIR  21
+#define EINVAL  22
+#define EMFILE  24
+#define ENOSPC  28
+#define EPIPE   32
+#define ENOSYS  38
+#define ENOTEMPTY 39
+
+/* Convert raw syscall return to errno-style */
+static inline int __syscall_ret(int r) {
+    if (r < 0) {
+        errno = -r;
+        return -1;
+    }
+    return r;
+}
+
+#endif
diff --git a/user/ulibc/include/stdio.h b/user/ulibc/include/stdio.h
new file mode 100644 (file)
index 0000000..b1cddf4
--- /dev/null
@@ -0,0 +1,14 @@
+#ifndef ULIBC_STDIO_H
+#define ULIBC_STDIO_H
+
+#include <stddef.h>
+#include <stdarg.h>
+
+int     putchar(int c);
+int     puts(const char* s);
+int     printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
+int     vprintf(const char* fmt, va_list ap);
+int     snprintf(char* buf, size_t size, const char* fmt, ...) __attribute__((format(printf, 3, 4)));
+int     vsnprintf(char* buf, size_t size, const char* fmt, va_list ap);
+
+#endif
diff --git a/user/ulibc/include/stdlib.h b/user/ulibc/include/stdlib.h
new file mode 100644 (file)
index 0000000..6794a99
--- /dev/null
@@ -0,0 +1,18 @@
+#ifndef ULIBC_STDLIB_H
+#define ULIBC_STDLIB_H
+
+#include <stddef.h>
+
+void*   malloc(size_t size);
+void    free(void* ptr);
+void*   calloc(size_t nmemb, size_t size);
+void*   realloc(void* ptr, size_t size);
+
+int     atoi(const char* s);
+void    exit(int status) __attribute__((noreturn));
+
+#ifndef NULL
+#define NULL ((void*)0)
+#endif
+
+#endif
diff --git a/user/ulibc/include/string.h b/user/ulibc/include/string.h
new file mode 100644 (file)
index 0000000..10fe09d
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ULIBC_STRING_H
+#define ULIBC_STRING_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+void*   memcpy(void* dst, const void* src, size_t n);
+void*   memset(void* s, int c, size_t n);
+void*   memmove(void* dst, const void* src, size_t n);
+int     memcmp(const void* a, const void* b, size_t n);
+size_t  strlen(const char* s);
+char*   strcpy(char* dst, const char* src);
+char*   strncpy(char* dst, const char* src, size_t n);
+int     strcmp(const char* a, const char* b);
+int     strncmp(const char* a, const char* b, size_t n);
+char*   strchr(const char* s, int c);
+char*   strrchr(const char* s, int c);
+char*   strcat(char* dst, const char* src);
+
+#endif
diff --git a/user/ulibc/include/syscall.h b/user/ulibc/include/syscall.h
new file mode 100644 (file)
index 0000000..a108fa9
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef ULIBC_SYSCALL_H
+#define ULIBC_SYSCALL_H
+
+#include <stdint.h>
+
+enum {
+    SYS_WRITE = 1,
+    SYS_EXIT  = 2,
+    SYS_GETPID = 3,
+    SYS_OPEN  = 4,
+    SYS_READ  = 5,
+    SYS_CLOSE = 6,
+    SYS_WAITPID = 7,
+    SYS_LSEEK = 9,
+    SYS_FSTAT = 10,
+    SYS_STAT = 11,
+    SYS_DUP = 12,
+    SYS_DUP2 = 13,
+    SYS_PIPE = 14,
+    SYS_EXECVE = 15,
+    SYS_FORK = 16,
+    SYS_GETPPID = 17,
+    SYS_POLL = 18,
+    SYS_KILL = 19,
+    SYS_SELECT = 20,
+    SYS_IOCTL = 21,
+    SYS_SETSID = 22,
+    SYS_SETPGID = 23,
+    SYS_GETPGRP = 24,
+    SYS_SIGACTION = 25,
+    SYS_SIGPROCMASK = 26,
+    SYS_SIGRETURN = 27,
+    SYS_MKDIR = 28,
+    SYS_UNLINK = 29,
+    SYS_GETDENTS = 30,
+    SYS_FCNTL = 31,
+    SYS_CHDIR = 32,
+    SYS_GETCWD = 33,
+    SYS_PIPE2 = 34,
+    SYS_DUP3 = 35,
+    SYS_OPENAT = 36,
+    SYS_FSTATAT = 37,
+    SYS_UNLINKAT = 38,
+    SYS_RENAME = 39,
+    SYS_RMDIR = 40,
+    SYS_BRK = 41,
+    SYS_NANOSLEEP = 42,
+    SYS_CLOCK_GETTIME = 43,
+    SYS_MMAP = 44,
+    SYS_MUNMAP = 45,
+};
+
+/* Raw syscall wrappers — up to 5 args via INT 0x80 */
+static inline int _syscall0(int nr) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr) : "memory");
+    return ret;
+}
+
+static inline int _syscall1(int nr, int a1) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1) : "memory");
+    return ret;
+}
+
+static inline int _syscall2(int nr, int a1, int a2) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2) : "memory");
+    return ret;
+}
+
+static inline int _syscall3(int nr, int a1, int a2, int a3) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2), "d"(a3) : "memory");
+    return ret;
+}
+
+static inline int _syscall4(int nr, int a1, int a2, int a3, int a4) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2), "d"(a3), "S"(a4) : "memory");
+    return ret;
+}
+
+static inline int _syscall5(int nr, int a1, int a2, int a3, int a4, int a5) {
+    int ret;
+    __asm__ volatile("int $0x80" : "=a"(ret) : "a"(nr), "b"(a1), "c"(a2), "d"(a3), "S"(a4), "D"(a5) : "memory");
+    return ret;
+}
+
+#endif
diff --git a/user/ulibc/include/unistd.h b/user/ulibc/include/unistd.h
new file mode 100644 (file)
index 0000000..35976f1
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef ULIBC_UNISTD_H
+#define ULIBC_UNISTD_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+
+#define STDIN_FILENO  0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+int     read(int fd, void* buf, size_t count);
+int     write(int fd, const void* buf, size_t count);
+int     open(const char* path, int flags);
+int     close(int fd);
+int     lseek(int fd, int offset, int whence);
+int     dup(int oldfd);
+int     dup2(int oldfd, int newfd);
+int     pipe(int fds[2]);
+int     fork(void);
+int     execve(const char* path, const char* const* argv, const char* const* envp);
+int     getpid(void);
+int     getppid(void);
+int     chdir(const char* path);
+int     getcwd(char* buf, size_t size);
+int     mkdir(const char* path);
+int     unlink(const char* path);
+int     rmdir(const char* path);
+int     setsid(void);
+int     setpgid(int pid, int pgid);
+int     getpgrp(void);
+void*   brk(void* addr);
+
+void    _exit(int status) __attribute__((noreturn));
+
+#endif
diff --git a/user/ulibc/src/crt0.S b/user/ulibc/src/crt0.S
new file mode 100644 (file)
index 0000000..746cc10
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * ulibc crt0 — C runtime startup for AdrOS userspace
+ * Entry point: _start → main() → exit()
+ */
+.section .text
+.global _start
+.extern main
+.extern exit
+
+_start:
+    /* Set up user data segments */
+    mov $0x23, %ax
+    mov %ax, %ds
+    mov %ax, %es
+    mov %ax, %fs
+    mov %ax, %gs
+
+    /* Call main() — no argc/argv yet */
+    push $0         /* envp = NULL */
+    push $0         /* argv = NULL */
+    push $0         /* argc = 0 */
+    call main
+    add $12, %esp
+
+    /* exit(main return value) */
+    push %eax
+    call exit
+
+    /* Should never reach here */
+1:  jmp 1b
+
+.section .note.GNU-stack,"",@progbits
diff --git a/user/ulibc/src/errno.c b/user/ulibc/src/errno.c
new file mode 100644 (file)
index 0000000..8330a8f
--- /dev/null
@@ -0,0 +1 @@
+int errno = 0;
diff --git a/user/ulibc/src/stdio.c b/user/ulibc/src/stdio.c
new file mode 100644 (file)
index 0000000..87089a1
--- /dev/null
@@ -0,0 +1,185 @@
+#include "stdio.h"
+#include "unistd.h"
+#include "string.h"
+#include <stdarg.h>
+
+int putchar(int c) {
+    char ch = (char)c;
+    write(STDOUT_FILENO, &ch, 1);
+    return c;
+}
+
+int puts(const char* s) {
+    int len = (int)strlen(s);
+    write(STDOUT_FILENO, s, (size_t)len);
+    write(STDOUT_FILENO, "\n", 1);
+    return len + 1;
+}
+
+/* Minimal vsnprintf supporting: %d %i %u %x %X %s %c %p %% */
+int vsnprintf(char* buf, size_t size, const char* fmt, va_list ap) {
+    size_t pos = 0;
+
+#define PUTC(c) do { if (pos < size - 1) buf[pos] = (c); pos++; } while(0)
+
+    if (size == 0) {
+        /* Just count characters */
+        while (*fmt) { pos++; fmt++; }
+        return (int)pos;
+    }
+
+    while (*fmt) {
+        if (*fmt != '%') {
+            PUTC(*fmt);
+            fmt++;
+            continue;
+        }
+        fmt++; /* skip '%' */
+
+        /* Flags */
+        int pad_zero = 0;
+        int left_align = 0;
+        while (*fmt == '0' || *fmt == '-') {
+            if (*fmt == '0') pad_zero = 1;
+            if (*fmt == '-') left_align = 1;
+            fmt++;
+        }
+        if (left_align) pad_zero = 0;
+
+        /* Width */
+        int width = 0;
+        while (*fmt >= '0' && *fmt <= '9') {
+            width = width * 10 + (*fmt - '0');
+            fmt++;
+        }
+
+        /* Specifier */
+        char tmp[32];
+        int tmplen = 0;
+        const char* str = 0;
+
+        switch (*fmt) {
+        case 'd':
+        case 'i': {
+            int v = va_arg(ap, int);
+            int neg = 0;
+            unsigned int uv;
+            if (v < 0) { neg = 1; uv = (unsigned int)(-(v + 1)) + 1; }
+            else { uv = (unsigned int)v; }
+            if (uv == 0) { tmp[tmplen++] = '0'; }
+            else { while (uv) { tmp[tmplen++] = (char)('0' + uv % 10); uv /= 10; } }
+            if (neg) tmp[tmplen++] = '-';
+            /* reverse */
+            for (int i = 0; i < tmplen / 2; i++) {
+                char t = tmp[i]; tmp[i] = tmp[tmplen-1-i]; tmp[tmplen-1-i] = t;
+            }
+            str = tmp;
+            break;
+        }
+        case 'u': {
+            unsigned int v = va_arg(ap, unsigned int);
+            if (v == 0) { tmp[tmplen++] = '0'; }
+            else { while (v) { tmp[tmplen++] = (char)('0' + v % 10); v /= 10; } }
+            for (int i = 0; i < tmplen / 2; i++) {
+                char t = tmp[i]; tmp[i] = tmp[tmplen-1-i]; tmp[tmplen-1-i] = t;
+            }
+            str = tmp;
+            break;
+        }
+        case 'x':
+        case 'X':
+        case 'p': {
+            const char* hex = (*fmt == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
+            unsigned int v;
+            if (*fmt == 'p') {
+                v = (unsigned int)(uintptr_t)va_arg(ap, void*);
+                tmp[tmplen++] = '0';
+                tmp[tmplen++] = 'x';
+            } else {
+                v = va_arg(ap, unsigned int);
+            }
+            int start = tmplen;
+            if (v == 0) { tmp[tmplen++] = '0'; }
+            else { while (v) { tmp[tmplen++] = hex[v & 0xF]; v >>= 4; } }
+            /* reverse the hex digits only */
+            for (int i = start; i < start + (tmplen - start) / 2; i++) {
+                int j = start + (tmplen - start) - 1 - (i - start);
+                char t = tmp[i]; tmp[i] = tmp[j]; tmp[j] = t;
+            }
+            str = tmp;
+            break;
+        }
+        case 's': {
+            str = va_arg(ap, const char*);
+            if (!str) str = "(null)";
+            tmplen = (int)strlen(str);
+            break;
+        }
+        case 'c': {
+            tmp[0] = (char)va_arg(ap, int);
+            tmplen = 1;
+            str = tmp;
+            break;
+        }
+        case '%':
+            PUTC('%');
+            fmt++;
+            continue;
+        case '\0':
+            goto done;
+        default:
+            PUTC('%');
+            PUTC(*fmt);
+            fmt++;
+            continue;
+        }
+        fmt++;
+
+        if (str && tmplen == 0) tmplen = (int)strlen(str);
+
+        /* Padding */
+        int pad = width - tmplen;
+        if (!left_align && pad > 0) {
+            char pc = pad_zero ? '0' : ' ';
+            for (int i = 0; i < pad; i++) PUTC(pc);
+        }
+        for (int i = 0; i < tmplen; i++) PUTC(str[i]);
+        if (left_align && pad > 0) {
+            for (int i = 0; i < pad; i++) PUTC(' ');
+        }
+    }
+
+done:
+    if (pos < size) buf[pos] = '\0';
+    else if (size > 0) buf[size - 1] = '\0';
+    return (int)pos;
+
+#undef PUTC
+}
+
+int snprintf(char* buf, size_t size, const char* fmt, ...) {
+    va_list ap;
+    va_start(ap, fmt);
+    int r = vsnprintf(buf, size, fmt, ap);
+    va_end(ap);
+    return r;
+}
+
+int vprintf(const char* fmt, va_list ap) {
+    char buf[1024];
+    int n = vsnprintf(buf, sizeof(buf), fmt, ap);
+    if (n > 0) {
+        int w = n;
+        if (w > (int)(sizeof(buf) - 1)) w = (int)(sizeof(buf) - 1);
+        write(STDOUT_FILENO, buf, (size_t)w);
+    }
+    return n;
+}
+
+int printf(const char* fmt, ...) {
+    va_list ap;
+    va_start(ap, fmt);
+    int r = vprintf(fmt, ap);
+    va_end(ap);
+    return r;
+}
diff --git a/user/ulibc/src/stdlib.c b/user/ulibc/src/stdlib.c
new file mode 100644 (file)
index 0000000..7afd02d
--- /dev/null
@@ -0,0 +1,75 @@
+#include "stdlib.h"
+#include "unistd.h"
+#include "string.h"
+
+/*
+ * 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.
+ */
+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;
+
+    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* result = brk(new_end);
+
+    if ((unsigned int)result < (unsigned int)new_end) {
+        return (void*)0;  /* OOM */
+    }
+
+    heap_end = new_end;
+    return old_end;
+}
+
+void free(void* ptr) {
+    /* Bump allocator: no-op for now */
+    (void)ptr;
+}
+
+void* calloc(size_t nmemb, size_t size) {
+    size_t total = nmemb * size;
+    if (nmemb != 0 && total / nmemb != size) return (void*)0;
+    void* p = malloc(total);
+    if (p) memset(p, 0, total);
+    return p;
+}
+
+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). */
+    void* new_ptr = malloc(size);
+    if (new_ptr) memcpy(new_ptr, ptr, size);
+    return new_ptr;
+}
+
+int atoi(const char* s) {
+    int n = 0;
+    int neg = 0;
+    while (*s == ' ' || *s == '\t') s++;
+    if (*s == '-') { neg = 1; s++; }
+    else if (*s == '+') { s++; }
+    while (*s >= '0' && *s <= '9') {
+        n = n * 10 + (*s - '0');
+        s++;
+    }
+    return neg ? -n : n;
+}
+
+void exit(int status) {
+    _exit(status);
+}
diff --git a/user/ulibc/src/string.c b/user/ulibc/src/string.c
new file mode 100644 (file)
index 0000000..1201fe2
--- /dev/null
@@ -0,0 +1,93 @@
+#include "string.h"
+
+void* memcpy(void* dst, const void* src, size_t n) {
+    uint8_t* d = (uint8_t*)dst;
+    const uint8_t* s = (const uint8_t*)src;
+    for (size_t i = 0; i < n; i++) d[i] = s[i];
+    return dst;
+}
+
+void* memset(void* s, int c, size_t n) {
+    uint8_t* p = (uint8_t*)s;
+    for (size_t i = 0; i < n; i++) p[i] = (uint8_t)c;
+    return s;
+}
+
+void* memmove(void* dst, const void* src, size_t n) {
+    uint8_t* d = (uint8_t*)dst;
+    const uint8_t* s = (const uint8_t*)src;
+    if (d < s) {
+        for (size_t i = 0; i < n; i++) d[i] = s[i];
+    } else {
+        for (size_t i = n; i > 0; i--) d[i-1] = s[i-1];
+    }
+    return dst;
+}
+
+int memcmp(const void* a, const void* b, size_t n) {
+    const uint8_t* x = (const uint8_t*)a;
+    const uint8_t* y = (const uint8_t*)b;
+    for (size_t i = 0; i < n; i++) {
+        if (x[i] != y[i]) return (int)x[i] - (int)y[i];
+    }
+    return 0;
+}
+
+size_t strlen(const char* s) {
+    size_t n = 0;
+    while (s[n]) n++;
+    return n;
+}
+
+char* strcpy(char* dst, const char* src) {
+    size_t i = 0;
+    while (src[i]) { dst[i] = src[i]; i++; }
+    dst[i] = 0;
+    return dst;
+}
+
+char* strncpy(char* dst, const char* src, size_t n) {
+    size_t i = 0;
+    while (i < n && src[i]) { dst[i] = src[i]; i++; }
+    while (i < n) { dst[i] = 0; i++; }
+    return dst;
+}
+
+int strcmp(const char* a, const char* b) {
+    while (*a && *a == *b) { a++; b++; }
+    return (int)(unsigned char)*a - (int)(unsigned char)*b;
+}
+
+int strncmp(const char* a, const char* b, size_t n) {
+    for (size_t i = 0; i < n; i++) {
+        if (a[i] != b[i]) return (int)(unsigned char)a[i] - (int)(unsigned char)b[i];
+        if (a[i] == 0) break;
+    }
+    return 0;
+}
+
+char* strchr(const char* s, int c) {
+    while (*s) {
+        if (*s == (char)c) return (char*)s;
+        s++;
+    }
+    return (c == 0) ? (char*)s : (void*)0;
+}
+
+char* strrchr(const char* s, int c) {
+    const char* last = (void*)0;
+    while (*s) {
+        if (*s == (char)c) last = s;
+        s++;
+    }
+    if (c == 0) return (char*)s;
+    return (char*)last;
+}
+
+char* strcat(char* dst, const char* src) {
+    char* p = dst;
+    while (*p) p++;
+    while (*src) *p++ = *src++;
+    *p = 0;
+    return dst;
+}
diff --git a/user/ulibc/src/unistd.c b/user/ulibc/src/unistd.c
new file mode 100644 (file)
index 0000000..5114a75
--- /dev/null
@@ -0,0 +1,92 @@
+#include "unistd.h"
+#include "syscall.h"
+#include "errno.h"
+
+int read(int fd, void* buf, size_t count) {
+    return __syscall_ret(_syscall3(SYS_READ, fd, (int)buf, (int)count));
+}
+
+int write(int fd, const void* buf, size_t count) {
+    return __syscall_ret(_syscall3(SYS_WRITE, fd, (int)buf, (int)count));
+}
+
+int open(const char* path, int flags) {
+    return __syscall_ret(_syscall2(SYS_OPEN, (int)path, flags));
+}
+
+int close(int fd) {
+    return __syscall_ret(_syscall1(SYS_CLOSE, fd));
+}
+
+int lseek(int fd, int offset, int whence) {
+    return __syscall_ret(_syscall3(SYS_LSEEK, fd, offset, whence));
+}
+
+int dup(int oldfd) {
+    return __syscall_ret(_syscall1(SYS_DUP, oldfd));
+}
+
+int dup2(int oldfd, int newfd) {
+    return __syscall_ret(_syscall2(SYS_DUP2, oldfd, newfd));
+}
+
+int pipe(int fds[2]) {
+    return __syscall_ret(_syscall1(SYS_PIPE, (int)fds));
+}
+
+int fork(void) {
+    return __syscall_ret(_syscall0(SYS_FORK));
+}
+
+int execve(const char* path, const char* const* argv, const char* const* envp) {
+    return __syscall_ret(_syscall3(SYS_EXECVE, (int)path, (int)argv, (int)envp));
+}
+
+int getpid(void) {
+    return _syscall0(SYS_GETPID);
+}
+
+int getppid(void) {
+    return _syscall0(SYS_GETPPID);
+}
+
+int chdir(const char* path) {
+    return __syscall_ret(_syscall1(SYS_CHDIR, (int)path));
+}
+
+int getcwd(char* buf, size_t size) {
+    return __syscall_ret(_syscall2(SYS_GETCWD, (int)buf, (int)size));
+}
+
+int mkdir(const char* path) {
+    return __syscall_ret(_syscall1(SYS_MKDIR, (int)path));
+}
+
+int unlink(const char* path) {
+    return __syscall_ret(_syscall1(SYS_UNLINK, (int)path));
+}
+
+int rmdir(const char* path) {
+    return __syscall_ret(_syscall1(SYS_RMDIR, (int)path));
+}
+
+int setsid(void) {
+    return __syscall_ret(_syscall0(SYS_SETSID));
+}
+
+int setpgid(int pid, int pgid) {
+    return __syscall_ret(_syscall2(SYS_SETPGID, pid, pgid));
+}
+
+int getpgrp(void) {
+    return __syscall_ret(_syscall0(SYS_GETPGRP));
+}
+
+void* brk(void* addr) {
+    return (void*)_syscall1(SYS_BRK, (int)addr);
+}
+
+void _exit(int status) {
+    _syscall1(SYS_EXIT, status);
+    for (;;) __asm__ volatile("hlt");
+}