sllibc/stdio.c

592 lines
15 KiB
C
Raw Normal View History

2025-06-04 01:22:53 +10:00
// Copyright (c) 2025, Zak Fenton
// Zak Fenton's libc is licensed under the Mulan PSL v2. You can use this
// software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN “AS IS” BASIS, WITHOUT warranties of
// any kind, either express or implied, including but not limited to
// non-infringement, merchantability or fit for a particular purpose.
// See the Mulan PSL v2 for more details.
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include "../kernel/fcntl.h"
#include "internal.h"
FILE* stdin;
FILE* stdout;
FILE* stderr;
// In the object-oriented version, we can just malloc from the class
// like it's a struct, as long as the class pointer is filled in!
#ifdef LIBC_OOP
#define FILE_internals _libc_FILE
#else
#define FILE_internals FILE
#endif
FILE* _libc_openfd(int fd) {
FILE_internals* result = malloc(sizeof(FILE_internals));
if (result) {
#ifdef LIBC_OOP
// Need to set the special class field as well as regular fields
#warning TODO class field
#else
// Nothing special to set, just the regular fields
#endif
result->fd = fd;
result->ungot = -1;
}
return (void*) result;
}
void _libc_stdio_init() {
stdin = _libc_openfd(0);
stdout = _libc_openfd(1);
stderr = _libc_openfd(2);
}
FILE* fopen(const char* name, const char* mode) {
int has_r = 0;
int has_w = 0;
int has_a = 0;
int has_b = 0;
int has_plus = 0;
printf("open mode: \"%s\"\n", mode);
for (char* m = mode; *m; m++) {
switch (*m) {
case 'r':
if (has_r || has_w || has_a) {
return NULL; // Invalid mode
}
has_r = 1;
break;
case 'w':
if (has_r || has_w || has_a) {
return NULL; // Invalid mode
}
has_w = 1;
break;
case 'a':
if (has_r || has_w || has_a) {
return NULL; // Invalid mode
}
has_a = 1;
break;
case 'b':
if (has_b) {
return NULL; // Invalid mode
}
has_b = 1;
break;
case '+':
if (has_plus) {
return NULL; // Invalid mode
}
has_plus = 1;
break;
default:
return NULL; // Invalid mode
}
}
int o_mode = 0;
if (has_r) {
if (has_plus) {
o_mode |= O_RDWR;
} else {
o_mode = O_RDONLY;
}
} else if (has_w) {
o_mode = O_WRONLY;
o_mode |= O_CREATE;
if (has_plus) {
o_mode |= O_TRUNC;
}
} else if (has_a) {
o_mode = O_WRONLY;
o_mode = O_APPEND;
if (has_plus) {
o_mode |= O_CREATE;
}
} else {
FATAL("This should be unreachable.");
}
FILE_internals* result = malloc(sizeof(FILE_internals));
if (!result) {
//printf("Malloc failed\n");
return NULL;
}
result->fd = open(name, o_mode);
if (result->fd <= 0) {
//printf("Open failed, used mode %d got %d\n", o_mode, result->fd);
free(result);
return NULL;
}
result->ungot = -1;
result->eof = 0;
return (FILE*)result;
}
FILE* freopen(const char* name, const char* mode, FILE* f) {
UNIMPLEMENTED();
}
int fclose(FILE* f) {
if (f == NULL) {
return -1;
}
close(((FILE_internals*)f)->fd);
free(f);
return 0; // TODO: Return -1 if it fails
}
int fflush(FILE* f) {
return 0; // TODO: This function needs to be implemented for buffered streams
}
int feof(FILE* f) {
if (f == NULL) {
return 1;
}
return f->eof;
}
size_t fread(void* buffer, size_t size, size_t nmemb, FILE* f) {
// TODO: See note in calloc() about possibly checking size calculation
char* charbuff = buffer;
size_t realsize = size*nmemb;
if (realsize > 0 && ((FILE_internals*)f)->ungot != -1) {
*charbuff++ = (char) ((FILE_internals*)f)->ungot;
((FILE_internals*)f)->ungot = -1;
realsize--;
}
size_t realresult = read(((FILE_internals*)f)->fd, charbuff, realsize);
if (realresult == realsize) {
return nmemb;
} else {
((FILE_internals*)f)->eof = 1;
return realresult / size;
}
}
size_t fwrite(void* buffer, size_t size, size_t nmemb, FILE* f) {
// TODO: See note in calloc() about possibly checking size calculation
size_t realsize = size*nmemb;
size_t realresult = write(((FILE_internals*)f)->fd, buffer, realsize);
if (realresult == realsize) {
return nmemb;
} else {
return realresult / size;
}
}
int getc(FILE* f) {
unsigned char foo;
if (fread(&foo, 1, 1, f) == 1) {
return (int) foo;
} else {
return EOF;
}
}
int fgetc(FILE* f) {
return getc(f);
}
int ungetc(int c, FILE* f) {
if (((FILE_internals*)f)->ungot == -1) {
return EOF;
} else {
((FILE_internals*)f)->ungot = (int)((unsigned char)c);
}
}
int putc(int c, FILE* f) {
unsigned char chbuf = (unsigned char) c;
if (fwrite(&chbuf, 1, 1, f) != 1) {
return EOF;
}
return chbuf;
}
int putchar(int c) {
return putc(c, stdout);
}
int fputc(int c, FILE* f) {
return putc(c, f);
}
char* fgets(char* str, int size, FILE* f) {
int i = 0;
int ch;
while (i < size-1) {
ch = getc(f);
//printf("Got character '%c'\n", ch);
if (ch == EOF) {
if (i == 0) {
return NULL; // This is important for a program to be able to detect EOF!
}
goto fgets_finished;
}
str[i++] = (char) ch;
if (ch == '\n') {
goto fgets_finished;
}
}
fgets_finished: // Write terminating zero at i and return
str[i] = 0;
return str;
}
int fputs(const char* str, FILE* f) {
int n = strlen(str);
if (fwrite(str, 1, n, f) != n) {
return EOF;
} else {
return n;
}
}
int _libc_dprintf_backend(const char* str, int n, void* udata) {
int fd = (int)((intptr_t)udata);
return write(fd, str, n);
}
int _libc_fprintf_backend(const char* str, int n, void* udata) {
FILE* f = udata;
return fwrite(str, 1, n, f);
}
int _libc_sprintf_backend(const char* str, int n, void* udata) {
char** strvar = udata;
char* s = *strvar;
for (int i = 0; i < n; i++) {
*s++ = str[i];
}
*s = 0;
*strvar = s;
return n;
}
struct _libc_snprintf_args {
char* str;
size_t i;
size_t sz;
};
int _libc_snprintf_backend(const char* str, int n, void* udata) {
struct _libc_snprintf_args* args = udata;
char* s = args->str;
for (int i = 0; i < n; i++) {
if (args->i >= args->sz-1) {
return i;
}
args->str[args->i] = str[i];
args->i++;
}
args->str[args->i+1] = 0;
return n;
}
int _LIBC_PRINTF_CALLCONV printf(const char* fmt, ...) {
void* udata = (void*)((uintptr_t)1); // stdout
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(&_libc_dprintf_backend, udata, fmt, varargs);
va_end(varargs);
return result;
}
int _LIBC_PRINTF_CALLCONV sprintf(char *buf, const char* fmt, ...) {
void* udata = &buf;
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(&_libc_sprintf_backend, udata, fmt, varargs);
va_end(varargs);
return result;
}
int _LIBC_PRINTF_CALLCONV snprintf(char *buf, size_t n, const char* fmt, ...) {
struct _libc_snprintf_args args;
args.str = buf;
args.sz = n;
args.i = 0;
void* udata = &args;
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(&_libc_snprintf_backend, udata, fmt, varargs);
va_end(varargs);
return result;
}
int _LIBC_PRINTF_CALLCONV dprintf(int fd, const char* fmt, ...) {
void* udata = (void*)((uintptr_t)fd);
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(&_libc_dprintf_backend, udata, fmt, varargs);
va_end(varargs);
return result;
}
int _LIBC_PRINTF_CALLCONV fprintf(FILE* f, const char* fmt, ...) {
void* udata = f;
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(&_libc_fprintf_backend, udata, fmt, varargs);
va_end(varargs);
return result;
}
int _LIBC_PRINTF_CALLCONV vfprintf(FILE* f, const char* fmt, va_list list) {
void* udata = f;
uintptr_t result = _libc_vfnprintf(&_libc_fprintf_backend, udata, fmt, list);
return result;
}
int _LIBC_PRINTF_CALLCONV vsnprintf(char* buf, size_t n, const char* fmt, va_list list) {
struct _libc_snprintf_args args;
args.str = buf;
args.sz = n;
args.i = 0;
void* udata = &args;
uintptr_t result = _libc_vfnprintf(&_libc_snprintf_backend, udata, fmt, list);
return result;
}
// These are non-standard, but the other *printf functions need to be implemented somehow,
// so fnprintf/vfnprintf just use a callback function for output of n bytes of string output.
int _LIBC_PRINTF_CALLCONV _libc_fnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, ...) {
va_list varargs;
va_start(varargs, fmt);
uintptr_t result = _libc_vfnprintf(fn, udata, fmt, varargs);
va_end(varargs);
return result;
}
#define DUMPTOFN() \
if (dayswithoutincident) { \
if (fn(f-dayswithoutincident, dayswithoutincident, udata) != dayswithoutincident) { \
return EOF; \
} \
written += dayswithoutincident; \
dayswithoutincident = 0; \
}
int _LIBC_PRINTF_CALLCONV _libc_vfnprintf(_libc_fnprintf_fn_t fn, void* udata, const char* fmt, va_list list) {
int dayswithoutincident = 0;
int written = 0;
const char* f = fmt;
int c;
while ((c = *f)) {
if (c == '%') {
DUMPTOFN();
int has_decimals = 0;
int decimals = 0;
nextctrl:
int tc = *(f+1);
int longcount = 0; // for counting %ld/%lld
switch (tc) {
case '%': {
f++;
dayswithoutincident++; // Just let the second '%' be printed with the next string part
} break;
case '.': {
has_decimals = 1;
f++;
} goto nextctrl;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
decimals = decimals * 10 + (tc - '0');
f++;
} goto nextctrl;
case 'f': {
char buf[20];
int i = 20;
double fval = va_arg(list, double);
//fval = -543.210987654321;
long ival = (long) fval;
int isneg = ival < 0;
unsigned long uval = isneg ? -ival : ival;
do {
buf[--i] = (char)('0' + uval % 10);
uval /= 10;
} while (uval > 0);
if (ival < 0) {
buf[--i] = '-';
}
int nwr = fn(buf+i, 20-i, udata);
if (nwr != 20-i) {
return EOF;
}
written += nwr;
if (!has_decimals) {
decimals = 6;
}
double x = fabs(fval - (double) ival);
if (fn(".", 1, udata) != 1) {
return EOF;
}
written++;
while (decimals > 0) {
x *= 10;
decimals--;
int ix = (int) x;
char xch = (char) ('0'+(ix%10));
if (fn(&xch, 1, udata) != 1) {
return EOF;
}
written++;
}
f++;
} break;
case 'S': { // Formatted substring, I thought there was a standard % for it
char* s = va_arg(list, char*);
if (!s) {
s = "(null)";
}
int len = strlen(s);
int nwr = _libc_vfnprintf(fn, udata, s, list);
if (nwr == EOF) {
return EOF;
}
written += nwr;
f++;
} break;
case 's': {
char* s = va_arg(list, char*);
if (!s) {
s = "(null)";
}
int len = strlen(s);
int nwr = fn(s, len, udata);
if (nwr != len) {
return EOF;
}
written += nwr;
f++;
} break;
case 'c': {
int chr = va_arg(list, int);
char chrc = (char) chr;
if (fn(&chrc, 1, udata) != 1) {
return EOF;
}
written++;
f++;
} break;
case 'l': // %ld/%lx is just handled as a special case of %d/%x
longcount++;
f++;
goto nextctrl;
case 'u': // Unsigned is just handled as a special case of %d
case 'd': {
// TODO: Testing/edge case for negative maximum?
char buf[20];
int i = 20;
if (longcount) {
long val = va_arg(list, long);
int isneg = ((tc == 'd') && (val < 0));
unsigned long uval = isneg ? -val : val;
// Not actually needed, unless you want a full string: buf[i--] = '0';
do {
buf[--i] = (char)('0' + uval % 10);
uval /= 10;
} while (uval > 0);
if (isneg) {
buf[--i] = '-';
}
} else {
int val = va_arg(list, int);
int isneg = ((tc == 'd') && (val < 0));
unsigned int uval = isneg ? -val : val;
// Not actually needed, unless you want a full string: buf[i--] = '0';
do {
buf[--i] = (char)('0' + uval % 10);
uval /= 10;
} while (uval > 0);
if (isneg) {
buf[--i] = '-';
}
}
int nwr = fn(buf+i, 20-i, udata);
if (nwr != 20-i) {
return EOF;
}
written += nwr;
f++;
} break;
case 'p': // Pointer is treated as %lx
longcount++;
case 'x':
case 'X': { // Hex is handled a separate case to %d because the loop is slightly slower and sign is never used
const char* digits = (tc == 'x') ? "0123456789abcdef" : "0123456789ABCDEF";
char buf[16]; // Size is easier to predict for hex, two characters of digits per byte
int i = 16;
if (longcount) {
unsigned long uval = va_arg(list, unsigned long);
// Not actually needed, unless you want a full string: buf[i--] = '0';
do {
buf[--i] = digits[uval % 16];
uval /= 16;
} while (uval > 0);
} else {
unsigned int uval = va_arg(list, unsigned int);
// Not actually needed, unless you want a full string: buf[i--] = '0';
do {
buf[--i] = digits[uval % 16];
uval /= 16;
} while (uval > 0);
}
int nwr = fn(buf+i, 16-i, udata);
if (nwr != 16-i) {
return EOF;
}
written += nwr;
f++;
} break;
default: { // For now just print %x??? when 'x' is unknown, but this is probably unsafe and an error should be reported properly instead (TODO)
// Also skip the argument (assume all arguments are word-sized for now)
void* _ignored = va_arg(list, void*);
if (fn(f, 2, udata) != 2) {
return EOF;
}
if (fn("???", 3, udata) != 3) {
return EOF;
}
written+=5;
f++;
}
}
} else {
dayswithoutincident++;
}
f++;
}
DUMPTOFN();
return written;
}
void perror(const char* errormsg) {
UNIMPLEMENTED();
}