summaryrefslogtreecommitdiff
path: root/agen5/fmemopen.c
diff options
context:
space:
mode:
Diffstat (limited to 'agen5/fmemopen.c')
-rw-r--r--agen5/fmemopen.c743
1 files changed, 743 insertions, 0 deletions
diff --git a/agen5/fmemopen.c b/agen5/fmemopen.c
new file mode 100644
index 0000000..6e865be
--- /dev/null
+++ b/agen5/fmemopen.c
@@ -0,0 +1,743 @@
+#if defined(ENABLE_FMEMOPEN)
+#include <sys/ioctl.h>
+
+typedef enum {
+ FMEMC_INVALID = 0,
+ FMEMC_GET_BUF_ADDR
+} fmemctl_t;
+
+typedef struct {
+ enum { FMEMC_GBUF_LEAVE_OWNERSHIP,
+ FMEMC_GBUF_TAKE_OWNERSHIP
+ } own;
+ char * buffer;
+ size_t buf_size;
+ size_t eof;
+} fmemc_get_buf_addr_t;
+
+#ifdef HURD
+#define _IOT__IOTBASE_fmemc_get_buf_addr_t sizeof(fmemc_get_buf_addr_t)
+#endif
+
+#define IOCTL_FMEMC_GET_BUF_ADDR \
+ _IOWR('m', FMEMC_GET_BUF_ADDR, fmemc_get_buf_addr_t)
+
+/**
+ * @file /old-home/bkorb/tools/mine/lib/fmemopen/fmemopen.c
+ *
+ * Copyright (c) 2004-2012 by Bruce Korb. All rights reserved.
+ *
+ * This code was inspired from software written by
+ * Hanno Mueller, kontakt@hanno.de
+ * and completely rewritten by Bruce Korb, bkorb@gnu.org
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#if defined(HAVE_FOPENCOOKIE)
+# if defined(HAVE_LIBIO_H)
+# include <libio.h>
+# endif
+
+ typedef off64_t * seek_off_t;
+ typedef int seek_ret_t;
+
+#elif defined(HAVE_FUNOPEN)
+ typedef fpos_t seek_off_t;
+ typedef fpos_t seek_ret_t;
+
+# ifdef NEED_COOKIE_FUNCTION_TYPEDEFS
+ typedef int (cookie_read_function_t )(void *, char *, int);
+ typedef int (cookie_write_function_t)(void *, char const *, int);
+ typedef fpos_t (cookie_seek_function_t )(void *, fpos_t, int);
+ typedef int (cookie_close_function_t)(void *);
+# endif /* NEED_COOKIE_FUNCTION_TYPEDEFS */
+#endif
+
+#define PROP_TABLE \
+_Prop_( read, "Read from buffer" ) \
+_Prop_( write, "Write to buffer" ) \
+_Prop_( append, "Append to buffer okay" ) \
+_Prop_( binary, "byte data - not string" ) \
+_Prop_( create, "allocate the string" ) \
+_Prop_( truncate, "start writing at start" ) \
+_Prop_( allocated, "we allocated the buffer" ) \
+_Prop_( fixed_size, "writes do not append" )
+
+#define _Prop_(n,s) BIT_ID_ ## n,
+typedef enum { PROP_TABLE BIT_CT } fmem_flags_e;
+#undef _Prop_
+
+#define FLAG_BIT(n) (1 << BIT_ID_ ## n)
+
+typedef unsigned long mode_bits_t;
+typedef unsigned char buf_bytes_t;
+
+typedef struct fmem_cookie_s fmem_cookie_t;
+struct fmem_cookie_s {
+ mode_bits_t mode;
+ buf_bytes_t * buffer;
+ size_t buf_size; /* Full size of buffer */
+ size_t next_ix; /* Current position */
+ size_t eof; /* End Of File */
+ size_t pg_size; /* size of a memory page.
+ Future architectures allow it to vary
+ by memory region. */
+};
+
+typedef struct {
+ FILE * fp;
+ fmem_cookie_t * cookie;
+} cookie_fp_map_t;
+
+static cookie_fp_map_t const * map = NULL;
+static unsigned int map_ct = 0;
+static unsigned int map_alloc_ct = 0;
+
+/* = = = START-STATIC-FORWARD = = = */
+static int
+fmem_getmode(char const * mode, mode_bits_t * pRes);
+
+static int
+fmem_extend(fmem_cookie_t *pFMC, size_t new_size);
+
+static ssize_t
+fmem_read(void *cookie, void *pBuf, size_t sz);
+
+static ssize_t
+fmem_write(void *cookie, const void *pBuf, size_t sz);
+
+static seek_ret_t
+fmem_seek(void * cookie, seek_off_t offset, int dir);
+
+static int
+fmem_close(void * cookie);
+
+static bool
+fmem_config_user_buf(fmem_cookie_t * pFMC, void * buf, ssize_t len);
+
+static bool
+fmem_alloc_buf(fmem_cookie_t * pFMC, ssize_t len);
+/* = = = END-STATIC-FORWARD = = = */
+
+#ifdef TEST_FMEMOPEN
+ static fmem_cookie_t* saved_cookie = NULL;
+#endif
+
+/**
+ * Convert a mode string into mode bits.
+ */
+static int
+fmem_getmode(char const * mode, mode_bits_t * pRes)
+{
+ if (mode == NULL)
+ return 1;
+
+ switch (*mode) {
+ case 'a': *pRes = FLAG_BIT(write) | FLAG_BIT(append);
+ break;
+ case 'w': *pRes = FLAG_BIT(write) | FLAG_BIT(truncate);
+ break;
+ case 'r': *pRes = FLAG_BIT(read);
+ break;
+ default: return EINVAL;
+ }
+
+ /*
+ * If someone wants to supply a "wxxbxbbxbb+" mode string, I don't care.
+ */
+ for (;;) {
+ switch (*++mode) {
+ case '+': *pRes |= FLAG_BIT(read) | FLAG_BIT(write);
+ if (mode[1] != NUL)
+ return EINVAL;
+ break;
+ case NUL: break;
+ case 'b': *pRes |= FLAG_BIT(binary); continue;
+ case 'x': continue;
+ default: return EINVAL;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * Extend the space associated with an fmem file.
+ */
+static int
+fmem_extend(fmem_cookie_t *pFMC, size_t new_size)
+{
+ size_t ns = (new_size + (pFMC->pg_size - 1)) & (~(pFMC->pg_size - 1));
+
+ /*
+ * We can expand the buffer only if we are in append mode.
+ */
+ if (pFMC->mode & FLAG_BIT(fixed_size))
+ goto no_space;
+
+ if ((pFMC->mode & FLAG_BIT(allocated)) == 0) {
+ /*
+ * Previously, this was a user supplied buffer. We now move to one
+ * of our own. The user is responsible for the earlier memory.
+ */
+ void* bf = malloc(ns);
+ if (bf == NULL)
+ goto no_space;
+
+ memcpy(bf, pFMC->buffer, pFMC->buf_size);
+ pFMC->buffer = bf;
+ pFMC->mode |= FLAG_BIT(allocated);
+ }
+ else {
+ void* bf = realloc(pFMC->buffer, ns);
+ if (bf == NULL)
+ goto no_space;
+
+ pFMC->buffer = bf;
+ }
+
+ /*
+ * Unallocated file space is set to zeros. Emulate that.
+ */
+ memset(pFMC->buffer + pFMC->buf_size, 0, ns - pFMC->buf_size);
+ pFMC->buf_size = ns;
+ return 0;
+
+ no_space:
+ errno = ENOSPC;
+ return -1;
+}
+
+/**
+ * Handle file system callback to read data from our string.
+ */
+static ssize_t
+fmem_read(void *cookie, void *pBuf, size_t sz)
+{
+ fmem_cookie_t *pFMC = cookie;
+
+ if (pFMC->next_ix + sz > pFMC->eof) {
+ if (pFMC->next_ix >= pFMC->eof)
+ return (sz > 0) ? -1 : 0;
+ sz = pFMC->eof - pFMC->next_ix;
+ }
+
+ memcpy(pBuf, pFMC->buffer + pFMC->next_ix, sz);
+
+ pFMC->next_ix += sz;
+
+ return sz;
+}
+
+/**
+ * Handle file system callback to write data to our string
+ */
+static ssize_t
+fmem_write(void *cookie, const void *pBuf, size_t sz)
+{
+ fmem_cookie_t *pFMC = cookie;
+ int add_nul_char;
+
+ /*
+ * In append mode, always seek to the end before writing.
+ */
+ if (pFMC->mode & FLAG_BIT(append))
+ pFMC->next_ix = pFMC->eof;
+
+ /*
+ * Only add a NUL character if:
+ *
+ * * we are not in binary mode
+ * * there are data to write
+ * * the last character to write is not already NUL
+ */
+ add_nul_char =
+ ((pFMC->mode & FLAG_BIT(binary)) != 0)
+ && (sz > 0)
+ && (((char*)pBuf)[sz - 1] != NUL);
+
+ {
+ size_t next_pos = pFMC->next_ix + sz + add_nul_char;
+ if (next_pos > pFMC->buf_size) {
+ if (fmem_extend(pFMC, next_pos) != 0) {
+ /*
+ * We could not extend the memory. Try to write some data.
+ * Fail if we are either at the end or not writing data.
+ */
+ if ((pFMC->next_ix >= pFMC->buf_size) || (sz == 0))
+ return -1; /* no space at all. errno is set. */
+
+ /*
+ * Never add the NUL for a truncated write. "sz" may be
+ * unchanged or limited here.
+ */
+ add_nul_char = 0;
+ sz = pFMC->buf_size - pFMC->next_ix;
+ }
+ }
+ }
+
+ memcpy(pFMC->buffer + pFMC->next_ix, pBuf, sz);
+
+ pFMC->next_ix += sz;
+
+ /*
+ * Check for new high water mark and remember it. Add a NUL if
+ * we do that and if we have a new high water mark.
+ */
+ if (pFMC->next_ix > pFMC->eof) {
+ pFMC->eof = pFMC->next_ix;
+ if (add_nul_char)
+ /*
+ * There is space for this NUL. The "add_nul_char" is not part of
+ * the "sz" that was added to "next_ix".
+ */
+ pFMC->buffer[ pFMC->eof ] = NUL;
+ }
+
+ return sz;
+}
+
+/**
+ * Handle file system callback to set a new current position
+ */
+static seek_ret_t
+fmem_seek(void * cookie, seek_off_t offset, int dir)
+{
+ size_t new_pos;
+ fmem_cookie_t *pFMC = cookie;
+
+#ifdef HAVE_FOPENCOOKIE
+ /*
+ * GNU interface: offset passed and returned by address.
+ */
+ switch (dir) {
+ case SEEK_SET: new_pos = *offset; break;
+ case SEEK_CUR: new_pos = pFMC->next_ix + *offset; break;
+ case SEEK_END: new_pos = pFMC->eof - *offset; break;
+
+ default:
+ goto seek_oops;
+ }
+#else
+ /*
+ * BSD interface: offset passed by value, returned as retval.
+ */
+ switch (dir) {
+ case SEEK_SET: new_pos = offset; break;
+ case SEEK_CUR: new_pos = pFMC->next_ix + offset; break;
+ case SEEK_END: new_pos = pFMC->eof - offset; break;
+
+ default:
+ goto seek_oops;
+ }
+#endif
+
+ if ((signed)new_pos < 0)
+ goto seek_oops;
+
+ if (new_pos > pFMC->buf_size) {
+ if (fmem_extend(pFMC, new_pos))
+ return -1; /* errno is set */
+ }
+
+ pFMC->next_ix = new_pos;
+
+#ifdef HAVE_FOPENCOOKIE
+ *offset = (off64_t)new_pos;
+ return 0;
+#else
+ return new_pos;
+#endif
+
+ seek_oops:
+ errno = EINVAL;
+ return -1;
+}
+
+/**
+ * Free up the memory associated with an fmem file.
+ * If the user is managing the space, then the allocated bit is set.
+ */
+static int
+fmem_close(void * cookie)
+{
+ fmem_cookie_t * pFMC = cookie;
+ cookie_fp_map_t * pmap = (void *)map;
+ unsigned int mct = map_ct;
+
+ while (mct-- != 0) {
+ if (pmap->cookie == cookie) {
+ *pmap = map[--map_ct];
+ break;
+ }
+ pmap++;
+ }
+
+ if (mct > map_ct)
+ errno = EINVAL;
+
+ if (pFMC->mode & FLAG_BIT(allocated))
+ free(pFMC->buffer);
+ free(pFMC);
+
+ return 0;
+}
+
+/**
+ * Configure the user supplied buffer.
+ */
+static bool
+fmem_config_user_buf(fmem_cookie_t * pFMC, void * buf, ssize_t len)
+{
+ /*
+ * User allocated buffer. User responsible for disposal.
+ */
+ if (len == 0) {
+ free(pFMC);
+ errno = EINVAL;
+ return false;
+ }
+
+ pFMC->buffer = (buf_bytes_t*)buf;
+
+ /* Figure out where our "next byte" and EOF are.
+ * Truncated files start at the beginning.
+ */
+ if (pFMC->mode & FLAG_BIT(truncate)) {
+ /*
+ * "write" mode
+ */
+ pFMC->eof = \
+ pFMC->next_ix = 0;
+ }
+
+ else if (pFMC->mode & FLAG_BIT(binary)) {
+ pFMC->eof = len;
+ pFMC->next_ix = (pFMC->mode & FLAG_BIT(append)) ? len : 0;
+
+ } else {
+ /*
+ * append or read text mode -- find the end of the buffer
+ * (the first NUL character)
+ */
+ buf_bytes_t *p = (buf_bytes_t*)buf;
+
+ pFMC->eof = 0;
+ while ((*p != NUL) && (++(pFMC->eof) < (size_t)len)) p++;
+ pFMC->next_ix =
+ (pFMC->mode & FLAG_BIT(append)) ? pFMC->eof : 0;
+ }
+
+ /*
+ * text mode - NUL terminate buffer, if it fits.
+ */
+ if ( ((pFMC->mode & FLAG_BIT(binary)) == 0)
+ && (pFMC->next_ix < (size_t)len)) {
+ pFMC->buffer[pFMC->next_ix] = NUL;
+ }
+
+ pFMC->buf_size = len;
+ return true;
+}
+
+/**
+ * Allocate an initial buffer for fmem.
+ */
+static bool
+fmem_alloc_buf(fmem_cookie_t * pFMC, ssize_t len)
+{
+ /*
+ * We must allocate the buffer. If "len" is zero, set it to page size.
+ */
+ pFMC->mode |= FLAG_BIT(allocated);
+ if (len == 0)
+ len = pFMC->pg_size;
+
+ /*
+ * Unallocated file space is set to NULs. Emulate that.
+ */
+ pFMC->buffer = calloc((size_t)1, (size_t)len);
+ if (pFMC->buffer == NULL) {
+ errno = ENOMEM;
+ free(pFMC);
+ return false;
+ }
+
+ /*
+ * We've allocated the buffer. The end of file and next entry
+ * are both zero.
+ */
+ pFMC->next_ix = 0;
+ pFMC->eof = 0;
+ pFMC->buf_size = len;
+ return true;
+}
+
+/*=export_func ag_fmemopen
+ *
+ * what: Open a stream to a string
+ *
+ * arg: + void* + buf + buffer to use for i/o +
+ * arg: + ssize_t + len + size of the buffer +
+ * arg: + char* + mode + mode string, a la fopen(3C) +
+ *
+ * ret-type: FILE*
+ * ret-desc: a stdio FILE* pointer
+ *
+ * err: NULL is returned and errno is set to @code{EINVAL} or @code{ENOSPC}.
+ *
+ * doc:
+ *
+ * This function requires underlying @var{libc} functionality:
+ * either @code{fopencookie(3GNU)} or @code{funopen(3BSD)}.
+ *
+ * If @var{buf} is @code{NULL}, then a buffer is allocated. The initial
+ * allocation is @var{len} bytes. If @var{len} is less than zero, then the
+ * buffer will be reallocated as more space is needed. Any allocated
+ * memory is @code{free()}-ed when @code{fclose(3C)} is called.
+ *
+ * If @code{buf} is not @code{NULL}, then @code{len} must not be zero.
+ * It may still be less than zero to indicate that the buffer may
+ * be reallocated.
+ *
+ * The mode string is interpreted as follows. If the first character of
+ * the mode is:
+ *
+ * @table @code
+ * @item a
+ * Then the string is opened in "append" mode. In binary mode, "appending"
+ * will begin from the end of the initial buffer. Otherwise, appending will
+ * start at the first NUL character in the initial buffer (or the end of the
+ * buffer if there is no NUL character). Do not use fixed size buffers
+ * (negative @var{len} lengths) in append mode.
+ *
+ * @item w
+ * Then the string is opened in "write" mode. Any initial buffer is presumed
+ * to be empty.
+ *
+ * @item r
+ * Then the string is opened in "read" mode.
+ * @end table
+ *
+ * @noindent
+ * If it is not one of these three, the open fails and @code{errno} is
+ * set to @code{EINVAL}. These initial characters may be followed by:
+ *
+ * @table @code
+ * @item +
+ * The buffer is marked as updatable and both reading and writing is enabled.
+ *
+ * @item b
+ * The I/O is marked as "binary" and a trailing NUL will not be inserted
+ * into the buffer. Without this mode flag, one will be inserted after the
+ * @code{EOF}, if it fits. It will fit if the buffer is extensible (the
+ * provided @var{len} was negative). This mode flag has no effect if
+ * the buffer is opened in read-only mode.
+ *
+ * @item x
+ * This is ignored.
+ * @end table
+ *
+ * @noindent
+ * Any other letters following the inital 'a', 'w' or 'r' will cause an error.
+=*/
+FILE *
+ag_fmemopen(void * buf, ssize_t len, char const * mode)
+{
+ fmem_cookie_t *pFMC;
+
+ {
+ mode_bits_t bits;
+
+ if (fmem_getmode(mode, &bits) != 0) {
+ return NULL;
+ }
+
+ pFMC = malloc(sizeof(fmem_cookie_t));
+ if (pFMC == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ pFMC->mode = bits;
+ }
+
+ /*
+ * Two more mode bits that do not come from the mode string:
+ * a negative size implies fixed size buffer and a NULL
+ * buffer pointer means we must allocate (and free) it.
+ */
+ if (len <= 0) {
+ /*
+ * We only need page size if we might extend an allocation.
+ */
+ len = -len;
+ pFMC->pg_size = getpagesize();
+ }
+
+ else {
+ pFMC->mode |= FLAG_BIT(fixed_size);
+ }
+
+ if (buf != NULL) {
+ if (! fmem_config_user_buf(pFMC, buf, len))
+ return NULL;
+
+ } else if ((pFMC->mode & (FLAG_BIT(append) | FLAG_BIT(truncate))) == 0) {
+ /*
+ * Not appending and not truncating. We must be reading.
+ * We also have no user supplied buffer. Nonsense.
+ */
+ errno = EINVAL;
+ free(pFMC);
+ return NULL;
+ }
+
+ else if (! fmem_alloc_buf(pFMC, len))
+ return NULL;
+
+#ifdef TEST_FMEMOPEN
+ saved_cookie = pFMC;
+#endif
+
+ {
+ FILE * res;
+
+ cookie_read_function_t* pRd = (pFMC->mode & FLAG_BIT(read))
+ ? (cookie_read_function_t*)fmem_read : NULL;
+ cookie_write_function_t* pWr = (pFMC->mode & FLAG_BIT(write))
+ ? (cookie_write_function_t*)fmem_write : NULL;
+
+#ifdef HAVE_FOPENCOOKIE
+ cookie_io_functions_t iof;
+ iof.read = pRd;
+ iof.write = pWr;
+ iof.seek = fmem_seek;
+ iof.close = fmem_close;
+
+ res = fopencookie(pFMC, mode, iof);
+#else
+ res = funopen(pFMC, pRd, pWr, fmem_seek, fmem_close);
+#endif
+ if (res == NULL)
+ return res;
+
+ if (++map_ct >= map_alloc_ct) {
+ void * p = (map_alloc_ct > 0)
+ ? realloc((void *)map, (map_alloc_ct += 4) * sizeof(*map))
+ : malloc((map_alloc_ct = 4) * sizeof(*map));
+
+ if (p == NULL) {
+ fclose(res);
+ errno = ENOMEM; /* "fclose" set it to "EINVAL". */
+ return NULL;
+ }
+
+ map = p;
+ }
+
+ {
+ cookie_fp_map_t * p = (void *)(map + map_ct - 1);
+ p->fp = res;
+ p->cookie = pFMC;
+ }
+
+ return res;
+ }
+}
+
+/*=export_func ag_fmemioctl
+ *
+ * what: perform an ioctl on a FILE* descriptor
+ *
+ * arg: + FILE* + fp + file pointer +
+ * arg: + int + req + ioctl command +
+ * arg: + ... + varargs + arguments for command +
+ *
+ * ret-type: int
+ * ret-desc: zero on success, otherwise error in errno
+ *
+ * err: errno is set to @code{EINVAL} or @code{ENOSPC}.
+ *
+ * doc:
+ *
+ * The file pointer passed in must have been returned by ag_fmemopen.
+=*/
+int
+ag_fmemioctl(FILE * fp, int req, ...)
+{
+ fmem_cookie_t * cookie;
+ fmemc_get_buf_addr_t * gba;
+
+ if ((unsigned int)req != IOCTL_FMEMC_GET_BUF_ADDR) {
+ /*
+ * It is not any of the IOCTL commands we know about.
+ */
+ errno = EINVAL;
+ return -1;
+ }
+
+ {
+ cookie_fp_map_t const * pmap = map;
+ unsigned int mct = map_ct;
+
+ for (;;) {
+ if (mct-- == 0) {
+ /*
+ * fmemopen didn't create this FILE*, so it is invalid.
+ */
+ errno = EINVAL;
+ return -1;
+ }
+ if (pmap->fp == fp)
+ break;
+ pmap++;
+ }
+
+ cookie = pmap->cookie;
+ }
+
+ {
+ va_list ap;
+ va_start(ap, req);
+ gba = va_arg(ap, fmemc_get_buf_addr_t *);
+ va_end(ap);
+ }
+
+ gba->buffer = (char *)(cookie->buffer);
+ gba->buf_size = cookie->buf_size;
+ gba->eof = cookie->eof;
+ if (gba->own != FMEMC_GBUF_LEAVE_OWNERSHIP)
+ cookie->mode &= ~FLAG_BIT(allocated);
+ return 0;
+}
+
+/*
+ * Local Variables:
+ * mode: C
+ * c-file-style: "stroustrup"
+ * indent-tabs-mode: nil
+ * End:
+ * end of fmemopen.c */
+#endif /* ENABLE_FMEMOPEN */