summaryrefslogtreecommitdiff
path: root/src/libs/uio/uiostream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libs/uio/uiostream.c')
-rw-r--r--src/libs/uio/uiostream.c603
1 files changed, 603 insertions, 0 deletions
diff --git a/src/libs/uio/uiostream.c b/src/libs/uio/uiostream.c
new file mode 100644
index 0000000..eb101a5
--- /dev/null
+++ b/src/libs/uio/uiostream.c
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2003 Serge van den Boom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ * Nota bene: later versions of the GNU General Public License do not apply
+ * to this program.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "uioport.h"
+#include "iointrn.h"
+#include "uiostream.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "uioutils.h"
+#include "utils.h"
+#ifdef uio_MEM_DEBUG
+# include "memdebug.h"
+#endif
+
+#define uio_Stream_BLOCK_SIZE 1024
+
+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_flushWriteBuffer(uio_Stream *stream);
+static void uio_Stream_discardReadBuffer(uio_Stream *stream);
+
+
+uio_Stream *
+uio_fopen(uio_DirHandle *dir, const char *path, const char *mode) {
+ int openFlags;
+ uio_Handle *handle;
+ uio_Stream *stream;
+ int i;
+
+ switch (*mode) {
+ case 'r':
+ openFlags = O_RDONLY;
+ break;
+ case 'w':
+ openFlags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ openFlags = O_WRONLY| O_CREAT | O_APPEND;
+ default:
+ errno = EINVAL;
+ fprintf(stderr, "Invalid mode string in call to uio_fopen().\n");
+ return NULL;
+ }
+ mode++;
+
+ // C'89 says 'b' may either be the second or the third character.
+ // If someone specifies both 'b' and 't', he/she is out of luck.
+ i = 2;
+ while (i-- && (*mode != '\0')) {
+ switch (*mode) {
+ case 'b':
+#ifdef WIN32
+ openFlags |= O_BINARY;
+#endif
+ break;
+ case 't':
+#ifdef WIN32
+ openFlags |= O_TEXT;
+#endif
+ break;
+ case '+':
+ openFlags = (openFlags & ~O_ACCMODE) | O_RDWR;
+ break;
+ default:
+ i = 0;
+ // leave the while loop
+ break;
+ }
+ mode++;
+ }
+
+ // Any characters in the mode string that might follow are ignored.
+
+ handle = uio_open(dir, path, openFlags, S_IRUSR | S_IWUSR |
+ S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (handle == NULL) {
+ // errno is set
+ return NULL;
+ }
+
+ stream = uio_Stream_new(handle, openFlags);
+ return stream;
+}
+
+int
+uio_fclose(uio_Stream *stream) {
+ if (stream->operation == uio_StreamOperation_write)
+ uio_Stream_flushWriteBuffer(stream);
+ uio_close(stream->handle);
+ uio_Stream_delete(stream);
+ return 0;
+}
+
+// "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;
+ size_t bytesRead;
+
+ bytesToRead = size * nmemb;
+ bytesRead = 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->dataEnd - stream->dataStart, bytesToRead);
+ memcpy(buf, stream->dataStart, numRead);
+ buf = (void *) ((char *) buf + numRead);
+ stream->dataStart += numRead;
+ bytesToRead -= numRead;
+ bytesRead += numRead;
+ }
+ if (bytesToRead == 0) {
+ // Done already
+ return nmemb;
+ }
+
+ {
+ // Read the rest directly into the caller's buffer.
+ ssize_t numRead;
+ numRead = uio_read(stream->handle, buf, bytesToRead);
+ if (numRead == -1) {
+ stream->status = uio_Stream_STATUS_ERROR;
+ goto out;
+ }
+ bytesRead += numRead;
+ if ((size_t) numRead < bytesToRead) {
+ // End of file
+ stream->status = uio_Stream_STATUS_EOF;
+ stream->operation = uio_StreamOperation_none;
+ goto out;
+ }
+ }
+
+out:
+ if (bytesToRead == 0)
+ return nmemb;
+ return bytesRead / size;
+}
+
+char *
+uio_fgets(char *s, int size, uio_Stream *stream) {
+ int orgSize;
+ char *buf;
+
+ uio_assertReadSanity(stream);
+ stream->operation = uio_StreamOperation_read;
+
+ size--;
+ orgSize = size;
+ buf = s;
+ while (size > 0) {
+ size_t maxRead;
+ const char *newLinePos;
+
+ // Fill buffer if empty.
+ if (stream->dataStart == stream->dataEnd) {
+ if (uio_Stream_fillReadBuffer(stream) == -1) {
+ // errno is set
+ stream->status = uio_Stream_STATUS_ERROR;
+ return NULL;
+ }
+ 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;
+ }
+ break;
+ }
+ }
+
+ // Search in buffer
+ maxRead = minu(stream->dataEnd - stream->dataStart, size);
+ newLinePos = memchr(stream->dataStart, '\n', maxRead);
+ if (newLinePos != NULL) {
+ // Newline found.
+ maxRead = newLinePos + 1 - stream->dataStart;
+ memcpy(buf, stream->dataStart, maxRead);
+ stream->dataStart += maxRead;
+ buf[maxRead] = '\0';
+ return buf;
+ }
+ // No newline present.
+ memcpy(buf, stream->dataStart, maxRead);
+ stream->dataStart += maxRead;
+ buf += maxRead;
+ size -= maxRead;
+ }
+
+ *buf = '\0';
+ return s;
+}
+
+int
+uio_fgetc(uio_Stream *stream) {
+ int result;
+
+ uio_assertReadSanity(stream);
+ stream->operation = uio_StreamOperation_read;
+
+ 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->dataStart == stream->dataEnd) {
+ // End-of-file
+ stream->status = uio_Stream_STATUS_EOF;
+ stream->operation = uio_StreamOperation_none;
+ return (int) EOF;
+ }
+ }
+
+ result = (int) *((unsigned char *) stream->dataStart);
+ stream->dataStart++;
+ return result;
+}
+
+// Only one character pushback is guaranteed, just like with stdio ungetc().
+int
+uio_ungetc(int c, uio_Stream *stream) {
+ assert((stream->openFlags & O_ACCMODE) != O_WRONLY);
+ assert(c >= 0 && c <= 255);
+
+ return (int) EOF;
+ // not implemented
+// return c;
+}
+
+// NB. POSIX allows errno to be set for vsprintf(), but does not require it:
+// "The value of errno may be set to nonzero by a library function call
+// whether or not there is an error, provided the use of errno is not
+// documented in the description of the function in this International
+// Standard." The latter is the case for vsprintf().
+int
+uio_vfprintf(uio_Stream *stream, const char *format, va_list args) {
+ // This could be done faster, but going through snprintf() is easiest,
+ // and is fast enough for now.
+ char *buf;
+ int putResult;
+ int savedErrno;
+
+ buf = uio_vasprintf(format, args);
+ if (buf == NULL) {
+ // errno may or may not be set
+ return -1;
+ }
+
+ putResult = uio_fputs(buf, stream);
+ savedErrno = errno;
+
+ uio_free(buf);
+
+ errno = savedErrno;
+ return putResult;
+}
+
+int
+uio_fprintf(uio_Stream *stream, const char *format, ...) {
+ va_list args;
+ int result;
+
+ va_start(args, format);
+ result = uio_vfprintf(stream, format, args);
+ va_end(args);
+
+ return result;
+}
+
+int
+uio_fputc(int c, uio_Stream *stream) {
+ assert((stream->openFlags & O_ACCMODE) != O_RDONLY);
+ assert(c >= 0 && c <= 255);
+
+ 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->dataEnd = (unsigned char) c;
+ stream->dataEnd++;
+ return c;
+}
+
+int
+uio_fputs(const char *s, uio_Stream *stream) {
+ int result;
+
+ result = uio_fwrite(s, strlen(s), 1, stream);
+ if (result != 1)
+ return EOF;
+ return 0;
+}
+
+int
+uio_fseek(uio_Stream *stream, long offset, int whence) {
+ int newPos;
+
+ 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;
+
+ newPos = uio_lseek(stream->handle, offset, whence);
+ if (newPos == -1) {
+ // errno is set
+ return -1;
+ }
+ stream->status = uio_Stream_STATUS_OK;
+ // Clear error or end-of-file flag.
+
+ return 0;
+}
+
+long
+uio_ftell(uio_Stream *stream) {
+ 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
+// 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().
+size_t
+uio_fwrite(const void *buf, size_t size, size_t nmemb, uio_Stream *stream) {
+ ssize_t bytesToWrite;
+ ssize_t bytesWritten;
+
+ uio_assertWriteSanity(stream);
+ stream->operation = uio_StreamOperation_write;
+
+ // 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 (bytesToWrite < stream->bufEnd - stream->dataEnd) {
+ // There's enough space in the write buffer to store everything.
+ 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 first.
+ if (uio_Stream_flushWriteBuffer(stream) == -1) {
+ // errno is set
+ // Error status (for ferror()) is set.
+ return 0;
+ }
+
+ 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 (bytesWritten == bytesToWrite)
+ return nmemb;
+ return (size_t) bytesWritten / size;
+}
+
+// NB: stdio fflush() accepts NULL to flush all streams. uio_flush() does
+// not.
+int
+uio_fflush(uio_Stream *stream) {
+ assert(stream != NULL);
+
+ 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
+uio_feof(uio_Stream *stream) {
+ return stream->status == uio_Stream_STATUS_EOF;
+}
+
+int
+uio_ferror(uio_Stream *stream) {
+ return stream->status == uio_Stream_STATUS_ERROR;
+}
+
+void
+uio_clearerr(uio_Stream *stream) {
+ stream->status = uio_Stream_STATUS_OK;
+}
+
+// Counterpart of fileno()
+uio_Handle *
+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;
+
+ 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;
+ return -1;
+ }
+ assert(stream->dataStart == stream->buf);
+ stream->dataEnd = stream->buf;
+
+ 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->operation == uio_StreamOperation_read);
+
+ numRead = uio_read(stream->handle, stream->buf,
+ uio_Stream_BLOCK_SIZE);
+ if (numRead == -1)
+ return -1;
+ stream->dataStart = stream->buf;
+ stream->dataEnd = stream->buf + numRead;
+ return 0;
+}
+
+static inline uio_Stream *
+uio_Stream_new(uio_Handle *handle, int openFlags) {
+ uio_Stream *result;
+
+ result = uio_Stream_alloc();
+ 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->dataStart = result->buf;
+ result->dataEnd = result->buf;
+ result->bufEnd = result->buf + uio_Stream_BLOCK_SIZE;
+ return result;
+}
+
+static inline uio_Stream *
+uio_Stream_alloc(void) {
+ uio_Stream *result = uio_malloc(sizeof (uio_Stream));
+#ifdef uio_MEM_DEBUG
+ uio_MemDebug_debugAlloc(uio_Stream, (void *) result);
+#endif
+ return result;
+}
+
+static inline void
+uio_Stream_delete(uio_Stream *stream) {
+ uio_free(stream->buf);
+ uio_Stream_free(stream);
+}
+
+static inline void
+uio_Stream_free(uio_Stream *stream) {
+#ifdef uio_MEM_DEBUG
+ uio_MemDebug_debugFree(uio_Stream, (void *) stream);
+#endif
+ uio_free(stream);
+}
+