Forest OS libc
A lightweight, Linux-compatible C standard library for Forest OS. Forest OS libc provides the middleman layer between userspace applications and the Forest OS kernel, handling architecture abstraction, error translation, data type conversion, and POSIX-compatible function signatures.
Source location: libs/libc/
Overview
Forest OS libc (internally called "Fern libc") is a C23-compatible standard library designed for embedded and OS-development contexts. It wraps kernel system calls behind familiar POSIX and C standard interfaces, enabling developers to write portable C code that targets Forest OS.
Key design goals:
- Linux ABI compatibility -- syscall numbers and register conventions match Linux x86/x86_64
- POSIX conformance -- stdio, stdlib, string, unistd, signal, pthread, dirent, and more
- Freestanding operation -- no dependency on external toolchain libraries; compiled with
-ffreestanding -nostdinc - Minimal footprint -- bump allocator for
malloc, minimal buffering, no locale support
Directory Structure
libs/libc/
├── include/libc/ # Public headers
│ ├── sys/ # System headers (types, stat, socket, mman, etc.)
│ ├── arpa/ # Network address functions (arpa/inet.h)
│ ├── netinet/ # Internet protocol headers (netinet/in.h)
│ ├── stdio.h # Standard I/O
│ ├── stdlib.h # General utilities, memory allocation
│ ├── string.h # String and memory functions
│ ├── unistd.h # POSIX system interface
│ ├── errno.h # Error numbers
│ ├── signal.h # Signal handling
│ ├── pthread.h # POSIX threads (stubs)
│ ├── dirent.h # Directory operations
│ ├── fcntl.h # File control
│ ├── time.h # Time functions
│ ├── math.h # Mathematical functions
│ ├── termios.h # Terminal I/O
│ ├── poll.h # I/O multiplexing
│ └── ... # 56 headers total
├── src/ # Implementation
│ ├── syscalls.c # Low-level syscall wrappers (1101 lines)
│ ├── string.c # String/memory functions
│ ├── stdio.c # Buffered I/O, printf family
│ ├── stdlib.c # malloc, qsort, string conversion
│ ├── errno.c # errno variable, perror()
│ ├── pthread.c # Thread stubs (single-threaded)
│ ├── signal.c # Signal handling
│ ├── dirent.c # Directory reading (getdents)
│ └── assert.c # __assert_fail()
└── README.md
Supported Headers and Functions
stdio.h -- Standard I/O
Buffered I/O built on raw read()/write() syscalls.
| Category | Functions |
|---|---|
| File operations | fopen, freopen, fclose, fflush, setvbuf |
| Formatted I/O | printf, fprintf, snprintf, sprintf, vsnprintf, dprintf |
| Character I/O | fgetc, fputc, getchar, putchar, ungetc |
| String I/O | fgets, fputs, puts |
| Block I/O | fread, fwrite |
| Positioning | fseek, ftell, rewind, fgetpos, fsetpos |
| Error handling | feof, ferror, clearerr, perror |
| POSIX extensions | fileno, fdopen, getline, getdelim |
| GNU extensions | asprintf, vasprintf |
| Thread-safe | flockfile, ftrylockfile, funlockfile |
| Unlocked I/O | getc_unlocked, putchar_unlocked, fread_unlocked, etc. |
FILE structure (stdio.h:51):
typedef struct _FILE {
int fd; /* File descriptor */
int flags; /* Mode and status flags */
char *buffer; /* Buffer pointer */
size_t buf_size; /* Buffer size */
size_t buf_pos; /* Current position in buffer */
size_t buf_len; /* Valid data length in buffer */
int error; /* Error indicator */
int eof; /* End-of-file indicator */
int unget; /* Pushed-back character (-1 if none) */
} FILE;
Note: printf() format specifier support includes %s, %c, %d/%i, %u, %x/%X, %p, and %%. Width and zero-padding are supported. %f/%e/%g (floating-point formatting) are not yet implemented.
stdlib.h -- General Utilities
| Category | Functions |
|---|---|
| Memory allocation | malloc, calloc, realloc, free, posix_memalign, aligned_alloc |
| String conversion | atoi, atol, atoll, atof, strtol, strtoul, strtoll, strtoull, strtod, strtof |
| Non-standard | itoa, ltoa, lltoa |
| Random numbers | rand, srand, rand_r |
| Environment | getenv, setenv, unsetenv, putenv |
| Process control | exit, _Exit, abort, atexit, system |
| Search/sort | bsearch, qsort, qsort_r |
| Math helpers | abs, labs, llabs, div, ldiv, lldiv |
| Temp files | mkstemp, mkdtemp, mktemp, realpath |
Memory allocator is a simple bump allocator using brk() (stdlib.c:23-63). Allocations are 16-byte aligned. free() is a no-op -- memory is never reclaimed. This is suitable for short-lived programs and OS development.
string.h -- String Handling
| Category | Functions |
|---|---|
| Copying | memcpy, memmove, strcpy, strncpy, strlcpy, strdup, strndup |
| Concatenation | strcat, strncat, strlcat |
| Comparison | memcmp, strcmp, strncmp, strcasecmp, strncasecmp, strcoll, strxfrm |
| Search | memchr, memrchr, memmem, strchr, strrchr, strstr, strpbrk |
| Length | strlen, strnlen |
| Tokenization | strtok, strtok_r, strsep |
| Spans | strspn, strcspn |
| Set | memset, memset_explicit (C23) |
| Error strings | strerror |
| BSD compat | bzero, bcopy, bcmp, index, rindex |
memcpy uses word-sized copies when source, destination, and length are all aligned (string.c:19-39).
unistd.h -- POSIX System Interface
| Category | Functions |
|---|---|
| File I/O | read, write, pread, pwrite, close, lseek, fsync, ftruncate |
| File descriptors | dup, dup2, dup3, pipe, pipe2 |
| Process control | fork, vfork, execve, execv, execvp, execl, execlp, _exit |
| Process info | getpid, getppid, getpgrp, getpgid, setpgid, setsid, getsid, gettid |
| User/group | getuid, geteuid, getgid, getegid, setuid, setgid, seteuid, setegid |
| Working directory | getcwd, chdir, fchdir, chroot |
| File system | access, link, symlink, readlink, unlink, rmdir, chmod, chown |
| Sleep | sleep, usleep, alarm, pause |
| Terminal | isatty, ttyname, tcgetpgrp, tcsetpgrp |
| Hostname | gethostname, sethostname |
| Misc | sysconf, getopt, brk, sbrk, sync, nice |
| Forest ext | poweroff, reboot |
errno.h -- Error Numbers
All 133 Linux-compatible error codes are defined (errno.h:22-155), including:
- File errors:
ENOENT,EACCES,EEXIST,ENOTDIR,EISDIR,EMFILE,ENOSPC - Process errors:
ESRCH,ECHILD,EAGAIN,ENOMEM - Network errors:
ECONNREFUSED,ETIMEDOUT,EADDRINUSE,ENETUNREACH - Signal errors:
EINTR
The errno macro expands to (*__errno_location()) for thread-safe access (errno.h:19).
signal.h -- Signal Handling
All standard POSIX signals (1-31) plus real-time signals (32-64) are defined.
| Category | Functions/Macros |
|---|---|
| Handler setup | signal, sigaction |
| Set manipulation | sigemptyset, sigfillset, sigaddset, sigdelset, sigismember |
| Blocking | sigprocmask, sigpending, sigsuspend |
| Sending | kill, killpg, raise, sigqueue |
| Waiting | sigwait, sigwaitinfo, sigtimedwait |
| Info | strsignal, psignal, psiginfo |
The sigaction struct includes sa_handler/sa_sigaction, sa_mask, sa_flags, and sa_restorer -- matching the Linux kernel's struct sigaction layout.
pthread.h -- POSIX Threads
Important: Full threading is not implemented in the Forest kernel. The pthread header provides a complete API surface for compatibility, but functions return error codes:
| Function | Return value |
|---|---|
pthread_create |
EAGAIN (no thread support) |
pthread_join |
ESRCH |
pthread_self |
getpid() (process ID as thread ID) |
pthread_mutex_lock |
Always succeeds (single-threaded) |
pthread_cond_wait |
EINVAL (would deadlock) |
pthread_once |
Calls init routine, then marks as done |
What works: pthread_mutex_init/lock/unlock, pthread_once, thread-specific data (pthread_key_create/setspecific/getspecific), barriers, spinlocks.
What doesn't: pthread_create, pthread_join, pthread_detach, condition variable waits.
dirent.h -- Directory Operations
| Functions |
|---|
opendir, fdopendir, closedir, readdir, readdir64 |
readdir_r, readdir64_r, rewinddir, seekdir, telldir, dirfd |
scandir, scandir64, scandirat |
getdents, getdents64 |
Directory reading uses the getdents/getdents64 syscalls internally, parsing Linux-format directory entries.
Additional Headers
| Header | Purpose |
|---|---|
fcntl.h |
File open flags (O_RDONLY, O_CREAT, O_NONBLOCK, etc.), open, openat, fcntl, flock |
time.h |
clock, time, mktime, gmtime, localtime, strftime, clock_gettime, nanosleep, gettimeofday |
math.h |
Full set of trig, log, power, rounding functions (software FPU) |
termios.h |
tcgetattr, tcsetattr, terminal flags |
poll.h |
poll() with POLLIN, POLLOUT, POLLERR, etc. |
sys/socket.h |
socket, bind, listen, accept, connect, send, recv, setsockopt |
netinet/in.h |
sockaddr_in, sockaddr_in6, protocol numbers, multicast |
arpa/inet.h |
inet_ntop, inet_pton, htonl, htons, ntohl, ntohs |
netdb.h |
gethostbyname, getaddrinfo, getnameinfo |
sys/mman.h |
mmap, munmap, mprotect, madvise, mlock |
sys/stat.h |
struct stat, S_ISREG, S_ISDIR, permission bits |
sched.h |
sched_yield, sched_param, cpu_set_t, scheduling policies |
System Call Architecture
How System Calls Work
When an application calls a libc function like read():
ssize_t bytes = read(fd, buffer, size);
The libc read() function (syscalls.c:312-314):
- Places arguments in CPU registers (
fdinebx/edi,bufferinecx/rsi,sizeinedx/rdx) - Places the syscall number in
eax/rax(e.g.,SYS_read = 0) - Issues
int $0x80to transfer control to the kernel - Receives the return value in
eax/rax - Checks for errors (negative return = error)
- Sets
errnoif needed and returns-1or the actual value
Internal Syscall Primitives
The libc defines six internal functions for invoking syscalls with 0-6 arguments (syscalls.c:79-251):
static inline syscall_ret_t __syscall0(syscall_arg_t num);
static inline syscall_ret_t __syscall1(syscall_arg_t num, syscall_arg_t a1);
static inline syscall_ret_t __syscall2(syscall_arg_t num, syscall_arg_t a1, syscall_arg_t a2);
static inline syscall_ret_t __syscall3(syscall_arg_t num, syscall_arg_t a1, syscall_arg_t a2, syscall_arg_t a3);
static inline syscall_ret_t __syscall4(...);
static inline syscall_ret_t __syscall5(...);
static inline syscall_ret_t __syscall6(...);
The 6-argument variant uses push %rbp / mov arg6, %rbp to pass the sixth argument in EBP, then restores it.
Generic Syscall Interface
Applications can make raw syscalls via syscall() (syscalls.c:292-304):
#include <sys/syscall.h>
#include <unistd.h>
long result = syscall(SYS_write, STDOUT_FILENO, "hello\n", 6);
Register Conventions
Forest OS uses Linux-compatible register conventions for system calls:
| Register (x86) | Register (x86_64) | Purpose |
|---|---|---|
eax |
rax |
Syscall number (in) / return value (out) |
ebx |
rdi |
Argument 1 |
ecx |
rsi |
Argument 2 |
edx |
rdx |
Argument 3 |
esi |
r10 |
Argument 4 |
edi |
r8 |
Argument 5 |
ebp |
r9 |
Argument 6 |
All syscalls are invoked via int $0x80 (interrupt 0x80), which is the standard x86 Linux syscall vector.
Error Handling
Forest OS libc follows the POSIX error convention:
- On success: return the actual value (>= 0, or a valid pointer)
- On error: return
-1(for functions returningint/ssize_t) orNULL(for functions returning pointers), and seterrnoto the positive error code
The translation happens in __syscall_ret() (syscalls.c:266-272):
static inline long __syscall_ret(syscall_ret_t ret) {
if (ret < 0 && ret > -4096) {
errno = (int)(-ret);
return -1;
}
return (long)ret;
}
The kernel returns negative error codes (e.g., -ENOENT = -2). The libc negates them and stores the positive value in errno.
perror and strerror
#include <stdio.h>
#include <errno.h>
FILE *fp = fopen("/nonexistent", "r");
if (!fp) {
perror("open failed"); // prints: "open failed: No such file or directory"
}
strerror() returns a human-readable string for any error code (string.c:500-595).
Supported Syscalls
Forest OS implements Linux x86_64-compatible syscall numbers. The full list is defined in sys/syscall.h. Key syscalls with their numbers:
| Category | Syscall | Number |
|---|---|---|
| File I/O | read |
0 |
write |
1 | |
open |
2 | |
close |
3 | |
lseek |
8 | |
| Memory | mmap |
9 |
mprotect |
10 | |
munmap |
11 | |
brk |
12 | |
| Signals | rt_sigaction |
13 |
rt_sigprocmask |
14 | |
| File control | ioctl |
16 |
pipe |
22 | |
select |
23 | |
| Networking | socket |
41 |
connect |
42 | |
accept |
43 | |
bind |
49 | |
listen |
50 | |
| Process | fork |
57 |
vfork |
58 | |
execve |
59 | |
exit |
60 | |
wait4 |
61 | |
kill |
62 | |
| User/group | getuid |
102 |
getgid |
104 | |
setuid |
105 | |
| Time | gettimeofday |
96 |
clock_gettime |
228 | |
nanosleep |
35 | |
| Mount | mount |
165 |
umount2 |
166 |
Forest-Specific Extensions (syscall >= 470)
| Syscall | Number | Purpose |
|---|---|---|
netinfo |
470 | Query network interface information |
mmap_fb |
471 | Map framebuffer into userspace |
munmap_fb |
472 | Unmap framebuffer |
get_fb_info |
473 | Get framebuffer dimensions/format |
power |
474 | Power management (shutdown/reboot) |
userctl |
475 | User account management |
start_fb_watcher |
476 | Start framebuffer change notifications |
stop_fb_watcher |
477 | Stop framebuffer change notifications |
fb_flush |
478 | Flush framebuffer to display |
read_kbd_event |
479 | Read keyboard input event |
read_mouse_event |
480 | Read mouse input event |
poll_input |
481 | Check for pending input events |
Networking Wrappers
Forest OS libc provides a complete BSD sockets API for TCP/UDP networking:
Basic TCP Client
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void) {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = inet_addr("127.0.0.1"),
};
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sockfd);
return 1;
}
const char *msg = "Hello, Forest OS!";
write(sockfd, msg, strlen(msg));
char buf[1024];
ssize_t n = read(sockfd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Received: %s\n", buf);
}
close(sockfd);
return 0;
}
Basic TCP Server
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void) {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = INADDR_ANY,
};
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(server_fd, 128);
printf("Listening on port 8080...\n");
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
char buf[1024];
ssize_t n = read(client_fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Client said: %s\n", buf);
write(client_fd, "Echo!", 5);
}
close(client_fd);
close(server_fd);
return 0;
}
Byte Order Conversion
#include <arpa/inet.h>
uint32_t net_order = htonl(0x01020304); // host to network
uint16_t port = htons(8080); // host to network (short)
uint32_t host_val = ntohl(net_order); // network to host
UDP Socket
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(void) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(9000),
.sin_addr.s_addr = INADDR_ANY,
};
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
char buf[1024];
struct sockaddr_in sender;
socklen_t sender_len = sizeof(sender);
ssize_t n = recvfrom(fd, buf, sizeof(buf), 0,
(struct sockaddr *)&sender, &sender_len);
// Send response to sender
sendto(fd, "ACK", 3, 0, (struct sockaddr *)&sender, sender_len);
close(fd);
return 0;
}
Thread Support
Forest OS libc provides a full pthread API for source compatibility, but threading is not implemented in the kernel. The implementation is in src/pthread.c.
What Works (Single-Threaded)
These functions operate correctly in a single-threaded context:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
void increment(void) {
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
}
pthread_once_t init = PTHREAD_ONCE_INIT;
void init_func(void) {
printf("Initialized once\n");
}
int main(void) {
pthread_once(&init, init_func); // Works
pthread_once(&init, init_func); // Skipped (already done)
increment(); // Works
increment(); // Works
return 0;
}
What Doesn't Work
pthread_t tid;
int rc = pthread_create(&tid, NULL, worker, NULL);
// rc == EAGAIN (thread creation not supported)
rc = pthread_join(tid, NULL);
// rc == ESRCH (no such thread)
Thread-Specific Data
#include <pthread.h>
pthread_key_t my_key;
void cleanup(void *val) {
free(val);
}
int main(void) {
pthread_key_create(&my_key, cleanup);
pthread_setspecific(my_key, "hello");
char *val = pthread_getspecific(my_key);
// val == "hello"
pthread_key_delete(my_key);
return 0;
}
Building libc
Integrated Build (with Forest OS build system)
# In your Makefile
include libs/libc/Makefile.inc
CFLAGS += $(LIBC_INCLUDES)
my_program: my_program.o $(LIBC_LIBRARY)
$(LD) -o $@ $^
Standalone Build
# Create output directory
mkdir -p obj/forestlibc
# Compile all source files with freestanding flags
for src in libs/libc/src/*.c; do
gcc -ffreestanding -nostdinc -Ilibs/libc/include/libc \
-c "$src" -o "obj/forestlibc/$(basename "$src" .c).o"
done
# Create static library
ar rcs obj/libforest.a obj/forestlibc/*.o
Compiler Flags
When using Forest OS libc, your code must be compiled with:
-ffreestanding # No assumption about standard library
-nostdinc # Don't search system include paths
-Ilibs/libc/include/libc # Add our headers
Link against libforest.a (or the appropriate library target).
Usage Examples
Hello World
#include <stdio.h>
int main(void) {
printf("Hello from Forest OS!\n");
return 0;
}
File I/O
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
int main(void) {
// Write a file
int fd = open("/tmp/test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd >= 0) {
write(fd, "Hello, Forest!\n", 15);
close(fd);
}
// Read it back with stdio
FILE *fp = fopen("/tmp/test.txt", "r");
if (fp) {
char line[256];
while (fgets(line, sizeof(line), fp)) {
printf("%s", line);
}
fclose(fp);
}
// Check file info
struct stat st;
if (stat("/tmp/test.txt", &st) == 0) {
printf("Size: %ld bytes\n", st.st_size);
}
return 0;
}
Process Control
#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child: PID=%d, PPID=%d\n", getpid(), getppid());
_exit(0);
} else if (pid > 0) {
// Parent process
int status;
waitpid(pid, &status, 0);
printf("Parent: child exited with status %d\n", WEXITSTATUS(status));
}
return 0;
}
Memory Mapping
#include <sys/mman.h>
#include <stdio.h>
int main(void) {
// Map anonymous memory
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
// Use the memory
char *str = (char *)ptr;
snprintf(str, 4096, "Mapped at %p", ptr);
printf("%s\n", str);
munmap(ptr, 4096);
return 0;
}
Directory Listing
#include <dirent.h>
#include <stdio.h>
int main(void) {
DIR *dir = opendir("/");
if (!dir) {
perror("opendir");
return 1;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
printf("%s (type=%d)\n", entry->d_name, entry->d_type);
}
closedir(dir);
return 0;
}
Time Functions
#include <time.h>
#include <stdio.h>
int main(void) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
printf("Current time: %s\n", buf);
// High-resolution time
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
printf("Monotonic: %ld.%09ld seconds\n", ts.tv_sec, ts.tv_nsec);
return 0;
}
String Conversion and Sorting
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int cmp_int(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int main(void) {
// String conversion
long val = strtol("0x1A", NULL, 16);
printf("Parsed hex: %ld\n", val); // 26
// Sorting
int arr[] = {5, 2, 8, 1, 9, 3};
qsort(arr, 6, sizeof(int), cmp_int);
for (int i = 0; i < 6; i++) {
printf("%d ", arr[i]);
}
printf("\n"); // 1 2 3 5 8 9
return 0;
}
Limitations Compared to glibc/musl
| Feature | Forest OS libc | glibc | musl |
|---|---|---|---|
malloc implementation |
Bump allocator (no free) |
Full ptmalloc2 | Full musl allocator |
printf format specifiers |
%s, %c, %d, %u, %x, %p |
Full C11 | Full C11 |
Floating-point formatting (%f) |
Not implemented | Yes | Yes |
| Locale support | C locale only | Full i18n/l10n | Minimal locale |
| Wide characters | Minimal stubs | Full | Full |
| Threading | Stubs (single-threaded) | Full NPTL | Full musl threads |
system() |
Returns ENOSYS |
Full implementation | Full implementation |
atexit handlers |
Stored but not called on _exit |
Full | Full |
realloc |
Always allocates new copy | In-place when possible | In-place when possible |
math.h |
Software implementations | Hardware FPU | Hardware FPU |
regex.h |
Not implemented | Full POSIX | Full POSIX |
iconv.h |
Not implemented | Full | Full |
| Shared libraries | Not supported | Full ELF shared libs | Full ELF shared libs |
| Dynamic linking | Not supported | Full | Full |
| NSS/pam | Not supported | Full | Partial |
Known Gaps
free()is a no-op -- memory is never returned to the kernel. Suitable for short-lived programs but will leak in long-running daemons.- No
%f/%e/%gin printf -- floating-point formatting is not implemented. Usesnprintfwith integer arithmetic for numeric output. - No thread creation --
pthread_createreturnsEAGAIN. Mutex/once/TSD primitives work for single-threaded synchronization. - No
system()-- always returns -1 withENOSYS. atexithandlers are registered but not invoked by_exit()(they are only invoked byexit()if it calls_exitafter them).readdirskips.and..-- unlike POSIX, these entries are filtered out in the implementation (dirent.c:147-149).
Contributing
When adding new functionality:
- Add the function declaration in the appropriate header under
include/libc/ - Implement in the appropriate
src/*.cfile - If it's a syscall wrapper, add to
src/syscalls.cwith the correctSYS_xxxnumber - Follow existing code style: 4-space indent,
snake_case,extern "C"in headers - Add the function to the relevant section in the syscall number header if needed
License
Forest OS libc is part of Forest OS and follows the same license terms.