From 96784f611f57852569ea13be8b6cf1638acb2de7 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Wed, 25 Mar 2026 20:21:12 +0000 Subject: [PATCH] lib: c: io: implement standard FILE and DIR interfaces --- lib/libc/include/dirent.h | 20 +++++ lib/libc/include/stdio.h | 29 +++++++ lib/libc/include/sys/types.h | 2 + lib/libc/io/stdio/closedir.c | 19 +++++ lib/libc/io/stdio/dir.c | 81 ++++++++++++++++++ lib/libc/io/stdio/dir.h | 24 ++++++ lib/libc/io/stdio/fclose.c | 24 ++++++ lib/libc/io/stdio/fdopendir.c | 0 lib/libc/io/stdio/feof.c | 6 ++ lib/libc/io/stdio/ferr.c | 6 ++ lib/libc/io/stdio/fgetc.c | 31 +++++++ lib/libc/io/stdio/fgets.c | 40 +++++++++ lib/libc/io/stdio/file.c | 76 +++++++++++++++++ lib/libc/io/stdio/file.h | 25 ++++++ lib/libc/io/stdio/fopen.c | 151 ++++++++++++++++++++++++++++++++++ lib/libc/io/stdio/fputc.c | 0 lib/libc/io/stdio/fputs.c | 0 lib/libc/io/stdio/fread.c | 0 lib/libc/io/stdio/fwrite.c | 0 lib/libc/io/stdio/opendir.c | 30 +++++++ lib/libc/io/stdio/readdir.c | 35 ++++++++ 21 files changed, 599 insertions(+) create mode 100644 lib/libc/io/stdio/closedir.c create mode 100644 lib/libc/io/stdio/dir.c create mode 100644 lib/libc/io/stdio/dir.h create mode 100644 lib/libc/io/stdio/fclose.c create mode 100644 lib/libc/io/stdio/fdopendir.c create mode 100644 lib/libc/io/stdio/feof.c create mode 100644 lib/libc/io/stdio/ferr.c create mode 100644 lib/libc/io/stdio/fgetc.c create mode 100644 lib/libc/io/stdio/fgets.c create mode 100644 lib/libc/io/stdio/file.c create mode 100644 lib/libc/io/stdio/file.h create mode 100644 lib/libc/io/stdio/fopen.c create mode 100644 lib/libc/io/stdio/fputc.c create mode 100644 lib/libc/io/stdio/fputs.c create mode 100644 lib/libc/io/stdio/fread.c create mode 100644 lib/libc/io/stdio/fwrite.c create mode 100644 lib/libc/io/stdio/opendir.c create mode 100644 lib/libc/io/stdio/readdir.c diff --git a/lib/libc/include/dirent.h b/lib/libc/include/dirent.h index 69abd3c..95754fd 100644 --- a/lib/libc/include/dirent.h +++ b/lib/libc/include/dirent.h @@ -1,6 +1,8 @@ #ifndef DIRENT_H_ #define DIRENT_H_ +#include + #define DT_UNKNOWN 0 #define DT_BLK 1 #define DT_CHR 2 @@ -10,4 +12,22 @@ #define DT_REG 6 #define DT_SOCK 7 +struct dirent { + ino_t d_ino; + off_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +struct __opaque_dir; +typedef struct __opaque_dir DIR; + +extern DIR *opendir(const char *name); +extern DIR *fdopendir(int fd); + +extern int closedir(DIR *dirp); + +extern struct dirent *readdir(DIR *dirp); + #endif diff --git a/lib/libc/include/stdio.h b/lib/libc/include/stdio.h index c934b8c..3a98218 100644 --- a/lib/libc/include/stdio.h +++ b/lib/libc/include/stdio.h @@ -4,10 +4,15 @@ #include #include +#define EOF -1 + #ifdef __cplusplus extern "C" { #endif +struct __opaque_file; +typedef struct __opaque_file FILE; + extern int snprintf(char *buffer, size_t count, const char *format, ...); extern int vsnprintf( char *buffer, @@ -15,6 +20,30 @@ extern int vsnprintf( const char *format, va_list va); +extern FILE *fopen(const char *path, const char *mode); +extern int fclose(FILE *stream); + +extern int feof(FILE *stream); +extern int ferr(FILE *stream); + +extern size_t fread(void *buf, size_t size, size_t count, FILE *stream); +extern size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream); + +extern int fgetc(FILE *stream); +static inline int getc(FILE *stream) +{ + return fgetc(stream); +} + +extern int fputc(int c, FILE *stream); +static inline int putc(int c, FILE *stream) +{ + return fputc(c, stream); +} + +extern char *fgets(char *restrict str, int count, FILE *restrict stream); +extern int fputs(const char *restrict str, FILE *restrict stream); + #ifdef __cplusplus } #endif diff --git a/lib/libc/include/sys/types.h b/lib/libc/include/sys/types.h index 7f6b647..7ee646d 100644 --- a/lib/libc/include/sys/types.h +++ b/lib/libc/include/sys/types.h @@ -7,6 +7,8 @@ #define SEEK_CUR 1 #define SEEK_END 2 +typedef size_t ino_t; + struct dentry { unsigned long d_ino; unsigned short d_reclen; diff --git a/lib/libc/io/stdio/closedir.c b/lib/libc/io/stdio/closedir.c new file mode 100644 index 0000000..6edc494 --- /dev/null +++ b/lib/libc/io/stdio/closedir.c @@ -0,0 +1,19 @@ +#include "dir.h" + +#include +#include +#include +#include +#include + +int closedir(struct __opaque_dir *dirp) +{ + close(dirp->d_fd); + + if (dirp->d_buf) { + free(dirp->d_buf); + } + + free(dirp); + return SUCCESS; +} diff --git a/lib/libc/io/stdio/dir.c b/lib/libc/io/stdio/dir.c new file mode 100644 index 0000000..a1699b9 --- /dev/null +++ b/lib/libc/io/stdio/dir.c @@ -0,0 +1,81 @@ +#include "dir.h" + +#include +#include +#include +#include + +#define DEFAULT_BUFFER_SIZE 4096 + +static void dirent_convert(const struct dentry *in, struct dirent *out) +{ + out->d_ino = in->d_ino; + out->d_type = in->d_type; + out->d_reclen = in->d_reclen; + strncpy(out->d_name, in->d_name, sizeof out->d_name - 1); + out->d_name[sizeof out->d_name - 1] = 0; +} + +int __libc_dir_refill(struct __opaque_dir *d) +{ + if (d->d_fd < 0) { + return EBADF; + } + + if (d->d_flags & DIR_ERR) { + return EIO; + } + + if (d->d_flags & DIR_EOF) { + return SUCCESS; + } + + if (!d->d_buf) { + d->d_buf_max = DEFAULT_BUFFER_SIZE; + d->d_buf = malloc(d->d_buf_max); + if (!d->d_buf) { + d->d_buf_max = 0; + return ENOMEM; + } + } + + long r = getdents(d->d_fd, (struct dentry *)d->d_buf, d->d_buf_max); + if (r < 0) { + return -r; + } + + d->d_buf_datalen = r; + + if (r > 0) { + struct dentry *dent + = (struct dentry *)(d->d_buf + d->d_buf_readptr); + dirent_convert(dent, &d->d_current); + } + + return SUCCESS; +} + +int __libc_dir_move_next(struct __opaque_dir *d) +{ + if (d->d_fd < 0) { + return -EBADF; + } + + if (d->d_flags & DIR_ERR) { + return -EIO; + } + + if (d->d_flags & DIR_EOF) { + return -SUCCESS; + } + + d->d_buf_readptr += d->d_current.d_reclen; + if (d->d_buf_readptr >= d->d_buf_datalen) { + return 0; + } + + struct dentry *dent = (struct dentry *)(d->d_buf + d->d_buf_readptr); + dirent_convert(dent, &d->d_current); + + return 1; +} diff --git a/lib/libc/io/stdio/dir.h b/lib/libc/io/stdio/dir.h new file mode 100644 index 0000000..f889bb6 --- /dev/null +++ b/lib/libc/io/stdio/dir.h @@ -0,0 +1,24 @@ +#ifndef _LIBC_IO_DIR_H_ +#define _LIBC_IO_DIR_H_ + +#include +#include + +enum dir_flags { + DIR_EOF = 0x01u, + DIR_ERR = 0x02u, +}; + +struct __opaque_dir { + enum dir_flags d_flags; + int d_fd; + char *d_buf; + struct dirent d_current; + size_t d_buf_datalen, d_buf_max; + size_t d_buf_readptr; +}; + +extern int __libc_dir_refill(struct __opaque_dir *d); +extern int __libc_dir_move_next(struct __opaque_dir *d); + +#endif diff --git a/lib/libc/io/stdio/fclose.c b/lib/libc/io/stdio/fclose.c new file mode 100644 index 0000000..0a1f3a9 --- /dev/null +++ b/lib/libc/io/stdio/fclose.c @@ -0,0 +1,24 @@ +#include "file.h" + +#include +#include +#include +#include +#include +#include + +int fclose(struct __opaque_file *stream) +{ + if (stream->f_fd < 0) { + return __set_errno(EBADF); + } + + close(stream->f_fd); + + if (stream->f_buf) { + free(stream->f_buf); + } + + free(stream); + return SUCCESS; +} diff --git a/lib/libc/io/stdio/fdopendir.c b/lib/libc/io/stdio/fdopendir.c new file mode 100644 index 0000000..e69de29 diff --git a/lib/libc/io/stdio/feof.c b/lib/libc/io/stdio/feof.c new file mode 100644 index 0000000..ae7fb2b --- /dev/null +++ b/lib/libc/io/stdio/feof.c @@ -0,0 +1,6 @@ +#include "file.h" + +int feof(struct __opaque_file *stream) +{ + return (stream->f_flags & FILE_EOF) != 0; +} diff --git a/lib/libc/io/stdio/ferr.c b/lib/libc/io/stdio/ferr.c new file mode 100644 index 0000000..c69aa41 --- /dev/null +++ b/lib/libc/io/stdio/ferr.c @@ -0,0 +1,6 @@ +#include "file.h" + +int ferr(struct __opaque_file *stream) +{ + return (stream->f_flags & FILE_ERR) != 0; +} diff --git a/lib/libc/io/stdio/fgetc.c b/lib/libc/io/stdio/fgetc.c new file mode 100644 index 0000000..d36c518 --- /dev/null +++ b/lib/libc/io/stdio/fgetc.c @@ -0,0 +1,31 @@ +#include "file.h" + +#include +#include + +int fgetc(struct __opaque_file *stream) +{ + if (stream->f_flags & (FILE_EOF | FILE_ERR)) { + return EOF; + } + + size_t available = __libc_file_available(stream); + if (available == 0) { + int err = __libc_file_refill(stream); + if (err != SUCCESS) { + __set_errno(err); + stream->f_flags |= FILE_ERR; + return EOF; + } + + available = __libc_file_available(stream); + } + + if (available == 0) { + stream->f_flags |= FILE_EOF; + return EOF; + } + + char c = stream->f_buf[stream->f_buf_readptr++]; + return c; +} diff --git a/lib/libc/io/stdio/fgets.c b/lib/libc/io/stdio/fgets.c new file mode 100644 index 0000000..a484ed6 --- /dev/null +++ b/lib/libc/io/stdio/fgets.c @@ -0,0 +1,40 @@ +#include "file.h" + +#include + +char *fgets( + char *restrict str, + int count, + struct __opaque_file *restrict stream) +{ + if (stream->f_flags & (FILE_EOF | FILE_ERR)) { + return NULL; + } + + size_t i = 0; + while (1) { + int c = fgetc(stream); + + if (c == EOF) { + str[i] = 0; + break; + } + + str[i++] = c; + + if (c == '\n') { + str[i] = 0; + break; + } + } + + if (ferr(stream)) { + return NULL; + } + + if (feof(stream) && i == 0) { + return NULL; + } + + return str; +} diff --git a/lib/libc/io/stdio/file.c b/lib/libc/io/stdio/file.c new file mode 100644 index 0000000..ed7774c --- /dev/null +++ b/lib/libc/io/stdio/file.c @@ -0,0 +1,76 @@ +#include "file.h" + +#include +#include +#include +#include + +#define DEFAULT_BUFFER_SIZE 4096 + +#define FILE_UNUSABLE(f) (((f)->f_flags & (FILE_EOF | FILE_ERR)) != 0) + +int __libc_file_refill(struct __opaque_file *f) +{ + f->f_buf_datalen = 0; + f->f_buf_readptr = 0; + + if (f->f_fd < 0) { + return EBADF; + } + + if (f->f_flags & FILE_ERR) { + return EIO; + } + + if (f->f_flags & FILE_EOF) { + return SUCCESS; + } + + if (!f->f_buf) { + f->f_buf_max = DEFAULT_BUFFER_SIZE; + f->f_buf = malloc(f->f_buf_max); + if (!f->f_buf) { + f->f_buf_max = 0; + return ENOMEM; + } + } + + long r = read(f->f_fd, f->f_buf, f->f_buf_max); + if (r < 0) { + return -r; + } + + f->f_buf_datalen = r; + return SUCCESS; +} + +int __libc_file_read(struct __opaque_file *f, void *out, size_t count) +{ + if (f->f_fd < 0) { + return EBADF; + } + + if (f->f_flags & FILE_ERR) { + return EIO; + } + + if (f->f_flags & FILE_EOF) { + return SUCCESS; + } + + size_t available = f->f_buf_datalen - f->f_buf_readptr; + if (count > available) { + count = available; + } + + memcpy(out, f->f_buf + f->f_buf_readptr, count); + + f->f_buf_readptr += count; + + return count; +} + +size_t __libc_file_available(struct __opaque_file *f) +{ + return f->f_buf_datalen - f->f_buf_readptr; +} diff --git a/lib/libc/io/stdio/file.h b/lib/libc/io/stdio/file.h new file mode 100644 index 0000000..2d1c90d --- /dev/null +++ b/lib/libc/io/stdio/file.h @@ -0,0 +1,25 @@ +#ifndef _LIBC_IO_FILE_H_ +#define _LIBC_IO_FILE_H_ + +#include + +enum file_flags { + FILE_EOF = 0x01u, + FILE_ERR = 0x02u, + FILE_BIN = 0x04u, +}; + +struct __opaque_file { + enum file_flags f_flags; + int f_fd; + char f_unget; + char *f_buf; + size_t f_buf_datalen, f_buf_max; + size_t f_buf_readptr; +}; + +extern int __libc_file_refill(struct __opaque_file *f); +extern int __libc_file_read(struct __opaque_file *f, void *out, size_t count); +extern size_t __libc_file_available(struct __opaque_file *f); + +#endif diff --git a/lib/libc/io/stdio/fopen.c b/lib/libc/io/stdio/fopen.c new file mode 100644 index 0000000..9b059a1 --- /dev/null +++ b/lib/libc/io/stdio/fopen.c @@ -0,0 +1,151 @@ +#include "file.h" + +#include +#include +#include +#include +#include +#include + +enum mode { + MODE_R = 0x01u, + MODE_W = 0x02u, + MODE_A = 0x04u, + MODE_B = 0x08u, + MODE_X = 0x10u, + MODE_PLUS = 0x20u, +}; + +static int flags_from_mode( + const char *mode_string, + int *out_sysflags, + enum file_flags *out_fflags) +{ + enum mode mode = 0; + int sysflags = 0; + enum file_flags fflags = 0; + + for (int i = 0; mode_string[i]; i++) { + switch (mode_string[i]) { + case 'r': + if (mode & (MODE_R | MODE_W | MODE_A | MODE_X)) { + return -1; + } + + mode |= MODE_R; + break; + case 'w': + if (mode & (MODE_R | MODE_W | MODE_A)) { + return -1; + } + mode |= MODE_W; + break; + case 'a': + if (mode & (MODE_R | MODE_W | MODE_A | MODE_X)) { + return -1; + } + break; + case '+': + if (mode & MODE_PLUS) { + return -1; + } + mode |= MODE_PLUS; + break; + case 'b': + if (mode & MODE_B) { + return -1; + } + mode |= MODE_B; + break; + case 'x': + if (mode & (MODE_R | MODE_A | MODE_X)) { + return -1; + } + mode |= MODE_X; + break; + + default: + return -1; + } + } + + switch ((int)mode) { + case MODE_R | MODE_B: + fflags = FILE_BIN; + case MODE_R: + sysflags = O_RDONLY; + break; + + case MODE_W | MODE_B: + fflags = FILE_BIN; + case MODE_W: + sysflags = O_WRONLY | O_TRUNC | O_CREAT; + break; + + case MODE_A | MODE_B: + fflags = FILE_BIN; + case MODE_A: + sysflags = O_WRONLY | O_APPEND | O_CREAT; + break; + + case MODE_R | MODE_PLUS | MODE_B: + fflags = FILE_BIN; + case MODE_R | MODE_PLUS: + sysflags = O_RDWR; + break; + + case MODE_W | MODE_PLUS | MODE_B: + fflags = FILE_BIN; + case MODE_W | MODE_PLUS: + sysflags = O_RDWR | O_TRUNC | O_CREAT; + break; + + case MODE_W | MODE_PLUS | MODE_X | MODE_B: + fflags = FILE_BIN; + case MODE_W | MODE_PLUS | MODE_X: + sysflags = O_RDWR | O_TRUNC | O_CREAT | O_EXCL; + break; + + case MODE_A | MODE_PLUS | MODE_B: + fflags = FILE_BIN; + case MODE_A | MODE_PLUS: + sysflags = O_RDWR | O_APPEND | O_CREAT; + break; + + default: + return -1; + } + + return 0; +} + +struct __opaque_file *fopen(const char *path, const char *mode) +{ + int sysflags = 0; + enum file_flags fflags = 0; + if (flags_from_mode(mode, &sysflags, &fflags) != 0) { + __set_errno(EINVAL); + return NULL; + } + + struct __opaque_file *out = malloc(sizeof *out); + if (!out) { + __set_errno(ENOMEM); + return NULL; + } + + memset(out, 0x0, sizeof *out); + + int fd = open(path, sysflags); + if (fd < 0) { + free(out); + __set_errno(-fd); + return NULL; + } + + out->f_fd = fd; + out->f_flags = fflags; + __set_errno(SUCCESS); + + return out; +} diff --git a/lib/libc/io/stdio/fputc.c b/lib/libc/io/stdio/fputc.c new file mode 100644 index 0000000..e69de29 diff --git a/lib/libc/io/stdio/fputs.c b/lib/libc/io/stdio/fputs.c new file mode 100644 index 0000000..e69de29 diff --git a/lib/libc/io/stdio/fread.c b/lib/libc/io/stdio/fread.c new file mode 100644 index 0000000..e69de29 diff --git a/lib/libc/io/stdio/fwrite.c b/lib/libc/io/stdio/fwrite.c new file mode 100644 index 0000000..e69de29 diff --git a/lib/libc/io/stdio/opendir.c b/lib/libc/io/stdio/opendir.c new file mode 100644 index 0000000..fe151dc --- /dev/null +++ b/lib/libc/io/stdio/opendir.c @@ -0,0 +1,30 @@ +#include "dir.h" + +#include +#include +#include +#include +#include +#include + +extern struct __opaque_dir *opendir(const char *name) +{ + struct __opaque_dir *out = malloc(sizeof *out); + if (!out) { + __set_errno(ENOMEM); + return NULL; + } + + memset(out, 0x0, sizeof *out); + + int fd = open(name, O_RDONLY | O_DIRECTORY); + if (fd < 0) { + free(out); + return NULL; + } + + out->d_flags = 0; + out->d_fd = fd; + + return out; +} diff --git a/lib/libc/io/stdio/readdir.c b/lib/libc/io/stdio/readdir.c new file mode 100644 index 0000000..3b8ab79 --- /dev/null +++ b/lib/libc/io/stdio/readdir.c @@ -0,0 +1,35 @@ +#include "dir.h" + +#include +#include +#include +#include +#include +#include + +struct dirent *readdir(struct __opaque_dir *dirp) +{ + int result = __libc_dir_move_next(dirp); + if (result < 0) { + dirp->d_flags |= DIR_ERR; + __set_errno(-result); + return NULL; + } + + if (result > 0) { + return &dirp->d_current; + } + + result = __libc_dir_refill(dirp); + if (result < 0) { + dirp->d_flags |= DIR_ERR; + __set_errno(-result); + return NULL; + } + + if (!dirp->d_buf_datalen) { + return NULL; + } + + return &dirp->d_current; +}