Simplification of uio Stream functions. No more internal seeks.

Taking advantage of the POSIX/SUSv2/C requirement that input and output
operations may not immediately follow eachother.



git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@2631 8092fc87-c524-0410-9efc-e669fe64eaf9
This commit is contained in:
Meep-Eep
2006-12-20 20:15:43 +00:00
parent 9cb412b41f
commit 97626d2ab5
4 changed files with 187 additions and 274 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
Changes towards version 0.7:
- None yet
- Simplification of uio Stream functions. No more internal seeks. - SvdB
Changes towards version 0.6:
- Fixed a bug where an input delay was used for non-network games - SvdB
+3 -15
View File
@@ -1,4 +1,4 @@
Needed before use in UQM:
Needed for use in UQM:
- documentation
- configuring for GLOB
- when doing uio_getStdioPhysical(), if write access is required, but
@@ -16,7 +16,6 @@ Documentation:
the physical structure is kept up to date when an entry is
removed or added.
- stream stuff is not thread safe
writing using stream functions is not implemented.
- uio_fflush() does not accept NULL as argument to flush all streams,
like stdio fflush() does.
- uio_close() does never fail
@@ -35,10 +34,9 @@ Documentation:
- You can use mount stuff from other repositories.
Bugs:
- uio_Stream file might get unaligned (not sure).
- 'openDir(repository, "dir/")' will have the trailing '/'
in the dirHandle, which will cause problems.
- 'openDirRelative(repository, "/")' causes segfaults lateron
- 'openDirRelative(repository, "/")' causes segfaults later on
- uio_rename() doesn't work on directories
uio_getPhysicalAccess() needs to be changed so that it works the same on
dirs as on files. stat() can be cleaned up too then.
@@ -53,18 +51,8 @@ Bugs:
issue. SIZE_MAX can be used to check for sizeof(size_t) at runtime.
- remove() is probably more compatible than unlink(). remove() is part
of the C standard (as well as POSIX), unlink() is just POSIX.
- seeking in files opened as "text" on Windows goes wrong. As uiostream
seeks itself, this can happen when there are no explicit seeks
by the user of uio. Are the seeks really necessary anyhow?
- seeking in files opened as "text" on Windows goes wrong.
- Network paths on Windows are not accepted.
- From the C standard: "[...] output shall not be directly followed by input
without an intervening call to the fflush function or to a file
positioning function (fseek, fsetpos, or rewind), and input shall not be
directly followed by output without an intervening call to a file
positioning function, unless the input operation encounters end-of- file."
This means some code in uiostream can be simplified, and a whole lot of
seeks will never be necessary. (It will not even be nessessary to keep
track of the current location in the file).
- No CRLF translation (and ^Z recognition) is done for files read from
zip files, even though that may be expected on Windows.
+165 -224
View File
@@ -31,17 +31,21 @@
#endif
#define uio_Stream_BLOCK_SIZE 1024
// The alignment of file reads as well as the buffer size.
#define uio_Stream_BLOCK_PAGE_START(offset) \
((offset) & ~(uio_Stream_BLOCK_SIZE - 1))
static inline uio_Stream *uio_Stream_new(uio_Handle *handle, int openFlags);
static inline void uio_Stream_delete(uio_Stream *stream);
static inline uio_Stream *uio_Stream_alloc(void);
static inline void uio_Stream_free(uio_Stream *stream);
#ifdef NDEBUG
# define uio_assertReadSanity(stream)
# define uio_assertWriteSanity(stream)
#else
static void uio_assertReadSanity(uio_Stream *stream);
static void uio_assertWriteSanity(uio_Stream *stream);
#endif
static int uio_Stream_fillReadBuffer(uio_Stream *stream);
static int uio_Stream_alignReadBuffer(uio_Stream *stream);
static int uio_Stream_flushWriteBuffer(uio_Stream *stream);
static void uio_Stream_discardReadBuffer(uio_Stream *stream);
uio_Stream *
@@ -108,18 +112,18 @@ uio_fopen(uio_DirHandle *dir, const char *path, const char *mode) {
int
uio_fclose(uio_Stream *stream) {
if (stream->writeStart != NULL)
if (stream->operation == uio_StreamOperation_write)
uio_Stream_flushWriteBuffer(stream);
uio_close(stream->handle);
uio_Stream_delete(stream);
return 0;
}
// If less than nmembs could be read, or an error occurs, the file pointer
// is undefined. clearerr() followed by fseek() need to be called before
// attempting to read or write again.
// I don't have the C standard myself, but I suspect this is the
// official behaviour for fread() and fwrite().
// "The file position indicator for the stream (if defined) is advanced by
// the number of characters successfully read. If an error occurs, the
// resulting value of the file position indicator for the stream is
// indeterminate. If a partial element is read, its value is
// indeterminate." (from POSIX for fread()).
size_t
uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
size_t bytesToRead;
@@ -128,21 +132,17 @@ uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
bytesToRead = size * nmemb;
bytesRead = 0;
assert((stream->openFlags & O_ACCMODE) != O_WRONLY);
if (stream->writeStart != NULL) {
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
return -1;
}
}
if (stream->readEnd - stream->bufPtr > 0) {
uio_assertReadSanity(stream);
stream->operation = uio_StreamOperation_read;
if (stream->dataEnd > stream->dataStart) {
// First use what's in the buffer.
size_t numRead;
numRead = minu(stream->readEnd - stream->bufPtr, bytesToRead);
memcpy(buf, stream->bufPtr, numRead);
numRead = minu(stream->dataEnd - stream->dataStart, bytesToRead);
memcpy(buf, stream->dataStart, numRead);
buf = (void *) ((char *) buf + numRead);
stream->bufPtr += numRead;
stream->dataStart += numRead;
bytesToRead -= numRead;
bytesRead += numRead;
}
@@ -152,6 +152,7 @@ uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
}
{
// Read the rest directly into the caller's buffer.
ssize_t numRead;
numRead = uio_read(stream->handle, buf, bytesToRead);
if (numRead == -1) {
@@ -159,13 +160,12 @@ uio_fread(void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
goto out;
}
bytesRead += numRead;
stream->seekLow += numRead;
if ((size_t) numRead < bytesToRead) {
// End of file
stream->status = uio_Stream_STATUS_EOF;
stream->operation = uio_StreamOperation_none;
goto out;
}
uio_Stream_alignReadBuffer(stream);
}
out:
@@ -179,14 +179,8 @@ uio_fgets(char *s, int size, uio_Stream *stream) {
int orgSize;
char *buf;
assert((stream->openFlags & O_ACCMODE) != O_WRONLY);
if (stream->writeStart != NULL) {
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
return NULL;
}
}
uio_assertReadSanity(stream);
stream->operation = uio_StreamOperation_read;
size--;
orgSize = size;
@@ -196,15 +190,16 @@ uio_fgets(char *s, int size, uio_Stream *stream) {
const char *newLinePos;
// Fill buffer if empty.
if (stream->bufPtr >= stream->readEnd) {
if (stream->dataStart == stream->dataEnd) {
if (uio_Stream_fillReadBuffer(stream) == -1) {
// errno is set
stream->status = uio_Stream_STATUS_ERROR;
return NULL;
}
if (stream->bufPtr == stream->readEnd) {
if (stream->dataStart == stream->dataEnd) {
// End-of-file
stream->status = uio_Stream_STATUS_EOF;
stream->operation = uio_StreamOperation_none;
if (size == orgSize) {
// Nothing was read.
return NULL;
@@ -212,22 +207,21 @@ uio_fgets(char *s, int size, uio_Stream *stream) {
break;
}
}
assert (stream->bufPtr < stream->readEnd);
// Search in buffer
maxRead = minu(stream->readEnd - stream->bufPtr, size);
newLinePos = memchr(stream->bufPtr, '\n', maxRead);
maxRead = minu(stream->dataEnd - stream->dataStart, size);
newLinePos = memchr(stream->dataStart, '\n', maxRead);
if (newLinePos != NULL) {
// Newline found.
maxRead = newLinePos + 1 - stream->bufPtr;
memcpy(buf, stream->bufPtr, maxRead);
stream->bufPtr += maxRead;
maxRead = newLinePos + 1 - stream->dataStart;
memcpy(buf, stream->dataStart, maxRead);
stream->dataStart += maxRead;
buf[maxRead] = '\0';
return buf;
}
// No newline present.
memcpy(buf, stream->bufPtr, maxRead);
stream->bufPtr += maxRead;
memcpy(buf, stream->dataStart, maxRead);
stream->dataStart += maxRead;
buf += maxRead;
size -= maxRead;
}
@@ -240,30 +234,25 @@ int
uio_fgetc(uio_Stream *stream) {
int result;
assert((stream->openFlags & O_ACCMODE) != O_WRONLY);
uio_assertReadSanity(stream);
stream->operation = uio_StreamOperation_read;
if (stream->writeStart != NULL) {
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
return -1;
}
}
if (stream->bufPtr >= stream->readEnd) {
if (stream->dataStart == stream->dataEnd) {
// Buffer is empty
if (uio_Stream_fillReadBuffer(stream) == -1) {
stream->status = uio_Stream_STATUS_ERROR;
return (int) EOF;
}
if (stream->bufPtr == stream->readEnd) {
if (stream->dataStart == stream->dataEnd) {
// End-of-file
stream->status = uio_Stream_STATUS_EOF;
stream->operation = uio_StreamOperation_none;
return (int) EOF;
}
}
assert(stream->bufPtr < stream->readEnd);
result = (int) *((unsigned char *) stream->bufPtr);
stream->bufPtr++;
result = (int) *((unsigned char *) stream->dataStart);
stream->dataStart++;
return result;
}
@@ -283,18 +272,20 @@ uio_fputc(int c, uio_Stream *stream) {
assert((stream->openFlags & O_ACCMODE) != O_RDONLY);
assert(c >= 0 && c <= 255);
if (stream->writeStart == NULL)
stream->writeStart = stream->bufPtr;
if (stream->bufPtr == stream->bufEnd) {
uio_assertWriteSanity(stream);
stream->operation = uio_StreamOperation_write;
if (stream->dataEnd == stream->bufEnd) {
// The buffer is full. Flush it out.
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
// Error status (for ferror()) is set.
return EOF;
}
}
*(unsigned char *) stream->bufPtr = (unsigned char) c;
stream->bufPtr++;
*(unsigned char *) stream->dataEnd = (unsigned char) c;
stream->dataEnd++;
return c;
}
@@ -312,78 +303,46 @@ int
uio_fseek(uio_Stream *stream, long offset, int whence) {
int newPos;
if (stream->writeStart != NULL) {
if (stream->operation == uio_StreamOperation_read) {
uio_Stream_discardReadBuffer(stream);
} else if (stream->operation == uio_StreamOperation_write) {
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
return -1;
}
}
assert(stream->dataStart == stream->buf);
assert(stream->dataEnd == stream->buf);
stream->operation = uio_StreamOperation_none;
assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END);
switch(whence) {
case SEEK_SET:
break;
case SEEK_CUR:
offset += stream->seekLow - (stream->readEnd - stream->bufPtr);
break;
case SEEK_END: {
struct stat statBuf;
if (uio_fstat(stream->handle, &statBuf) == -1) {
// errno is set
return -1;
}
offset += statBuf.st_size;
break;
}
}
// TODO: when implementing pushback: throw away pushback buffer.
// Maybe the new location is still inside the read buffer.
// If not, we must throw the buffer away.
// If the buffer was polluted from calls to ungetc(),
// always throw away the buffer.
if (offset >= stream->seekLow - (stream->readEnd - stream->buf) &&
offset <= stream->seekLow) {
// The buffer can be reused.
stream->status = uio_Stream_STATUS_OK;
stream->bufPtr = stream->buf +
(stream->readEnd - stream->buf) +
(offset - stream->seekLow);
return 0;
}
// The read buffer is not reusable.
newPos = uio_lseek(stream->handle, offset, SEEK_SET);
newPos = uio_lseek(stream->handle, offset, whence);
if (newPos == -1) {
// errno is set
return -1;
}
stream->seekLow = newPos;
stream->status = uio_Stream_STATUS_OK;
// Clear error or end-of-file flag.
stream->bufPtr = stream->buf;
stream->readEnd = stream->buf;
if ((stream->openFlags & O_ACCMODE) == O_RDONLY) {
// We're done if there's no writing involved.
return 0;
}
if (uio_Stream_alignReadBuffer(stream) == -1) {
// Even when the buffer can't be filled, it might be
// a suitable location for writing (for instance, at the end
// of the file).
return 0;
}
return 0;
}
long
uio_ftell(uio_Stream *stream) {
return (long) stream->seekLow - (stream->readEnd - stream->bufPtr);
off_t newPos;
newPos = uio_lseek(stream->handle, 0, SEEK_CUR);
if (newPos == (off_t) -1) {
// errno is set
return (long) -1;
}
if (stream->operation == uio_StreamOperation_write) {
newPos += stream->dataEnd - stream->dataStart;
} else if (stream->operation == uio_StreamOperation_read) {
newPos -= stream->dataEnd - stream->dataStart;
}
return (long) newPos;
}
// If less that nmemb elements could be written, or an error occurs, the
@@ -396,69 +355,45 @@ uio_fwrite(const void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
ssize_t bytesToWrite;
ssize_t bytesWritten;
assert((stream->openFlags & O_ACCMODE) != O_RDONLY);
uio_assertWriteSanity(stream);
// NB. If a file is opened in append mode, the file position indicator
// is moved to the end of the file before writing.
// We leave that up to the physical layer.
bytesToWrite = size * nmemb;
if (stream->writeStart == NULL)
stream->writeStart = stream->bufPtr;
if (bytesToWrite < stream->bufEnd - stream->bufPtr) {
if (bytesToWrite < stream->bufEnd - stream->dataEnd) {
// There's enough space in the write buffer to store everything.
memcpy(stream->bufPtr, buf, bytesToWrite);
stream->bufPtr += bytesToWrite;
memcpy(stream->dataEnd, buf, bytesToWrite);
stream->dataEnd += bytesToWrite;
return nmemb;
}
// Not enough space in the write buffer to write everything.
// Flush what's left in the write buffer, and then directly write 'buf'.
// Flush what's left in the write buffer first.
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
// Error status (for ferror()) is set.
return 0;
}
if (stream->openFlags & O_APPEND) {
// If a file is opened in append mode, the file position indicator
// is moved to the end of the file before writing.
// We leave that up to the physical layer.
} else {
// TODO: It's possible that uio_Stream_flushWriteBuffer() just
// seeked back from the end of the part written, to the end of
// the part read. This seek will just undo that effect, so
// there's room for optimisation here. (decouple
// stream->seekLow from stream->readEnd by adding a seekHigh
// var?)
if (uio_lseek(stream->handle, stream->seekLow -
(stream->readEnd - stream->bufPtr), SEEK_SET) == -1) {
// errno is set
return 0;
}
stream->seekLow -= (stream->readEnd - stream->bufPtr);
}
if (bytesToWrite < stream->bufEnd - stream->dataEnd) {
// The now empty write buffer is large enough to store everything.
memcpy(stream->dataEnd, buf, bytesToWrite);
stream->dataEnd += bytesToWrite;
return nmemb;
}
// There is more data to write than fits in the (empty) write buffer.
// The data is written directly, in its entirety, without going
// through the write buffer.
bytesWritten = uio_write(stream->handle, buf, bytesToWrite);
if (bytesWritten != bytesToWrite) {
stream->status = uio_Stream_STATUS_ERROR;
if (bytesWritten == -1)
return 0;
}
if (stream->openFlags & O_APPEND) {
// Determine the new location in the file.
off_t newPos = uio_lseek(stream->handle, 0, SEEK_CUR);
if (newPos == -1) {
// errno is set
stream->status = uio_Stream_STATUS_ERROR;
return 0;
// XXX: is returning 0 the best thing to do? The data
// has actually been successfully written.
}
stream->seekLow = newPos;
} else {
stream->seekLow += bytesWritten;
}
// TODO: readStart is no longer aligned on a block.
stream->readStart = stream->buf;
stream->bufPtr = stream->buf;
stream->readEnd = stream->buf;
stream->writeStart = NULL;
if (bytesWritten == bytesToWrite)
return nmemb;
return (size_t) bytesWritten / size;
@@ -469,8 +404,16 @@ uio_fwrite(const void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
int
uio_fflush(uio_Stream *stream) {
assert(stream != NULL);
assert((stream->openFlags & O_ACCMODE) != O_RDONLY);
return uio_Stream_flushWriteBuffer(stream);
if (stream->operation == uio_StreamOperation_write) {
if (uio_Stream_flushWriteBuffer(stream) == -1) {
// errno is set
return (int) EOF;
}
stream->operation = uio_StreamOperation_none;
}
return 0;
}
int
@@ -494,88 +437,88 @@ uio_streamHandle(uio_Stream *stream) {
return stream->handle;
}
#ifndef NDEBUG
static void
uio_assertReadSanity(uio_Stream *stream) {
assert((stream->openFlags & O_ACCMODE) != O_WRONLY);
if (stream->operation == uio_StreamOperation_write) {
// "[...] output shall not be directly followed by input without an
// intervening call to the fflush function or to a file positioning
// function (fseek, fsetpos, or rewind), and input shall not be
// directly followed by output without an intervening call to a file
// positioning function, unless the input operation encounters
// end-of-file." (POSIX, C)
fprintf(stderr, "Error: Reading on a file directly after writing, "
"without an intervening call to fflush() or a file "
"positioning function.\n");
abort();
}
}
#endif
#ifndef NDEBUG
static void
uio_assertWriteSanity(uio_Stream *stream) {
assert((stream->openFlags & O_ACCMODE) != O_RDONLY);
if (stream->operation == uio_StreamOperation_read) {
// "[...] output shall not be directly followed by input without an
// intervening call to the fflush function or to a file positioning
// function (fseek, fsetpos, or rewind), and input shall not be
// directly followed by output without an intervening call to a file
// positioning function, unless the input operation encounters
// end-of-file." (POSIX, C)
fprintf(stderr, "Error: Writing on a file directly after reading, "
"without an intervening call to a file positioning "
"function.\n");
abort();
}
assert(stream->dataStart == stream->buf);
}
#endif
static int
uio_Stream_flushWriteBuffer(uio_Stream *stream) {
ssize_t bytesWritten;
off_t newPos;
newPos = uio_lseek(stream->handle,
(off_t) (stream->seekLow - (stream->readEnd - stream->writeStart)),
SEEK_SET);
if (newPos == -1) {
// errno is set
return -1;
}
stream->seekLow = newPos;
bytesWritten = uio_write(stream->handle, stream->writeStart,
stream->bufPtr - stream->writeStart);
if (bytesWritten != stream->bufPtr - stream->writeStart) {
assert(stream->operation == uio_StreamOperation_write);
bytesWritten = uio_write(stream->handle, stream->dataStart,
stream->dataEnd - stream->dataStart);
if (bytesWritten != stream->dataEnd - stream->dataStart) {
stream->status = uio_Stream_STATUS_ERROR;
if (bytesWritten != -1)
stream->seekLow += bytesWritten;
return -1;
}
stream->seekLow += bytesWritten;
stream->writeStart = NULL;
assert(stream->dataStart == stream->buf);
stream->dataEnd = stream->buf;
if (stream->bufPtr > stream->readEnd) {
stream->readEnd = stream->bufPtr;
} else {
newPos = uio_lseek(stream->handle,
(off_t) (stream->seekLow + (stream->readEnd - stream->bufPtr)),
SEEK_SET);
if (newPos == -1) {
// errno is set
// At least keep the internal state consistent so that
// a new uio_fseek() (after an uio_clearerr()) can succeed:
stream->readEnd = stream->bufPtr;
// SeekLow of the buffer is not aligned on a block.
return -1;
}
stream->seekLow = newPos;
}
return 0;
}
static void
uio_Stream_discardReadBuffer(uio_Stream *stream) {
assert(stream->operation == uio_StreamOperation_read);
stream->dataStart = stream->buf;
stream->dataEnd = stream->buf;
// TODO: when implementing pushback: throw away pushback buffer.
}
static int
uio_Stream_fillReadBuffer(uio_Stream *stream) {
ssize_t numRead;
assert(stream->bufPtr == stream->readEnd);
assert(stream->operation == uio_StreamOperation_read);
numRead = uio_read(stream->handle, stream->buf,
uio_Stream_BLOCK_SIZE);
if (numRead == -1)
return -1;
stream->bufPtr = stream->buf;
stream->readEnd = stream->buf + numRead;
stream->seekLow += numRead;
stream->dataStart = stream->buf;
stream->dataEnd = stream->buf + numRead;
return 0;
}
static int
uio_Stream_alignReadBuffer(uio_Stream *stream) {
off_t endAlign;
ssize_t numRead;
endAlign = uio_Stream_BLOCK_PAGE_START(stream->seekLow +
uio_Stream_BLOCK_SIZE - 1);
if (endAlign == stream->seekLow) {
// Nothing to do.
return 0;
}
numRead = uio_read(stream->handle, stream->buf,
endAlign - stream->seekLow);
if (numRead == -1)
return -1;
stream->bufPtr = stream->buf;
stream->readEnd = stream->buf + numRead;
stream->seekLow += numRead;
return 0;
}
static inline uio_Stream *
uio_Stream_new(uio_Handle *handle, int openFlags) {
uio_Stream *result;
@@ -584,13 +527,11 @@ uio_Stream_new(uio_Handle *handle, int openFlags) {
result->handle = handle;
result->openFlags = openFlags;
result->status = uio_Stream_STATUS_OK;
result->operation = uio_StreamOperation_none;
result->buf = uio_malloc(uio_Stream_BLOCK_SIZE);
result->bufPtr = result->buf;
result->readStart = result->buf;
result->readEnd = result->buf;
result->writeStart = NULL;
result->dataStart = result->buf;
result->dataEnd = result->buf;
result->bufEnd = result->buf + uio_Stream_BLOCK_SIZE;
result->seekLow = 0;
return result;
}
+18 -34
View File
@@ -55,48 +55,32 @@ uio_Handle *uio_streamHandle(uio_Stream *stream);
#include <fcntl.h>
#include "iointrn.h"
/*
* Layout of buf:
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | | | | | | |D|D|D|D|D|D|D|D| | | | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* ^ ^ ^ ^ ^
* +-readStart +-writeStart +-bufPtr +-readEnd bufEnd-+
*
* buf - start of the buffer
* readStart - start of the segment that was read
* writeStart - start of the part which is written, or NULL if nothing
* written.
* bufPtr - file pointer; here new reads or writes are done.
* readEnd - end of the part of the file that is pre-read.
* bufEnd - end of memory allocated for the buffer.
* D - dirty, needs to be written to disk
* X - invalid; reserved but not used memory
*
* Invariants:
* buf <= bufPtr <= bufEnd
* If writeStart != NULL: buf <= writeStart <= bufPtr <= bufEnd
* buf <= readStart <= readEnd <= bufEnd
* Note that writeStart and bufPtr may point past readEnd.
*
*/
typedef enum {
uio_StreamOperation_none,
uio_StreamOperation_read,
uio_StreamOperation_write
} uio_StreamOperation;
struct uio_Stream {
char *buf;
char *bufPtr;
char *readStart;
// Not really used atm.
char *writeStart;
char *readEnd;
// Start of the buffer.
char *dataStart;
// Start of the part of the buffer that is in use.
char *dataEnd;
// Start of the unused part of the buffer.
char *bufEnd;
off_t seekLow;
// Low(er) level file pointer. Always points to the position just
// past the part that's in the buffer as [readStart..readEnd].
// End of the buffer.
// INV: buf <= dataStart <= dataEnd <= bufEnd
// INV: if 'operation == uio_StreamOperation_write' then buf == dataStart
uio_Handle *handle;
int status;
#define uio_Stream_STATUS_OK 0
#define uio_Stream_STATUS_EOF 1
#define uio_Stream_STATUS_ERROR 2
uio_StreamOperation operation;
// What was the last action (reading or writing). This
// determines whether the buffer is a read or write buffer.
int openFlags;
// Flags used for opening the file.
};