diff --git a/CMakeLists.txt b/CMakeLists.txt index 1627252..c8d5b33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,9 +120,13 @@ set(SOURCES # stdio src/libc/stdio/asprintf.c src/libc/stdio/dprintf.c + src/libc/stdio/fclose.c + src/libc/stdio/fdopen.c src/libc/stdio/fflush.c src/libc/stdio/fileutil.c + src/libc/stdio/fopen.c src/libc/stdio/fprintf.c + src/libc/stdio/freopen.c src/libc/stdio/printf.c src/libc/stdio/printf/format_fixed.c src/libc/stdio/printf/format_fp.c diff --git a/STATUS b/STATUS index 5f0f449..919b6dc 100644 --- a/STATUS +++ b/STATUS @@ -84,18 +84,21 @@ TEST: Function/symbol/macro needs to be tested 7.18 => GCC 7.19 - 7.19.1 Introduction TEST - No wide-oriented streams (see notes) + 7.19.1 Introduction TEST (no wide-oriented streams *) + 7.19.4.1 remove TEST 7.19.4.2 rename TODO 7.19.4.3 tmpfile TODO 7.19.4.4 tmpnam TODO - 7.19.5.1 fclose TODO - 7.19.5.2 fflush TODO - 7.19.5.3 fopen TODO + + 7.19.5.1 fclose TEST + 7.19.5.2 fflush TEST + 7.19.5.3 fopen TEST + (EXT) fdopen TEST 7.19.5.4 freopen TODO - 7.19.5.5 setbuf TODO - 7.19.5.6 setvbuf TODO + 7.19.5.5 setbuf TEST + 7.19.5.6 setvbuf TEST + 7.19.6.1 fprintf LDEPS(fwrite) 7.19.6.2 fscanf TODO 7.19.6.3 printf LDEPS(fwrite, stdout) @@ -114,10 +117,32 @@ TEST: Function/symbol/macro needs to be tested (EXT) vasprintf - (EXT) dprintf TEST (EXT) vdprintf TEST - 7.19.7 Character input/output TODO - 7.19.8 Direct input/output TODO - 7.19.9 File positioning TODO - 7.19.10 Error-handling TODO + + 7.19.7.1 fgetc TODO + 7.19.7.2 fgets TODO + 7.19.7.3 fputc TODO + 7.19.7.4 fputs TODO + 7.19.7.5 getc TODO + 7.19.7.6 getchar TODO + 7.19.7.7 gets TODO + 7.19.7.8 putc TODO + 7.19.7.9 putchar TODO + 7.19.7.10 puts TODO + 7.19.7.11 ungetc TODO + + 7.19.8.1 fread TODO + 7.19.8.2 fwrite TODO + + 7.19.9.1 fgetpos TODO + 7.19.9.2 fseek TODO + 7.19.9.3 fsetpos TODO + 7.19.9.4 ftell TODO + 7.19.9.5 rewind TODO + + 7.19.10.1 clearerr TODO + 7.19.10.2 feof TODO + 7.19.10.3 ferror TODO + 7.19.10.4 perror TODO 7.20 7.20 MB_CUR_MAX TODO diff --git a/include/stdio.h b/include/stdio.h index 729029e..0630100 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -66,9 +66,26 @@ extern int remove(char const *__filename); ** File access functions. */ +/* Flush the stream and disassociate it from the underlying file. */ +extern int fclose(FILE *__fp); + /* Flush any written data in the FILE's internal buffer. */ extern int fflush(FILE *__fp); +/* Open a file and associate a stream with it. */ +extern FILE *fopen( + char const * __restrict__ __filename, + char const * __restrict__ __mode); + +/* Open a file descriptor and associate a stream with it. */ +extern FILE *fdopen(int __fd, char const *__mode); + +/* Reopen a stream with another file, or change its mode. */ +extern FILE *freopen( + char const * __restrict__ __filename, + char const * __restrict__ __mode, + FILE * __restrict__ __fp); + /* Use __buf as a buffer (of size BUFSIZ) for access to __fp. */ extern void setbuf(FILE * __restrict__ __fp, char * __restrict__ __buf); diff --git a/include/target/gint/bits/types/FILE.h b/include/target/gint/bits/types/FILE.h index b147e46..8d047e3 100644 --- a/include/target/gint/bits/types/FILE.h +++ b/include/target/gint/bits/types/FILE.h @@ -39,7 +39,20 @@ In writing mode, the region [0..bufpos) contains data received through API but not yet written to the file descriptor. ftell() reports fdpos + bufpos. - The rest of the buffer is undefined. */ + The rest of the buffer is undefined. + + Many fields in the FILE structure are abstracted away by API calls in layers: + + 1. [fd], [fdpos] and [error] are updated by the primitive functions of + fileutil.c which essentially wrap kernel I/O (plus clearerr() obviously). + 2. [buf], [bufsize], [bufmode] and [bufowned] are handled by setbuf(), + setvbuf() and fclose(). + 3. [bufpos], [bufread] and [bufdir] are set by primitive read/write functions + like fgets() or fwrite(), and cleared by fflush(). + 4. [readable], [writable], [append] and [text] are set by fopen() and + freopen(), and used as read-only by the primitive functions of 3. + + TODO: EOF indicator */ typedef struct { /* File descriptor */ int fd; diff --git a/src/libc/stdio/fclose.c b/src/libc/stdio/fclose.c new file mode 100644 index 0000000..8be26ef --- /dev/null +++ b/src/libc/stdio/fclose.c @@ -0,0 +1,10 @@ +#include +#include +#include +#include "fileutil.h" + +int fclose(FILE *fp) +{ + __fp_close(fp, true); + return 0; +} diff --git a/src/libc/stdio/fdopen.c b/src/libc/stdio/fdopen.c new file mode 100644 index 0000000..ca4f675 --- /dev/null +++ b/src/libc/stdio/fdopen.c @@ -0,0 +1,24 @@ +#include +#include +#include "fileutil.h" + +FILE *fdopen(int fd, char const *mode) +{ + FILE *fp = calloc(1, sizeof *fp); + if(!fp) goto err; + + int flags = __fp_parse_mode(mode, fp); + if(flags < 0) goto err; + + __fp_open(fp, fd, true); + + /* TODO: fdopen(): Seek to the current file offset of the fd */ + + return fp; + +err: + if(fp && fp->bufowned) + free(fp->buf); + free(fp); + return NULL; +} diff --git a/src/libc/stdio/fflush.c b/src/libc/stdio/fflush.c index 9e1cf08..d6cb4b9 100644 --- a/src/libc/stdio/fflush.c +++ b/src/libc/stdio/fflush.c @@ -13,21 +13,21 @@ int fflush(FILE *fp) if(!fp->buf) return 0; - int rc = 0; - /* In reading mode, reset the file offset */ if(fp->bufmode == __FILE_BUF_READ && fp->bufpos < fp->bufread) { fp->fdpos = fp->fdpos - fp->bufread + fp->bufpos; lseek(fp->fd, fp->fdpos, SEEK_SET); fp->bufpos = 0; fp->bufread = 0; + return 0; } /* In writing mode, write pending data */ - else if(fp->bufmode == __FILE_BUF_WRITE && fp->bufpos > 0) { - rc = __fp_write(fp, fp->buf, fp->bufpos); + if(fp->bufmode == __FILE_BUF_WRITE && fp->bufpos > 0) { + ssize_t written = __fp_write(fp, fp->buf, fp->bufpos); fp->bufpos = 0; + return (written == (ssize_t)fp->bufpos ? 0 : EOF); } - return rc; + return 0; } diff --git a/src/libc/stdio/fileutil.c b/src/libc/stdio/fileutil.c index a8662a3..6338ac6 100644 --- a/src/libc/stdio/fileutil.c +++ b/src/libc/stdio/fileutil.c @@ -1,19 +1,107 @@ #include "fileutil.h" #include +#include +#include -int __fp_write(FILE *fp, void const *data, size_t size) +int __fp_open(FILE *fp, int fd, bool use_buffering) { - if(!fp) - return EOF; + /* We initialize fdpos to 0 even in append mode (7.19.3§1) */ + fp->fd = fd; + fp->fdpos = 0; - ssize_t rc = write(fp->fd, data, size); + /* We assume all files in the filesystem are non-interactive in order + to conform to (7.19.5.3§7) + TODO: Vhex might want something more elaborate here */ + if(use_buffering && setvbuf(fp, NULL, _IOFBF, BUFSIZ)) + return -1; - if(rc < 0) { - fp->error = 1; - return EOF; - } - else { - fp->fdpos += rc; - return 0; - } + return 0; +} + +void __fp_close(FILE *fp, bool free_fp) +{ + fflush(fp); + close(fp->fd); + if(fp->bufowned) + free(fp->buf); + if(free_fp) + free(fp); +} + +ssize_t __fp_read(FILE *fp, void *data, size_t size) +{ + size_t read_ = 0; + + while(read_ < size) { + ssize_t rc = read(fp->fd, data + read_, size - read_); + + if(rc < 0) { + fp->error = 1; + return EOF; + } + if(rc == 0) + break; + + fp->fdpos += rc; + } + + return read_; +} + +ssize_t __fp_write(FILE *fp, void const *data, size_t size) +{ + size_t written = 0; + + while(written < size) { + ssize_t rc = write(fp->fd, data + written, size - written); + + if(rc < 0) { + fp->error = 1; + return EOF; + } + if(rc == 0) + break; + + fp->fdpos += rc; + } + + return written; +} + +int __fp_parse_mode(char const *mode, FILE *fp) +{ + int base = 0; + bool binary = false; + bool update = false; + + for(int i = 0; mode[i]; i++) { + if(mode[i] == 'r' || mode[i] == 'w' || mode[i] == 'a') { + if(base) goto err; + base = mode[i]; + } + else if(mode[i] == 'b') + binary = true; + else if(mode[i] == '+') + update = true; + } + if(!base) goto err; + + if(fp) { + fp->readable = (base == 'r' || update); + fp->writable = (base == 'w' || base == 'a' || update); + fp->append = (base == 'a'); + fp->text = !binary; + } + + if(base == 'r') + return (update ? O_RDWR : O_RDONLY); + if(base == 'w') + return (update ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; + if(base == 'a') + return (update ? O_RDWR : O_WRONLY) | O_CREAT; + /* Fallthrough */ + +err: + errno = EINVAL; + return -1; } diff --git a/src/libc/stdio/fileutil.h b/src/libc/stdio/fileutil.h index a008676..a010921 100644 --- a/src/libc/stdio/fileutil.h +++ b/src/libc/stdio/fileutil.h @@ -6,10 +6,27 @@ extern "C" { #endif #include +#include +#include + +/* Open a file descriptor in a pre-allocated FILE. */ +int __fp_open(FILE *fp, int fd, bool use_buffering); + +/* Close fp and free all of its resources. */ +void __fp_close(FILE *fp, bool free_fp); + +/* Reads data from a file descriptor; updates the fdpos and sets the error + indicator. Returns 0 on success, EOF on error. */ +ssize_t __fp_read(FILE *fp, void *data, size_t size); /* Write data to a file descriptor; updates the fdpos and sets the error indicator. Returns 0 on success, EOF on error. */ -int __fp_write(FILE *fp, void const *data, size_t size); +ssize_t __fp_write(FILE *fp, void const *data, size_t size); + +/* Parse mode bits. Returns corresponding open() flags if successful, -1 if the + mode is invalid. If [fp != NULL], also sets the fields of [fp]. Sets + errno = EINVAL in case of error. */ +int __fp_parse_mode(char const *mode, FILE *fp); #ifdef __cplusplus } diff --git a/src/libc/stdio/fopen.c b/src/libc/stdio/fopen.c new file mode 100644 index 0000000..469ac3a --- /dev/null +++ b/src/libc/stdio/fopen.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include "fileutil.h" + +FILE *fopen(char const * restrict filename, char const * restrict mode) +{ + FILE *fp = calloc(1, sizeof *fp); + if(!fp) goto err; + + int flags = __fp_parse_mode(mode, fp); + if(flags < 0) goto err; + + int fd = open(filename, flags, 0755); + if(fd < 0) goto err; + + __fp_open(fp, fd, true); + return fp; + +err: + if(fp && fp->bufowned) + free(fp->buf); + free(fp); + return NULL; +} diff --git a/src/libc/stdio/freopen.c b/src/libc/stdio/freopen.c new file mode 100644 index 0000000..6255c83 --- /dev/null +++ b/src/libc/stdio/freopen.c @@ -0,0 +1,27 @@ +#include +#include +#include +#include +#include "fileutil.h" + +FILE *freopen(char const * restrict filename, char const * restrict mode, + FILE * restrict fp) +{ + __fp_close(fp, false); + memset(fp, 0, sizeof *fp); + + int flags = __fp_parse_mode(mode, fp); + if(flags < 0) goto err; + + int fd = open(filename, flags, 0755); + if(fd < 0) goto err; + + __fp_open(fp, fd, true); + return fp; + +err: + if(fp && fp->bufowned) + free(fp->buf); + free(fp); + return NULL; +}