Rewrite stdio functions.

These changes aim to make the stdio functions much more stable, flexible,
correct and bugfree.
This commit is contained in:
Jonas 'Sortie' Termansen 2012-12-08 21:14:29 +01:00
parent 8e0aefda20
commit 9ad7690c74
19 changed files with 277 additions and 74 deletions

View File

@ -37,6 +37,8 @@ fclose.o \
feof.o \ feof.o \
ferror.o \ ferror.o \
fflush.o \ fflush.o \
fflush_stop_reading.o \
fflush_stop_writing.o \
fgetc.o \ fgetc.o \
fgets.o \ fgets.o \
flbf.o \ flbf.o \

View File

@ -6,6 +6,7 @@
#define _FILE_LAST_WRITE (1<<2) #define _FILE_LAST_WRITE (1<<2)
#define _FILE_LAST_READ (1<<3) #define _FILE_LAST_READ (1<<3)
#define _FILE_AUTO_LOCK (1<<4) #define _FILE_AUTO_LOCK (1<<4)
#define _FILE_STREAM (1<<5)
#define _FILE_MAX_PUSHBACK 8 #define _FILE_MAX_PUSHBACK 8
typedef struct _FILE typedef struct _FILE
{ {
@ -14,7 +15,7 @@ typedef struct _FILE
to customize how it works. Don't call the functions directly, though, as to customize how it works. Don't call the functions directly, though, as
the standard library does various kinds of buffering and conversion. */ the standard library does various kinds of buffering and conversion. */
size_t buffersize; size_t buffersize;
char* buffer; unsigned char* buffer;
void* user; void* user;
size_t (*read_func)(void* ptr, size_t size, size_t nmemb, void* user); size_t (*read_func)(void* ptr, size_t size, size_t nmemb, void* user);
size_t (*write_func)(const void* ptr, size_t size, size_t nmemb, void* user); size_t (*write_func)(const void* ptr, size_t size, size_t nmemb, void* user);
@ -31,8 +32,8 @@ typedef struct _FILE
struct _FILE* prev; struct _FILE* prev;
struct _FILE* next; struct _FILE* next;
int flags; int flags;
size_t bufferused; size_t offset_input_buffer;
size_t numpushedback; size_t amount_input_buffered;
unsigned char pushedback[_FILE_MAX_PUSHBACK]; size_t amount_output_buffered;
} FILE; } FILE;
#endif #endif

View File

@ -29,6 +29,7 @@ extern "C" int fclose(FILE* fp)
int result = fflush(fp); int result = fflush(fp);
result |= fp->close_func ? fp->close_func(fp->user) : 0; result |= fp->close_func ? fp->close_func(fp->user) : 0;
funregister(fp); funregister(fp);
if ( fp->free_func ) { fp->free_func(fp); } if ( fp->free_func )
fp->free_func(fp);
return result; return result;
} }

View File

@ -57,6 +57,7 @@ static size_t fdio_read(void* ptr, size_t size, size_t nmemb, void* user)
ssize_t numbytes = read(fdio->fd, buf + sofar, total - sofar); ssize_t numbytes = read(fdio->fd, buf + sofar, total - sofar);
if ( numbytes < 0 ) { fdio->flags |= FDIO_ERROR; break; } if ( numbytes < 0 ) { fdio->flags |= FDIO_ERROR; break; }
if ( numbytes == 0 ) { fdio->flags |= FDIO_EOF; break; } if ( numbytes == 0 ) { fdio->flags |= FDIO_EOF; break; }
return numbytes / size;
sofar += numbytes; sofar += numbytes;
} }
return sofar / size; return sofar / size;
@ -163,6 +164,8 @@ int fdio_install(FILE* fp, const char* mode, int fd)
fp->error_func = fdio_error; fp->error_func = fdio_error;
fp->fileno_func = fdio_fileno; fp->fileno_func = fdio_fileno;
fp->close_func = fdio_close; fp->close_func = fdio_close;
if ( lseek(fd, 0, SEEK_CUR) < 0 && errno == ESPIPE )
fp->flags |= _FILE_STREAM;
return 1; return 1;
} }

View File

@ -26,7 +26,8 @@
extern "C" int feof(FILE* fp) extern "C" int feof(FILE* fp)
{ {
if ( fp->numpushedback ) size_t input_buffered = fp->amount_input_buffered - fp->offset_input_buffer;
if ( input_buffered )
return 0; return 0;
if ( fp->eof_func ) if ( fp->eof_func )
return fp->eof_func(fp->user); return fp->eof_func(fp->user);

View File

@ -33,10 +33,12 @@ extern "C" int fflush(FILE* fp)
for ( fp = _firstfile; fp; fp = fp->next ) { result |= fflush(fp); } for ( fp = _firstfile; fp; fp = fp->next ) { result |= fflush(fp); }
return result; return result;
} }
if ( !fp->write_func ) { errno = EBADF; return EOF; }
if ( !fp->bufferused ) { return 0; } int mode = fp->flags & (_FILE_LAST_READ | _FILE_LAST_WRITE);
size_t written = fp->write_func(fp->buffer, 1, fp->bufferused, fp->user); if ( (mode & _FILE_LAST_READ) && fflush_stop_reading(fp) == EOF )
if ( written < fp->bufferused ) { return EOF; } return EOF;
fp->bufferused = 0; if ( (mode & _FILE_LAST_WRITE) && fflush_stop_writing(fp) == EOF )
return EOF;
fp->flags |= mode;
return 0; return 0;
} }

View File

@ -0,0 +1,58 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
fflush_stop_reading.cpp
Resets the FILE to a consistent state so it is ready for writing.
*******************************************************************************/
#include <sys/types.h>
#include <assert.h>
#include <stdio.h>
extern "C" int fflush_stop_reading(FILE* fp)
{
if ( !(fp->flags & _FILE_LAST_READ) )
return 0;
int ret = 0;
size_t bufferahead = fp->amount_input_buffered - fp->offset_input_buffer;
if ( (fp->flags & _FILE_STREAM) )
{
if ( bufferahead )
/* TODO: Data loss!*/{}
}
if ( !(fp->flags & _FILE_STREAM) )
{
off_t rewind_amount = -((off_t) bufferahead);
off_t my_pos = fp->tell_func(fp->user);
off_t expected_pos = my_pos + rewind_amount;
#if 1
if ( fp->seek_func && fp->seek_func(fp->user, expected_pos, SEEK_SET) != 0 )
#else
if ( fp->seek_func && fp->seek_func(fp->user, rewind_amount, SEEK_CUR) != 0 )
#endif
ret = EOF;
off_t newpos = fp->tell_func(fp->user);
assert(ret == EOF || expected_pos == newpos);
}
fp->amount_input_buffered = fp->offset_input_buffer = 0;
fp->flags &= ~_FILE_LAST_READ;
return ret;
}

View File

@ -0,0 +1,42 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
fflush_stop_writing.cpp
Resets the FILE to a consistent state so it is ready for reading.
*******************************************************************************/
#include <errno.h>
#include <stdio.h>
extern "C" int fflush_stop_writing(FILE* fp)
{
if ( !(fp->flags & _FILE_LAST_WRITE) )
return 0;
if ( !fp->write_func )
return errno = EBADF, EOF;
size_t size = sizeof(unsigned char);
size_t count = fp->amount_output_buffered;
int ret = 0;
if ( fp->write_func(fp->buffer, size, count, fp->user) != count )
ret = EOF; // TODO: Set errno!
fp->amount_output_buffered = 0;
fp->flags &= ~_FILE_LAST_WRITE;
return ret;
}

View File

@ -22,11 +22,48 @@
*******************************************************************************/ *******************************************************************************/
#include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
extern "C" int fgetc(FILE* fp) extern "C" int fgetc(FILE* fp)
{ {
unsigned char c; if ( fp->flags & _FILE_NO_BUFFER )
if ( fread(&c, 1, sizeof(c), fp) < sizeof(c) ) { return EOF; } {
return c; unsigned char c;
if ( fread(&c, sizeof(c), 1, fp) != 1 )
return EOF;
return c;
}
if ( !fp->read_func )
return EOF; // TODO: ferror doesn't report error!
if ( fp->flags & _FILE_LAST_WRITE )
fflush_stop_writing(fp);
fp->flags |= _FILE_LAST_READ;
if ( fp->offset_input_buffer < fp->amount_input_buffered )
retry:
return fp->buffer[fp->offset_input_buffer++];
assert(fp->buffer && fp->buffersize);
size_t pushback = _FILE_MAX_PUSHBACK;
if ( fp->buffersize <= pushback )
pushback = 0;
size_t count = fp->buffersize - pushback;
size_t size = sizeof(unsigned char);
size_t numread = fp->read_func(fp->buffer + pushback, size, count, fp->user);
if ( !numread )
return EOF;
fp->offset_input_buffer = pushback;
fp->amount_input_buffered = pushback + numread;
goto retry;
} }

View File

@ -36,9 +36,12 @@ extern "C" FILE* fnewfile(void)
FILE* fp = (FILE*) calloc(sizeof(FILE), 1); FILE* fp = (FILE*) calloc(sizeof(FILE), 1);
if ( !fp ) { return NULL; } if ( !fp ) { return NULL; }
fp->buffersize = BUFSIZ; fp->buffersize = BUFSIZ;
fp->buffer = (char*) malloc(fp->buffersize); fp->buffer = (unsigned char*) malloc(fp->buffersize);
if ( !fp->buffer ) { free(fp); return NULL; } if ( !fp->buffer ) { free(fp); return NULL; }
fp->flags = _FILE_AUTO_LOCK; fp->flags = _FILE_AUTO_LOCK;
fp->offset_input_buffer = 0;
fp->amount_input_buffered = 0;
fp->amount_output_buffered = 0;
fp->free_func = ffreefile; fp->free_func = ffreefile;
fregister(fp); fregister(fp);
return fp; return fp;

View File

@ -26,5 +26,5 @@
extern "C" size_t fpending(FILE* fp) extern "C" size_t fpending(FILE* fp)
{ {
return fp->bufferused; return fp->amount_output_buffered;
} }

View File

@ -26,5 +26,7 @@
extern "C" void fpurge(FILE* fp) extern "C" void fpurge(FILE* fp)
{ {
fp->bufferused = 0; fp->offset_input_buffer = 0;
fp->amount_input_buffered = 0;
fp->amount_output_buffered = 0;
} }

View File

@ -24,9 +24,29 @@
#include <stdio.h> #include <stdio.h>
extern "C" int fputc(int cint, FILE* fp) extern "C" int fputc(int c, FILE* fp)
{ {
unsigned char c = (unsigned char) cint; if ( fp->flags & _FILE_NO_BUFFER )
if ( fwrite(&c, 1, sizeof(c), fp) < sizeof(c) ) { return EOF; } {
unsigned char c_char = c;
if ( fwrite(&c_char, sizeof(c_char), 1, fp) != 1 )
return EOF;
return c;
}
if ( !fp->write_func )
return EOF; // TODO: ferror doesn't report error!
if ( fp->flags & _FILE_LAST_READ )
fflush_stop_reading(fp);
fp->flags |= _FILE_LAST_WRITE;
if ( fp->amount_output_buffered == fp->buffersize && fflush(fp) != 0 )
return EOF;
fp->buffer[fp->amount_output_buffered++] = c;
if ( c == '\n' && fflush(fp) != 0 )
return EOF;
return c; return c;
} }

View File

@ -23,23 +23,32 @@
*******************************************************************************/ *******************************************************************************/
#include <stdio.h> #include <stdio.h>
#include <errno.h>
extern "C" size_t fread(void* ptr, size_t size, size_t nmemb, FILE* fp) extern "C" size_t fread(void* ptr, size_t size, size_t nmemb, FILE* fp)
{ {
if ( fp->numpushedback && size != 1 ) { errno = ENOSYS; return 0; } if ( fp->flags & _FILE_NO_BUFFER )
if ( fp->numpushedback && nmemb )
{ {
unsigned char* buf = (unsigned char*) ptr; if ( !fp->read_func )
size_t amount = nmemb < fp->numpushedback ? nmemb : fp->numpushedback; return 0; // TODO: ferror doesn't report error!
for ( size_t i = 0; i < amount; i++ ) if ( fp->flags & _FILE_LAST_WRITE )
{ fflush_stop_writing(fp);
buf[i] = fp->pushedback[--(fp->numpushedback)]; fp->flags |= _FILE_LAST_READ;
} return fp->read_func(ptr, size, nmemb, fp->user);
if ( nmemb <= amount ) { return nmemb; }
return amount + fread(buf + amount, size, nmemb - amount, fp);
} }
if ( !fp->read_func ) { errno = EBADF; return 0; }
fp->flags &= ~_FILE_LAST_WRITE; fp->flags |= _FILE_LAST_READ; unsigned char* buf = (unsigned char*) ptr;
return fp->read_func(ptr, size, nmemb, fp->user); for ( size_t n = 0; n < nmemb; n++ )
{
size_t offset = n * size;
for ( size_t i = 0; i < size; i++ )
{
int c = fgetc(fp);
if ( c == EOF )
return n;
size_t index = i + offset;
buf[index] = c;
}
}
return nmemb;
} }

View File

@ -22,11 +22,15 @@
*******************************************************************************/ *******************************************************************************/
#include <errno.h>
#include <stdio.h> #include <stdio.h>
extern "C" int fseeko(FILE* fp, off_t offset, int whence) extern "C" int fseeko(FILE* fp, off_t offset, int whence)
{ {
fp->numpushedback = 0; if ( fflush(fp) != 0 )
fflush(fp); return -1;
return (fp->seek_func) ? fp->seek_func(fp->user, offset, whence) : 0; if ( !fp->seek_func )
return errno = EBADF, -1;
int ret = fp->seek_func(fp->user, offset, whence);
return ret;
} }

View File

@ -22,11 +22,22 @@
*******************************************************************************/ *******************************************************************************/
#include <stdio.h> #include <sys/types.h>
#include <errno.h> #include <errno.h>
#include <stdio.h>
extern "C" off_t ftello(FILE* fp) extern "C" off_t ftello(FILE* fp)
{ {
if ( !fp->tell_func ) { errno = EBADF; return -1; } if ( !fp->tell_func )
return fp->tell_func(fp->user) - fp->numpushedback; return errno = EBADF, -1;
off_t offset = fp->tell_func(fp->user);
if ( offset < 0 )
return -1;
off_t readahead = fp->amount_input_buffered - fp->offset_input_buffer;
off_t writebehind = fp->amount_output_buffered;
off_t result = offset - readahead + writebehind;
if ( result < 0 ) // Too much ungetc'ing.
return 0;
return result;
} }

View File

@ -22,43 +22,31 @@
*******************************************************************************/ *******************************************************************************/
#include <assert.h>
#include <stdio.h> #include <stdio.h>
#include <errno.h>
extern "C" size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* fp) extern "C" size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* fp)
{ {
if ( !fp->write_func ) if ( fp->flags & _FILE_NO_BUFFER )
return errno = EBADF, 0;
fp->flags &= ~_FILE_LAST_READ; fp->flags |= _FILE_LAST_WRITE;
char* str = (char*) ptr;
size_t total = size * nmemb;
size_t sofar = 0;
while ( sofar < total )
{ {
size_t left = total - sofar; if ( !fp->write_func )
if ( fp->flags & _FILE_NO_BUFFER || !fp->buffersize ) return 0; // TODO: ferror doesn't report error!
{ if ( fp->flags & _FILE_LAST_READ )
size_t ret = sofar + fp->write_func(str + sofar, 1, left, fp->user); fflush_stop_reading(fp);
return ret; fp->flags |= _FILE_LAST_WRITE;
} return fp->write_func(ptr, size, nmemb, fp->user);
}
size_t available = fp->buffersize - fp->bufferused; const unsigned char* buf = (const unsigned char*) ptr;
if ( !available ) for ( size_t n = 0; n < nmemb; n++ )
{
size_t offset = n * size;
for ( size_t i = 0; i < size; i++ )
{ {
if ( fflush(fp) == 0 ) continue; size_t index = offset + i;
else return sofar; if ( fputc(buf[index], fp) == EOF )
} return n;
size_t count = available < left ? available : left;
for ( size_t i = 0; i < count; i++ )
{
char c = str[sofar++];
fp->buffer[fp->bufferused++] = c;
assert(fp->bufferused <= fp->buffersize);
if ( c == '\n' || fp->buffersize == fp->bufferused )
break;
} }
} }
return sofar;
return nmemb;
} }

View File

@ -162,6 +162,8 @@ extern char* tempnam(const char* dir, const char* pfx);
#define fpending __fpending #define fpending __fpending
#define flushlbf _flushlbf #define flushlbf _flushlbf
#define fsetlocking __fsetlocking #define fsetlocking __fsetlocking
int fflush_stop_reading(FILE* fp);
int fflush_stop_writing(FILE* fp);
void fseterr(FILE* fp); void fseterr(FILE* fp);
void fregister(FILE* fp); void fregister(FILE* fp);
void funregister(FILE* fp); void funregister(FILE* fp);

View File

@ -25,11 +25,28 @@
#include <stdio.h> #include <stdio.h>
#include <errno.h> #include <errno.h>
#include <string.h>
extern "C" int ungetc(int c, FILE* fp) extern "C" int ungetc(int c, FILE* fp)
{ {
if ( fp->numpushedback == _FILE_MAX_PUSHBACK ) { errno = ERANGE; return EOF; } if ( !fp->read_func || (fp->flags & _FILE_NO_BUFFER) )
unsigned char uc = c; return EOF;
fp->pushedback[fp->numpushedback++] = uc;
return uc; if ( fp->flags & _FILE_LAST_WRITE )
fflush_stop_writing(fp);
fp->flags |= _FILE_LAST_READ;
if ( fp->offset_input_buffer == 0 )
{
size_t amount = fp->amount_input_buffered - fp->offset_input_buffer;
size_t offset = fp->buffersize - amount;
if ( !offset )
return EOF;
memmove(fp->buffer + offset, fp->buffer, sizeof(fp->buffer[0]) * amount);
fp->offset_input_buffer = offset;
fp->amount_input_buffered = offset + amount;
}
fp->buffer[--fp->offset_input_buffer] = c;
return c;
} }