Hi Perry, et al., On Tue, Aug 24, 2010 at 6:40 AM, Perry E. Metzger <perry%piermont.com@localhost> wrote: > On Mon, 23 Aug 2010 20:33:05 -0700 Bruce Korb <bkorb%gnu.org@localhost> wrote: >> On Mon, Aug 23, 2010 at 4:15 PM, Perry E. Metzger >> <perry%piermont.com@localhost> wrote: >> By the way, I'm curious about plans for fmemopen. > > I'm unaware of any such plans. However, if you have an > implementation, why don't you send it to tech-userlevel along with a > pointer to the fact that it is an implementation of the stated posix > spec at the given URL? (You don't have commit access yourself, > correct?) That's correct. No access. Attached is the fmemopen.c file, unifdef-ed to just include the BSD funopen implementation. (My original leverages off of either funopen or fopencookie. I'm platform agnostic.) This implementation is a strict superset of POSIX: >> a la the pending POSIX spec: >> http://www.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html because the POSIX spec disallows extending the buffer size and I saw no reason for the constraint. Relaxing that restriction and allowing for access to the buffer pointer eliminates the need for open_memstream(), too. I prefer not having any more interfaces than necessary. :) In fact, I'd prefer a user space fs that had a kernel module that could tell the open() code to finish open processing by calling a library space open function, a la funopen and fopencookie. This would permit this call to work: fd = open("/path/to/userfs/additional/path/info", O_RDWR.) but I have digressed. By using the interface exactly as described in the POSIX docs, you get exactly the POSIX behavior. Additional behaviors are available by using an ssize_t size with a negative value and by calling fmemioctl(). Regards, Bruce
/** * \file fmemopen.c * * Copyright (c) 2004-2010 by Bruce Korb. All rights reserved. * * Time-stamp: "2010-08-20 12:59:32 bkorb" * * This code was inspired from software written by * Hanno Mueller, kontakt%hanno.de@localhost * and completely rewritten by Bruce Korb, bkorb%gnu.org@localhost * * 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. * 3. Neither the name of the author nor the name of any other contributor * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. */ #include <sys/types.h> #include <fcntl.h> #include <errno.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "libfmem.h" typedef fpos_t seek_off_t; typedef fpos_t seek_ret_t; 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 *); #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" ) #ifndef NUL # define NUL '\0' #endif #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 { 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. */ } fmem_cookie_t; typedef struct { FILE * fp; fmem_cookie_t * cookie; } cookie_fp_map_t; typedef enum { FMO_FALSE = 0, FMO_TRUE = 1 } fmo_bool; 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 *pMode, 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 fmo_bool fmem_config_user_buf(fmem_cookie_t * pFMC, void * buf, ssize_t len); static fmo_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; /* * 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; } 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; return new_pos; 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 fmo_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 FMO_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) < 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 < len)) { pFMC->buffer[pFMC->next_ix] = NUL; } pFMC->buf_size = len; return FMO_TRUE; } /** * Allocate an initial buffer for fmem. */ static fmo_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 FMO_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 FMO_TRUE; } /*=export_func 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 * fmo_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; res = funopen(pFMC, pRd, pWr, fmem_seek, fmem_close); 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 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 fmo_fmemopen. =*/ int fmemioctl(FILE * fp, int req, ...) { fmem_cookie_t * cookie; fmemc_get_buf_addr_t * gba; switch (req) { case IOCTL_FMEMC_GET_BUF_ADDR: break; default: /* * 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 agen5/fmemopen.c */
Attachment:
fmemopen.3
Description: Binary data
Attachment:
fmemioctl.3
Description: Binary data
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "libfmem.h" static char const text[] = "The quick brown fox jumped over lazy dogs.\n"; static int const txtlen = sizeof(text) - 1; fmemc_get_buf_addr_t gba = { .own = FMEMC_GBUF_TAKE_OWNERSHIP }; void die(char const * msg) { fprintf(stderr, "fatal test error: %s\n", msg); exit(EXIT_FAILURE); } void ck_buf(char const * ck) { int ct = 1; do { if (strncmp(ck, text, txtlen) != 0) die("miscompare"); ck += txtlen; } while (++ct <= 3); if (strcmp(ck, "\n") != 0) die("miscompare"); } void ck_file(FILE * fp) { char buf[8 * sizeof(text)]; int ct; rewind(fp); ct = fread(buf, 1, sizeof(buf), fp); if (ct != (3 * txtlen) + 1) { fprintf(stderr, "data len is %d, not %d\n", ct, (3 * txtlen) + 1); exit(EXIT_FAILURE); } ck_buf(buf); } int main(int argc, char ** argv) { FILE * fp; fp = ag_fmemopen(NULL, 0, "w+"); if (fp == NULL) { perror("fmemopen failed"); exit(EXIT_FAILURE); } fprintf(fp, "%s", text); fwrite(text, txtlen, 1, fp); fputs(text, fp); fputc('\n', fp); fflush(fp); if (ag_fmemioctl(fp, IOCTL_FMEMC_GET_BUF_ADDR, &gba) != 0) { perror("fmemioctl failed"); exit(EXIT_FAILURE); } if (gba.eof != (3 * txtlen) + 1) { fprintf(stderr, "eof is %d, not %d\n", gba.eof, (3 * txtlen) + 1); exit(EXIT_FAILURE); } ck_buf(gba.buffer); ck_file(fp); fclose(fp); free(gba.buffer); fputs("data written correctly\n", stdout); return EXIT_SUCCESS; }