Skip to main content

Overview

LibC is SerenityOS’s implementation of the C standard library, providing POSIX-compliant interfaces for system calls, I/O, memory management, and more. It serves as the foundation for both C and C++ programs running on SerenityOS.
LibC wraps SerenityOS kernel syscalls and provides a familiar POSIX interface for application development.

Standard I/O

File Operations

Standard file I/O using FILE* streams.
#include <stdio.h>

// Open file
FILE* file = fopen("/path/to/file", "r");
if (!file) {
    perror("fopen");
    return 1;
}

// Read data
char buffer[1024];
size_t bytes = fread(buffer, 1, sizeof(buffer), file);

// Write data
fprintf(file, "Hello, World!\n");

// Close file
fclose(file);
// Read line by line
FILE* file = fopen("input.txt", "r");
char* line = NULL;
size_t len = 0;
ssize_t read;

while ((read = getline(&line, &len, file)) != -1) {
    printf("Line: %s", line);
}

free(line);
fclose(file);

Standard Streams

Pre-defined file streams for standard I/O.
stdin
FILE*
Standard input stream (file descriptor 0)
stdout
FILE*
Standard output stream (file descriptor 1)
stderr
FILE*
Standard error stream (file descriptor 2)
// Print to stdout
printf("Normal output\n");
fprintf(stdout, "Explicit stdout\n");

// Print to stderr
fprintf(stderr, "Error message\n");

// Read from stdin
char input[256];
fgets(input, sizeof(input), stdin);

Formatted I/O

// printf family
printf("Integer: %d\n", 42);
sprintf(buffer, "Formatted: %s", str);
snprintf(buffer, size, "Safe: %d", value);

// scanf family
int value;
scanf("%d", &value);
sscanf("42 3.14", "%d %f", &i, &f);

File System

Low-Level I/O

Direct file descriptor operations.
#include <fcntl.h>
#include <unistd.h>

// Open file
int fd = open("/path/to/file", O_RDONLY);
if (fd < 0) {
    perror("open");
    return 1;
}

// Read data
char buffer[1024];
ssize_t bytes = read(fd, buffer, sizeof(buffer));

// Write data
const char* msg = "Hello";
write(fd, msg, strlen(msg));

// Seek position
lseek(fd, 0, SEEK_SET); // Beginning
lseek(fd, 0, SEEK_END); // End

// Close
close(fd);
  • O_RDONLY - Read-only
  • O_WRONLY - Write-only
  • O_RDWR - Read and write
  • O_CREAT - Create if doesn’t exist
  • O_TRUNC - Truncate to zero length
  • O_APPEND - Append to end
  • O_NONBLOCK - Non-blocking mode
  • O_CLOEXEC - Close on exec

Directory Operations

#include <dirent.h>
#include <sys/stat.h>

// Open directory
DIR* dir = opendir("/path/to/dir");
if (!dir) {
    perror("opendir");
    return 1;
}

// Read entries
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
    printf("Name: %s\n", entry->d_name);
}

closedir(dir);

// Create directory
mkdir("/path/to/new/dir", 0755);

// Remove directory
rmdir("/path/to/dir");

File Metadata

#include <sys/stat.h>

struct stat sb;
if (stat("/path/to/file", &sb) == 0) {
    printf("Size: %ld bytes\n", sb.st_size);
    printf("Mode: %o\n", sb.st_mode);
    
    if (S_ISDIR(sb.st_mode)) {
        printf("Is directory\n");
    }
    if (S_ISREG(sb.st_mode)) {
        printf("Is regular file\n");
    }
}

Memory Management

Dynamic Allocation

#include <stdlib.h>
#include <string.h>

// Allocate memory
void* ptr = malloc(1024);
if (!ptr) {
    fprintf(stderr, "Out of memory\n");
    return 1;
}

// Allocate and zero
void* ptr = calloc(10, sizeof(int)); // Array of 10 ints

// Resize allocation
ptr = realloc(ptr, 2048);

// Free memory
free(ptr);
Memory Safety: Always check malloc/calloc/realloc return values and free allocated memory to prevent leaks.

Memory Operations

#include <string.h>

// Copy memory
memcpy(dest, src, size);

// Move memory (handles overlap)
memmove(dest, src, size);

// Set memory
memset(buffer, 0, size); // Zero memory
memset(buffer, 0xFF, size); // Fill with 0xFF

// Compare memory
if (memcmp(buf1, buf2, size) == 0) {
    // Buffers are equal
}

String Handling

String Operations

#include <string.h>

// Copy strings
char dest[100];
strcpy(dest, "Hello");
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Ensure null termination

// Concatenate
strcat(dest, " World");
strncat(dest, str, sizeof(dest) - strlen(dest) - 1);

// Compare
if (strcmp(str1, str2) == 0) {
    // Strings are equal
}

// Search
char* pos = strchr(str, 'x'); // Find character
char* sub = strstr(haystack, needle); // Find substring

// Length
size_t len = strlen(str);
Always use the “n” variants with explicit size limits:
// Unsafe
strcpy(dest, src); // Buffer overflow risk!

// Safe
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';

// Even better: use strlcpy if available
strlcpy(dest, src, sizeof(dest));

Character Classification

#include <ctype.h>

char c = 'A';

if (isalpha(c))  // Letter
if (isdigit(c))  // Digit
if (isalnum(c))  // Letter or digit
if (isspace(c))  // Whitespace
if (isupper(c))  // Uppercase
if (islower(c))  // Lowercase

// Convert case
char upper = toupper('a'); // 'A'
char lower = tolower('A'); // 'a'

Process Control

Process Management

#include <unistd.h>
#include <sys/wait.h>

// Fork process
pid_t pid = fork();
if (pid == 0) {
    // Child process
    printf("I'm the child!\n");
    exit(0);
} else if (pid > 0) {
    // Parent process
    printf("Child PID: %d\n", pid);
    
    // Wait for child
    int status;
    waitpid(pid, &status, 0);
    
    if (WIFEXITED(status)) {
        printf("Child exited: %d\n", WEXITSTATUS(status));
    }
}

Program Execution

#include <unistd.h>

// Execute program (replaces current process)
execl("/bin/ls", "ls", "-l", NULL);
execlp("ls", "ls", "-l", NULL); // Search PATH

char* args[] = { "ls", "-l", NULL };
execv("/bin/ls", args);
execvp("ls", args);

// If exec returns, it failed
perror("exec");

Environment Variables

#include <stdlib.h>

// Get variable
char* path = getenv("PATH");
if (path) {
    printf("PATH: %s\n", path);
}

// Set variable
setenv("MY_VAR", "value", 1); // 1 = overwrite

// Unset variable
unsetenv("MY_VAR");

Threading

POSIX Threads

#include <pthread.h>

void* thread_function(void* arg) {
    int* value = (int*)arg;
    printf("Thread received: %d\n", *value);
    return NULL;
}

int main() {
    pthread_t thread;
    int arg = 42;
    
    // Create thread
    pthread_create(&thread, NULL, thread_function, &arg);
    
    // Wait for completion
    pthread_join(thread, NULL);
    
    return 0;
}

Mutexes

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;

void* increment(void* arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_mutex_lock(&mutex);
        shared_counter++;
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

Time & Signals

Time Functions

#include <time.h>
#include <sys/time.h>

// Get current time
time_t now = time(NULL);

// Convert to string
printf("Time: %s", ctime(&now));

// Structured time
struct tm* tm_info = localtime(&now);
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
printf("Formatted: %s\n", buffer);

// High resolution time
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("Seconds: %ld, Nanoseconds: %ld\n", ts.tv_sec, ts.tv_nsec);

Signal Handling

#include <signal.h>

void signal_handler(int signum) {
    printf("Caught signal %d\n", signum);
    exit(1);
}

int main() {
    // Install handler
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
    
    // Block signals
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    pthread_sigmask(SIG_BLOCK, &set, NULL);
    
    return 0;
}

Networking

Sockets

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// Create socket
int sock = socket(AF_INET, SOCK_STREAM, 0);

// Server: bind and listen
struct sockaddr_in addr = {
    .sin_family = AF_INET,
    .sin_port = htons(8080),
    .sin_addr.s_addr = INADDR_ANY
};
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
listen(sock, 5);

// Accept connections
int client = accept(sock, NULL, NULL);

// Client: connect
connect(sock, (struct sockaddr*)&addr, sizeof(addr));

// Send/receive
send(sock, data, size, 0);
recv(sock, buffer, size, 0);

Error Handling

errno and perror

#include <errno.h>
#include <string.h>

int fd = open("/nonexistent", O_RDONLY);
if (fd < 0) {
    // Check specific error
    if (errno == ENOENT) {
        fprintf(stderr, "File not found\n");
    }
    
    // Print error message
    perror("open");
    
    // Get error string
    fprintf(stderr, "Error: %s\n", strerror(errno));
    
    return 1;
}
  • ENOENT - No such file or directory
  • EACCES - Permission denied
  • ENOMEM - Out of memory
  • EINVAL - Invalid argument
  • EIO - I/O error
  • EAGAIN - Try again (non-blocking I/O)
  • EINTR - Interrupted system call

Best Practices

Check Return Values: Always check return values from system calls and library functions. Most return -1 or NULL on error.
Buffer Safety: Use bounded functions (strncpy, snprintf) to prevent buffer overflows.
Resource Cleanup: Always close file descriptors, free memory, and destroy mutexes when done.

Build docs developers (and LLMs) love