// 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 #include #include #include #include #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(); }