/* * file.c — *** PROVISIONAL *** minimal unbuffered FILE * implementation * on top of POSIX-style fd I/O * (open/read/write/lseek/close). * * ============================================================ * TODO (v2): replace with a proper BUFFERED implementation. * See docs/TODO.md / Solid-C reference layout: * * typedef struct { * uint flags; // +0..1 file status flags * int level; // +2..3 empty/fill level of buffer * char *curp; // +4..5 current active pointer * int fd; // +6..7 underlying low-level fd * char *buffer; // +8..9 data transfer buffer * char hold; // +10 ungetc byte if no buffer * short token; // +11..12 reserved * char dummy; // +13 reserved * } FILE; * * The current implementation maps each fputc/fgetc to one read/write * syscall — fine for correctness checks, awful for throughput. Issues * 3/4/5 from the stdio-review (fwrite short-write flag, fgets n=1, * mode_to_flags break) are deferred until that rewrite. * ============================================================ * * stdin/stdout/stderr are STATIC sentinel FILEs flagged with * _F_CONIN/_F_CONOUT; fputc/fgetc detect them and call putchar() / * getchar() which already handle CR/LF translation and ESTEX calls. * * Their fd fields are 0 / -1 / -2. Negative values were chosen because * ESTEX OPEN can return small positive fds (1, 2, …) for ordinary * files — if we marked stdout/stderr with fd=1/2 a real file could * collide with their identifier. fd=0 for stdin is kept (POSIX-style) * because ESTEX does not return 0. Even so, none of these fd fields * is ever passed to a syscall — the _F_CONIN/_F_CONOUT flags drive * the dispatch. */ #include #include #include #include #include #include /* ---- console pseudo-streams ----------------------------------------*/ static FILE _stdin = { 0, _F_READ | _F_CONIN }; static FILE _stdout = { -1, _F_WRITE | _F_CONOUT }; static FILE _stderr = { -2, _F_WRITE | _F_CONOUT }; FILE *const stdin = &_stdin; FILE *const stdout = &_stdout; FILE *const stderr = &_stderr; /* ---- fopen / fclose -------------------------------------------------*/ /* Translate a fopen() mode string to the open() flags subset our * libc/io/open.c understands. Supported: r, w, a, with optional "+" * and trailing "b" (binary — we ignore as all I/O is binary). */ static int mode_to_flags(const char *mode, uint8_t *file_flags) { if (!mode || !*mode) { errno = EINVAL; return -1; } int oflags = 0; uint8_t ff = 0; char base = *mode; int plus = 0; for (const char *p = mode + 1; *p; p++) { if (*p == '+') plus = 1; /* 'b' and 't' are ignored — all I/O is binary on Sprinter. */ } switch (base) { case 'r': oflags = plus ? O_RDWR : O_RDONLY; ff = _F_READ | (plus ? _F_WRITE : 0); break; case 'w': oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; ff = _F_WRITE | (plus ? _F_READ : 0); break; case 'a': oflags = (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND; ff = _F_WRITE | _F_APPEND | (plus ? _F_READ : 0); break; default: errno = EINVAL; return -1; } *file_flags = ff; return oflags; } FILE *fopen(const char *path, const char *mode) { uint8_t ff; int oflags = mode_to_flags(mode, &ff); if (oflags < 0) return NULL; int fd = open(path, oflags); if (fd < 0) return NULL; FILE *fp = (FILE *)malloc(sizeof(FILE)); if (!fp) { int saved = errno; close(fd); errno = saved ? saved : ENOMEM; return NULL; } fp->fd = fd; fp->flags = ff; return fp; } int fclose(FILE *fp) { if (!fp) { errno = EBADF; return EOF; } /* Don't close stdin/stdout/stderr. */ if (fp == &_stdin || fp == &_stdout || fp == &_stderr) { return 0; } int r = close(fp->fd); free(fp); return r < 0 ? EOF : 0; } int fflush(FILE *fp) { /* Unbuffered — nothing to flush. */ (void)fp; return 0; } /* ---- char-at-a-time -------------------------------------------------*/ int fputc(int c, FILE *fp) { if (!fp) { errno = EBADF; return EOF; } if (fp->flags & _F_CONOUT) { return putchar(c); } if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; } uint8_t ch = (uint8_t)c; if (write(fp->fd, &ch, 1) != 1) { fp->flags |= _F_ERROR; return EOF; } return (int)ch; } int fgetc(FILE *fp) { if (!fp) { errno = EBADF; return EOF; } if (fp->flags & _F_CONIN) { return getchar(); } if (!(fp->flags & _F_READ)) { errno = EBADF; return EOF; } uint8_t ch; int r = read(fp->fd, &ch, 1); if (r == 0) { fp->flags |= _F_EOF; return EOF; } if (r < 0) { fp->flags |= _F_ERROR; return EOF; } return (int)ch; } /* ---- string-at-a-time ----------------------------------------------*/ int fputs(const char *s, FILE *fp) { if (!fp || !s) { errno = EBADF; return EOF; } if (fp->flags & _F_CONOUT) { while (*s) { if (putchar((unsigned char)*s++) == EOF) return EOF; } return 0; } if (!(fp->flags & _F_WRITE)) { errno = EBADF; return EOF; } size_t n = strlen(s); int w = write(fp->fd, s, (uint16_t)n); if (w < 0 || (size_t)w != n) { fp->flags |= _F_ERROR; return EOF; } return 0; } char *fgets(char *buf, int n, FILE *fp) { if (!buf || n < 2 || !fp) return NULL; int i = 0; while (i < n - 1) { int c = fgetc(fp); if (c == EOF) { if (i == 0) return NULL; break; } buf[i++] = (char)c; if (c == '\n') break; } buf[i] = '\0'; return buf; } /* ---- block-at-a-time -----------------------------------------------*/ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) { if (!ptr || !fp) return 0; if (size == 0 || nmemb == 0) return 0; if (fp->flags & _F_CONIN) { /* line-buffered console read — not very useful but functional. */ char *p = (char *)ptr; size_t total = size * nmemb; for (size_t i = 0; i < total; i++) { int c = getchar(); if (c == EOF) return i / size; p[i] = (char)c; } return nmemb; } if (!(fp->flags & _F_READ)) { errno = EBADF; return 0; } size_t total = size * nmemb; int r = read(fp->fd, ptr, (uint16_t)total); if (r < 0) { fp->flags |= _F_ERROR; return 0; } if ((size_t)r < total) fp->flags |= _F_EOF; return (size_t)r / size; } size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp) { if (!ptr || !fp) return 0; if (size == 0 || nmemb == 0) return 0; if (fp->flags & _F_CONOUT) { const char *p = (const char *)ptr; size_t total = size * nmemb; for (size_t i = 0; i < total; i++) { if (putchar((unsigned char)p[i]) == EOF) return i / size; } return nmemb; } if (!(fp->flags & _F_WRITE)) { errno = EBADF; return 0; } size_t total = size * nmemb; int w = write(fp->fd, ptr, (uint16_t)total); if (w < 0) { fp->flags |= _F_ERROR; return 0; } return (size_t)w / size; } /* ---- positioning ---------------------------------------------------*/ int fseek(FILE *fp, long off, int whence) { if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) { errno = EBADF; return -1; } long r = lseek(fp->fd, off, whence); if (r < 0) return -1; fp->flags &= (uint8_t)~_F_EOF; return 0; } long ftell(FILE *fp) { if (!fp || (fp->flags & (_F_CONIN | _F_CONOUT))) { errno = EBADF; return -1L; } return lseek(fp->fd, 0L, SEEK_CUR); } void rewind(FILE *fp) { if (!fp) return; fseek(fp, 0L, SEEK_SET); fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR); } /* ---- status --------------------------------------------------------*/ int feof (FILE *fp) { return fp && (fp->flags & _F_EOF) ? 1 : 0; } int ferror(FILE *fp) { return fp && (fp->flags & _F_ERROR) ? 1 : 0; } void clearerr(FILE *fp) { if (fp) fp->flags &= (uint8_t)~(_F_EOF | _F_ERROR); }