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.
This commit is contained in:
Jordan Bancino 2023-03-15 16:47:34 +00:00
parent 5dbaf3c223
commit 92da3542a6
4 changed files with 546 additions and 151 deletions

230
src/Io.c Normal file
View file

@ -0,0 +1,230 @@
#include <Io.h>
#include <Memory.h>
#include <errno.h>
#include <stdio.h>
#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;
}

View file

@ -1,170 +1,319 @@
#include <Stream.h>
#ifndef STREAM_BUFFER
#define STREAM_BUFFER 4096
#endif
#define STREAM_EOF (1 << 0)
#define STREAM_ERR (1 << 1)
#include <Io.h>
#include <Memory.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
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;
}

46
src/include/Io.h Normal file
View file

@ -0,0 +1,46 @@
#ifndef TELODENDRIA_IO_H
#define TELODENDRIA_IO_H
#include <unistd.h>
#include <stdarg.h>
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 */

View file

@ -1,51 +1,18 @@
#ifndef TELODENDRIA_STREAM_H
#define TELODENDRIA_STREAM_H
#include <unistd.h>
#include <Io.h>
#include <stdarg.h>
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 *);