From 92da3542a613c8ac3ac433a51ca445561ceae825 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Wed, 15 Mar 2023 16:47:34 +0000 Subject: [PATCH] Move low-level fopencookie()/funopen() functionality to Io API. The Stream API now provides the buffered I/O functionality analogous to the C standard library. --- src/Io.c | 230 ++++++++++++++++++++++++++ src/Stream.c | 379 ++++++++++++++++++++++++++++++------------- src/include/Io.h | 46 ++++++ src/include/Stream.h | 42 +---- 4 files changed, 546 insertions(+), 151 deletions(-) create mode 100644 src/Io.c create mode 100644 src/include/Io.h diff --git a/src/Io.c b/src/Io.c new file mode 100644 index 0000000..c261e5e --- /dev/null +++ b/src/Io.c @@ -0,0 +1,230 @@ +#include + +#include + +#include +#include + +#ifndef IO_PRINTF_BUFFER +#define IO_PRINTF_BUFFER 1024 +#endif + +struct Io +{ + IoFunctions io; + void *cookie; +}; + +Io * +IoCreate(void *cookie, IoFunctions funcs) +{ + Io *io; + + /* Must have at least read or write */ + if (!funcs.read && !funcs.write) + { + return NULL; + } + + io = Malloc(sizeof(Io)); + + if (!io) + { + return NULL; + } + + io->cookie = cookie; + + io->io.read = funcs.read; + io->io.write = funcs.write; + io->io.seek = funcs.seek; + io->io.close = funcs.close; + + return io; +} + +ssize_t +IoRead(Io *io, void *buf, size_t nBytes) +{ + if (!io || !io->io.read) + { + errno = EBADF; + return -1; + } + + return io->io.read(io->cookie, buf, nBytes); +} + +ssize_t +IoWrite(Io *io, void *buf, size_t nBytes) +{ + if (!io || !io->io.write) + { + errno = EBADF; + return -1; + } + + return io->io.write(io->cookie, buf, nBytes); +} + +off_t +IoSeek(Io *io, off_t offset, int whence) +{ + if (!io) + { + errno = EBADF; + return -1; + } + + if (!io->io.seek) + { + errno = EINVAL; + return -1; + } + + return io->io.seek(io->cookie, offset, whence); +} + +int +IoClose(Io *io) +{ + int ret; + + if (!io) + { + errno = EBADF; + return -1; + } + + if (io->io.close) + { + ret = io->io.close(io->cookie); + } + else + { + ret = 0; + } + + Free(io); + + return ret; +} + +static ssize_t +IoReadFd(void *cookie, void *buf, size_t nBytes) +{ + int fd = *((int *) cookie); + + return read(fd, buf, nBytes); +} + +static ssize_t +IoWriteFd(void *cookie, void *buf, size_t nBytes) +{ + int fd = *((int *) cookie); + + return write(fd, buf, nBytes); +} + +static off_t +IoSeekFd(void *cookie, off_t offset, int whence) +{ + int fd = *((int *) cookie); + + return lseek(fd, offset, whence); +} + +static int +IoCloseFd(void *cookie) +{ + int fd = *((int *) cookie); + + Free(cookie); + return close(fd); +} + +Io * +IoOpen(int fd) +{ + int *cookie = Malloc(sizeof(int)); + IoFunctions f; + + if (!cookie) + { + return NULL; + } + + *cookie = fd; + + f.read = IoReadFd; + f.write = IoWriteFd; + f.seek = IoSeekFd; + f.close = IoCloseFd; + + return IoCreate(cookie, f); +} + +int +IoVprintf(Io *io, const char *fmt, va_list ap) +{ + char *buf; + size_t write; + + int ret; + + if (!io || !fmt) + { + return -1; + } + + buf = Malloc(IO_PRINTF_BUFFER); + if (!buf) + { + return -1; + } + + write = vsnprintf(buf, IO_PRINTF_BUFFER, fmt, ap); + + if (write < 0) + { + Free(buf); + return write; + } + + /* Number of bytes to write exceeded buffer size; this should + * be rare, but may occasionally happen. If it does, realloc to + * the correct size and try again. + */ + if (write >= IO_PRINTF_BUFFER) + { + char *new = Realloc(buf, write + 1); + if (!new) + { + Free(buf); + return -1; + } + + buf = new; + + /* This time we don't care about the return value */ + vsnprintf(buf, write, fmt, ap); + } + + ret = IoWrite(io, buf, write); + + Free(buf); + return ret; +} + +int +IoPrintf(Io *io, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = IoVprintf(io, fmt, ap); + va_end(ap); + + return ret; +} diff --git a/src/Stream.c b/src/Stream.c index d6afbb8..491f21e 100644 --- a/src/Stream.c +++ b/src/Stream.c @@ -1,170 +1,319 @@ #include +#ifndef STREAM_BUFFER +#define STREAM_BUFFER 4096 +#endif + +#define STREAM_EOF (1 << 0) +#define STREAM_ERR (1 << 1) + +#include #include +#include #include +#include struct Stream { - StreamFunctions io; - void *cookie; + Io *io; - int *ub; - size_t ubSize; - size_t ubLen; + int *rBuf; + size_t rLen; + size_t rOff; + + int *wBuf; + size_t wLen; + + int *ugBuf; + size_t ugSize; + size_t ugLen; + + int flags : 2; }; Stream * -StreamCreate(void *cookie, StreamFunctions funcs) +StreamOpen(Io * io) { Stream *stream; - if (!funcs.read || !funcs.write) + if (!io) { return NULL; } stream = Malloc(sizeof(Stream)); - if (!stream) { return NULL; } - stream->cookie = cookie; - - stream->io.read = funcs.read; - stream->io.write = funcs.write; - stream->io.seek = funcs.seek; - stream->io.close = funcs.close; - - stream->ubSize = 0; + memset(stream, 0, sizeof(Stream)); + stream->io = io; return stream; } -ssize_t -StreamRead(Stream *stream, void *buf, size_t nBytes) -{ - if (!stream) - { - errno = EBADF; - return -1; - } - - return stream->io.read(stream->cookie, buf, nBytes); -} - -ssize_t -StreamWrite(Stream *stream, void *buf, size_t nBytes) -{ - if (!stream) - { - errno = EBADF; - return -1; - } - - return stream->io.write(stream->cookie, buf, nBytes); -} - -off_t -StreamSeek(Stream *stream, off_t offset, int whence) -{ - if (!stream) - { - errno = EBADF; - return -1; - } - - if (!stream->io.seek) - { - errno = EINVAL; - return -1; - } - - return stream->io.seek(stream->cookie, offset, whence); -} - int StreamClose(Stream *stream) { - int ret; + int ret = 0; if (!stream) { errno = EBADF; - return -1; + return EOF; } - if (stream->io.close) + if (stream->rBuf) { - ret = stream->io.close(stream->cookie); - } - else - { - ret = 0; + Free(stream->rBuf); } - if (stream->ubSize) + if (stream->wBuf) { - Free(stream->ub); + ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen); + Free(stream->wBuf); + + if (writeRes == -1) + { + ret = EOF; + } } + if (stream->ugBuf) + { + Free(stream->ugBuf); + } + + ret = IoClose(stream->io); Free(stream); return ret; } -static ssize_t -StreamReadFd(void *cookie, void *buf, size_t nBytes) +int +StreamVprintf(Stream * stream, const char *fmt, va_list ap) { - int fd = *((int *) cookie); - - return read(fd, buf, nBytes); -} - -static ssize_t -StreamWriteFd(void *cookie, void *buf, size_t nBytes) -{ - int fd = *((int *) cookie); - - return write(fd, buf, nBytes); -} - -static off_t -StreamSeekFd(void *cookie, off_t offset, int whence) -{ - int fd = *((int *) cookie); - - return lseek(fd, offset, whence); -} - -static int -StreamCloseFd(void *cookie) -{ - int fd = *((int *) cookie); - - return close(fd); -} - -Stream * -StreamOpen(int fd) -{ - int *fdp = Malloc(sizeof(int)); - StreamFunctions f; - - if (!fdp) + if (!stream) { - return NULL; + return -1; } - *fdp = fd; - - f.read = StreamReadFd; - f.write = StreamWriteFd; - f.seek = StreamSeekFd; - f.close = StreamCloseFd; - - return StreamCreate(fdp, f); + StreamFlush(stream); /* Flush the buffer out before doing the printf */ + /* Defer printf to underlying Io. We probably should buffer the + * printf operation just like StreamPutc() so we don't have to + * flush the buffer. + */ + return IoVprintf(stream->io, fmt, ap); +} + +int +StreamPrintf(Stream * stream, const char *fmt,...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = StreamVprintf(stream, fmt, ap); + va_end(ap); + + return ret; +} + +int +StreamGetc(Stream * stream) +{ + int c; + + if (!stream) + { + errno = EBADF; + return EOF; + } + + /* Empty the ungetc stack first */ + if (stream->ugLen) + { + c = stream->ugBuf[stream->ugLen - 1]; + stream->ugLen--; + return c; + } + + if (stream->flags & EOF) + { + return EOF; + } + + if (!stream->rBuf) + { + /* No buffer allocated yet */ + stream->rBuf = Malloc(STREAM_BUFFER * sizeof(int)); + if (!stream->rBuf) + { + stream->flags |= STREAM_ERR; + return EOF; + } + + stream->rOff = 0; + stream->rLen = 0; + } + + if (stream->rOff >= stream->rLen) + { + /* We read through the entire buffer; get a new one */ + ssize_t readRes = IoRead(stream->io, stream->rBuf, STREAM_BUFFER); + + if (readRes == 0) + { + stream->flags |= STREAM_EOF; + return EOF; + } + + if (readRes == -1) + { + stream->flags |= STREAM_ERR; + return EOF; + } + + stream->rOff = 0; + stream->rLen = readRes; + } + + /* Read the character in the buffer and advance the offset */ + c = stream->rBuf[stream->rOff]; + stream->rOff++; + + return c; +} + +int +StreamUngetc(Stream * stream, int c) +{ + if (!stream) + { + errno = EBADF; + return EOF; + } + + if (!stream->ugBuf) + { + stream->ugSize = STREAM_BUFFER; + stream->ugBuf = Malloc(stream->ugSize); + + if (!stream->ugBuf) + { + stream->flags |= STREAM_ERR; + return EOF; + } + } + + if (stream->ugLen >= stream->ugSize) + { + int *new; + + stream->ugSize += STREAM_BUFFER; + new = Realloc(stream->ugBuf, stream->ugSize); + if (!new) + { + stream->flags |= STREAM_ERR; + Free(stream->ugBuf); + stream->ugBuf = NULL; + return EOF; + } + + Free(stream->ugBuf); + stream->ugBuf = new; + } + + stream->ugBuf[stream->ugLen - 1] = c; + stream->ugLen++; + + return c; +} + +int +StreamPutc(Stream * stream, int c) +{ + if (!stream) + { + errno = EBADF; + return EOF; + } + + if (!stream->wBuf) + { + stream->wBuf = Malloc(STREAM_BUFFER * sizeof(int)); + if (!stream->wBuf) + { + stream->flags |= STREAM_ERR; + return EOF; + } + } + + if (stream->wLen == STREAM_BUFFER) + { + /* Buffer full; write it */ + ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen); + + if (writeRes == -1) + { + stream->flags |= STREAM_ERR; + return EOF; + } + + stream->wLen = 0; + } + + stream->wBuf[stream->wLen] = c; + stream->wLen++; + + return c; +} + +int +StreamEof(Stream * stream) +{ + return stream && (stream->flags & STREAM_EOF); +} + +int +StreamError(Stream * stream) +{ + return stream && (stream->flags & STREAM_ERR); +} + +void +StreamClearError(Stream * stream) +{ + if (stream) + { + stream->flags &= ~STREAM_ERR; + } +} + +int +StreamFlush(Stream * stream) +{ + if (!stream) + { + errno = EBADF; + return EOF; + } + + if (stream->wLen) + { + ssize_t writeRes = IoWrite(stream->io, stream->wBuf, stream->wLen); + + if (writeRes == -1) + { + stream->flags |= STREAM_ERR; + return EOF; + } + + stream->wLen = 0; + } + + return 0; } diff --git a/src/include/Io.h b/src/include/Io.h new file mode 100644 index 0000000..7d822a4 --- /dev/null +++ b/src/include/Io.h @@ -0,0 +1,46 @@ +#ifndef TELODENDRIA_IO_H +#define TELODENDRIA_IO_H + +#include +#include + +typedef struct Io Io; + +typedef ssize_t (IoReadFunc) (void *, void *, size_t); +typedef ssize_t (IoWriteFunc) (void *, void *, size_t); +typedef off_t (IoSeekFunc) (void *, off_t, int); +typedef int (IoCloseFunc) (void *); + +typedef struct IoFunctions +{ + IoReadFunc *read; + IoWriteFunc *write; + IoSeekFunc *seek; + IoCloseFunc *close; +} IoFunctions; + +extern Io * +IoCreate(void *, IoFunctions); + +extern ssize_t +IoRead(Io *, void *, size_t); + +extern ssize_t +IoWrite(Io *, void *, size_t); + +extern off_t +IoSeek(Io *, off_t, int); + +extern int +IoClose(Io *); + +extern Io * +IoOpen(int); + +extern int +IoVprintf(Io *, const char *, va_list); + +extern int +IoPrintf(Io *, const char *, ...); + +#endif /* TELODENDRIA_IO_H */ diff --git a/src/include/Stream.h b/src/include/Stream.h index 3a14ab8..d3cfbac 100644 --- a/src/include/Stream.h +++ b/src/include/Stream.h @@ -1,51 +1,18 @@ #ifndef TELODENDRIA_STREAM_H #define TELODENDRIA_STREAM_H -#include +#include + #include typedef struct Stream Stream; -/* - * Low-level Stream API in the style of POSIX system calls. - * Heavily inspired by GNU fopencookie() and BSD funopen(). - */ - -typedef ssize_t (StreamReadFunc) (void *, void *, size_t); -typedef ssize_t (StreamWriteFunc) (void *, void *, size_t); -typedef off_t (StreamSeekFunc) (void *, off_t, int); -typedef int (StreamCloseFunc) (void *); - -typedef struct StreamFunctions -{ - StreamReadFunc *read; - StreamWriteFunc *write; - StreamSeekFunc *seek; - StreamCloseFunc *close; -} StreamFunctions; - extern Stream * -StreamCreate(void *, StreamFunctions); - -extern ssize_t -StreamRead(Stream *, void *, size_t); - -extern ssize_t -StreamWrite(Stream *, void *, size_t); - -extern off_t -StreamSeek(Stream *, off_t, int); +StreamOpen(Io *io); extern int StreamClose(Stream *); -extern Stream * -StreamOpen(int); - -/* - * High level Stream API in the style of C standard I/O. - */ - extern int StreamVprintf(Stream *, const char *, va_list); @@ -58,6 +25,9 @@ StreamGetc(Stream *); extern int StreamUngetc(Stream *, int); +extern int +StreamPutc(Stream *, int); + extern int StreamEof(Stream *);