Implement a light-weight replacement for BSD variants that provide funopen() as a way to hook stdio (comparable to glibc's fopencookie).
Tested on FreeBSD 8.0. Fails to compile on other platforms, like Solaris, cygwin 1.5, and mingw. * modules/open_memstream: New file. * m4/open_memstream.m4 (gl_FUNC_OPEN_MEMSTREAM): Likewise. * m4/stdio_h.m4 (gl_STDIO_H): Check for declaration. (gl_STDIO_H_DEFAULTS): New witnesses. * modules/stdio (Makefile.am): Substitute them. * lib/stdio.in.h (open_memstream): Declare it. * lib/open_memstream.c: Implement it. * doc/posix-functions/open_memstream.texi (open_memstream): Document the replacement. Signed-off-by: Eric Blake <ebl...@redhat.com> --- ChangeLog | 11 ++ doc/posix-functions/open_memstream.texi | 9 +- lib/open_memstream.c | 191 +++++++++++++++++++++++++++++++ lib/stdio.in.h | 19 +++ m4/open_memstream.m4 | 26 ++++ m4/stdio_h.m4 | 8 +- modules/open_memstream | 29 +++++ modules/stdio | 2 + 8 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 lib/open_memstream.c create mode 100644 m4/open_memstream.m4 create mode 100644 modules/open_memstream diff --git a/ChangeLog b/ChangeLog index 78f9667..786f89e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,16 @@ 2010-04-23 Eric Blake <ebl...@redhat.com> + open_memstream: new module + * modules/open_memstream: New file. + * m4/open_memstream.m4 (gl_FUNC_OPEN_MEMSTREAM): Likewise. + * m4/stdio_h.m4 (gl_STDIO_H): Check for declaration. + (gl_STDIO_H_DEFAULTS): New witnesses. + * modules/stdio (Makefile.am): Substitute them. + * lib/stdio.in.h (open_memstream): Declare it. + * lib/open_memstream.c: Implement it. + * doc/posix-functions/open_memstream.texi (open_memstream): + Document the replacement. + open_memstream-tests: new module * modules/open_memstream-tests: New file. * tests/test-open_memstream.c: Likewise. diff --git a/doc/posix-functions/open_memstream.texi b/doc/posix-functions/open_memstream.texi index ccee96d..43d37b7 100644 --- a/doc/posix-functions/open_memstream.texi +++ b/doc/posix-functions/open_memstream.texi @@ -4,16 +4,19 @@ open_memstream POSIX specification: @url{http://www.opengroup.org/onlinepubs/9699919799/functions/open_memstream.html} -Gnulib module: --- +Gnulib module: open_memstream Portability problems fixed by Gnulib: @itemize +...@item +This function is missing on some platforms: +MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8. @end itemize Portability problems not fixed by Gnulib: @itemize @item This function is missing on some platforms: -MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11, -IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. +AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Solaris 10, Cygwin 1.5.x, +mingw, Interix 3.5, BeOS. @end itemize diff --git a/lib/open_memstream.c b/lib/open_memstream.c new file mode 100644 index 0000000..4947b02 --- /dev/null +++ b/lib/open_memstream.c @@ -0,0 +1,191 @@ +/* Open a write stream around a malloc'd string. + Copyright (C) 2010 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + 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, see <http://www.gnu.org/licenses/>. */ + +/* Written by Eric Blake <e...@byu.net>, 2010. */ + +#include <config.h> + +/* Specification. */ +#include <stdio.h> + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "verify.h" + +#if !HAVE_FUNOPEN +# error Sorry, not ported to your platform yet +#else + +# define INITIAL_ALLOC 64 + +struct data +{ + char **buf; /* User's argument. */ + size_t *len; /* User's argument. Smaller of pos or eof. */ + size_t pos; /* Current position. */ + size_t eof; /* End-of-file position. */ + size_t allocated; /* Allocated size of *buf, always > eof. */ + char c; /* Temporary storage for byte overwritten by NUL, if pos < eof. */ +}; +typedef struct data data; + +/* Stupid BSD interface uses int/int instead of ssize_t/size_t. */ +verify (sizeof (int) <= sizeof (size_t)); +verify (sizeof (int) <= sizeof (ssize_t)); + +static int +mem_write (void *c, const char *buf, int n) +{ + data *cookie = c; + char *cbuf = *cookie->buf; + + /* Be sure we don't overflow. */ + if ((ssize_t) (cookie->pos + n) < 0) + { + errno = EFBIG; + return EOF; + } + /* Grow the buffer, if necessary. Use geometric growth to avoid + quadratic realloc behavior. Overallocate, to accomodate the + requirement to always place a trailing NUL not counted by length. + Thus, we want max(prev_size*1.5, cookie->pos+n+1). */ + if (cookie->allocated <= cookie->pos + n) + { + size_t newsize = cookie->allocated * 3 / 2; + if (newsize < cookie->pos + n + 1) + newsize = cookie->pos + n + 1; + cbuf = realloc (cbuf, newsize); + if (!cbuf) + return EOF; + *cookie->buf = cbuf; + cookie->allocated = newsize; + } + /* If we have previously done a seek beyond eof, ensure all + intermediate bytges are NUL. */ + if (cookie->eof < cookie->pos) + memset (cbuf + cookie->eof, '\0', cookie->pos - cookie->eof); + memcpy (cbuf + cookie->pos, buf, n); + cookie->pos += n; + /* If the user has previously written beyond the current position, + remember what the trailing NUL is overwriting. Otherwise, + extend the stream. */ + if (cookie->eof < cookie->pos) + cookie->eof = cookie->pos; + else + cookie->c = cbuf[cookie->pos]; + cbuf[cookie->pos] = '\0'; + *cookie->len = cookie->pos; + return n; +} + +static fpos_t +mem_seek (void *c, fpos_t pos, int whence) +{ + data *cookie = c; + off_t offset = pos; + + if (whence == SEEK_CUR) + offset += cookie->pos; + else if (whence == SEEK_END) + offset += cookie->eof; + if (offset < 0) + { + errno = EINVAL; + offset = -1; + } + else if ((size_t) offset != offset) + { + errno = ENOSPC; + offset = -1; + } + else + { + if (cookie->pos < cookie->eof) + { + (*cookie->buf)[cookie->pos] = cookie->c; + cookie->c = '\0'; + } + cookie->pos = offset; + if (cookie->pos < cookie->eof) + { + cookie->c = (*cookie->buf)[cookie->pos]; + (*cookie->buf)[cookie->pos] = '\0'; + *cookie->len = cookie->pos; + } + else + *cookie->len = cookie->eof; + } + return offset; +} + +static int +mem_close (void *c) +{ + data *cookie = c; + char *buf; + + /* Be nice and try to reduce excess memory. */ + buf = realloc (*cookie->buf, *cookie->len + 1); + if (buf) + *cookie->buf = buf; + free (cookie); + return 0; +} + +FILE * +open_memstream (char **buf, size_t *len) +{ + FILE *f; + data *cookie; + + if (!buf || !len) + { + errno = EINVAL; + return NULL; + } + if (!(cookie = malloc (sizeof *cookie))) + return NULL; + if (!(*buf = malloc (INITIAL_ALLOC))) + { + free (cookie); + errno = ENOMEM; + return NULL; + } + **buf = '\0'; + *len = 0; + + f = funopen (cookie, NULL, mem_write, mem_seek, mem_close); + if (!f) + { + int saved_errno = errno; + free (cookie); + errno = saved_errno; + } + else + { + cookie->buf = buf; + cookie->len = len; + cookie->pos = 0; + cookie->eof = 0; + cookie->c = '\0'; + cookie->allocated = INITIAL_ALLOC; + } + return f; +} +#endif /* HAVE_FUNOPEN */ diff --git a/lib/stdio.in.h b/lib/stdio.in.h index 2b09256..8b49774 100644 --- a/lib/stdio.in.h +++ b/lib/stdio.in.h @@ -595,6 +595,25 @@ _GL_CXXALIAS_SYS (obstack_vprintf, int, _GL_CXXALIASWARN (obstack_vprintf); #endif +#if @GNULIB_OPEN_MEMSTREAM@ +/* Open a seekable read-write stream around an arbitrary length + string, which starts with zero length. After any fflush or fclose, + *BUF contains the NUL-terminated stream contents, and *LEN contains + the current stream size. After closing the stream, call + free(*BUF). */ +# if !...@have_open_memstream@ +_GL_FUNCDECL_SYS (open_memstream, FILE *, (char **, size_t *)); +# endif +_GL_CXXALIAS_SYS (open_memstream, FILE *, (char **, size_t *)); +_GL_CXXALIASWARN (open_memstream); +#elif defined GNULIB_POSIXCHECK +# undef open_memstream +# if HAVE_RAW_DECL_OPEN_MEMSTREAM +_GL_WARN_ON_USE (open_memstream, "open_memstream is not always present - " + "use gnulib module open_memstream for portability"); +# endif +#endif + #if @GNULIB_PERROR@ /* Print a message to standard error, describing the value of ERRNO, (if STRING is not NULL and not empty) prefixed with STRING and ": ", diff --git a/m4/open_memstream.m4 b/m4/open_memstream.m4 new file mode 100644 index 0000000..148fe11 --- /dev/null +++ b/m4/open_memstream.m4 @@ -0,0 +1,26 @@ +# open_memstream.m4 serial 1 +dnl Copyright (C) 2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_FUNC_OPEN_MEMSTREAM], +[ + AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS]) + AC_REQUIRE([gl_STDIO_H_DEFAULTS]) + AC_CHECK_FUNCS_ONCE([open_memstream]) + if test "$ac_cv_func_open_memstream" = no; then + HAVE_OPEN_MEMSTREAM=0 + AC_LIBOBJ([open_memstream]) + gl_PREREQ_OPEN_MEMSTREAM + fi +]) + +# Prerequisites of lib/open_memstream.c. +AC_DEFUN([gl_PREREQ_OPEN_MEMSTREAM], +[ + AC_CHECK_FUNCS([funopen]) + if test "$ac_cv_func_funopen" = no; then + AC_MSG_ERROR([Sorry, open_memstream not yet ported to your platform.]) + fi +]) diff --git a/m4/stdio_h.m4 b/m4/stdio_h.m4 index 1d1d95e..539c6ca 100644 --- a/m4/stdio_h.m4 +++ b/m4/stdio_h.m4 @@ -1,4 +1,4 @@ -# stdio_h.m4 serial 30 +# stdio_h.m4 serial 31 dnl Copyright (C) 2007-2010 Free Software Foundation, Inc. dnl This file is free software; the Free Software Foundation dnl gives unlimited permission to copy and/or distribute it, @@ -36,8 +36,8 @@ AC_DEFUN([gl_STDIO_H], dnl corresponding gnulib module is not in use, and which is not dnl guaranteed by C89. gl_WARN_ON_USE_PREPARE([[#include <stdio.h> - ]], [dprintf fpurge fseeko ftello getdelim getline popen renameat - snprintf tmpfile vdprintf vsnprintf]) + ]], [dprintf fpurge fseeko ftello getdelim getline open_memstream popen + renameat snprintf tmpfile vdprintf vsnprintf]) ]) AC_DEFUN([gl_STDIO_MODULE_INDICATOR], @@ -70,6 +70,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS], GNULIB_GETLINE=0; AC_SUBST([GNULIB_GETLINE]) GNULIB_OBSTACK_PRINTF=0; AC_SUBST([GNULIB_OBSTACK_PRINTF]) GNULIB_OBSTACK_PRINTF_POSIX=0; AC_SUBST([GNULIB_OBSTACK_PRINTF_POSIX]) + GNULIB_OPEN_MEMSTREAM=0; AC_SUBST([GNULIB_OPEN_MEMSTREAM]) GNULIB_PERROR=0; AC_SUBST([GNULIB_PERROR]) GNULIB_POPEN=0; AC_SUBST([GNULIB_POPEN]) GNULIB_PRINTF=0; AC_SUBST([GNULIB_PRINTF]) @@ -102,6 +103,7 @@ AC_DEFUN([gl_STDIO_H_DEFAULTS], HAVE_DPRINTF=1; AC_SUBST([HAVE_DPRINTF]) HAVE_FSEEKO=1; AC_SUBST([HAVE_FSEEKO]) HAVE_FTELLO=1; AC_SUBST([HAVE_FTELLO]) + HAVE_OPEN_MEMSTREAM=1; AC_SUBST([HAVE_OPEN_MEMSTREAM]) HAVE_RENAMEAT=1; AC_SUBST([HAVE_RENAMEAT]) HAVE_VASPRINTF=1; AC_SUBST([HAVE_VASPRINTF]) HAVE_VDPRINTF=1; AC_SUBST([HAVE_VDPRINTF]) diff --git a/modules/open_memstream b/modules/open_memstream new file mode 100644 index 0000000..d789c1d --- /dev/null +++ b/modules/open_memstream @@ -0,0 +1,29 @@ +Description: +open_memstream() function: open a write stream around a malloc'd string + +Files: +lib/open_memstream.c +m4/open_memstream.m4 + +Depends-on: +extensions +malloc-posix +realloc-posix +stdio +unistd +verify + +configure.ac: +gl_FUNC_OPEN_MEMSTREAM +gl_STDIO_MODULE_INDICATOR([open_memstream]) + +Makefile.am: + +Include: +<stdio.h> + +License: +LGPLv2+ + +Maintainer: +Eric Blake diff --git a/modules/stdio b/modules/stdio index c982dac..5808933 100644 --- a/modules/stdio +++ b/modules/stdio @@ -47,6 +47,7 @@ stdio.h: stdio.in.h $(CXXDEFS_H) $(ARG_NONNULL_H) $(WARN_ON_USE_H) -e 's|@''GNULIB_GETLINE''@|$(GNULIB_GETLINE)|g' \ -e 's|@''GNULIB_OBSTACK_PRINTF''@|$(GNULIB_OBSTACK_PRINTF)|g' \ -e 's|@''GNULIB_OBSTACK_PRINTF_POSIX''@|$(GNULIB_OBSTACK_PRINTF_POSIX)|g' \ + -e 's|@''GNULIB_OPEN_MEMSTREAM''@|$(GNULIB_OPEN_MEMSTREAM)|g' \ -e 's|@''GNULIB_PERROR''@|$(GNULIB_PERROR)|g' \ -e 's|@''GNULIB_POPEN''@|$(GNULIB_POPEN)|g' \ -e 's|@''GNULIB_PRINTF''@|$(GNULIB_PRINTF)|g' \ @@ -79,6 +80,7 @@ stdio.h: stdio.in.h $(CXXDEFS_H) $(ARG_NONNULL_H) $(WARN_ON_USE_H) -e 's|@''HAVE_DPRINTF''@|$(HAVE_DPRINTF)|g' \ -e 's|@''HAVE_FSEEKO''@|$(HAVE_FSEEKO)|g' \ -e 's|@''HAVE_FTELLO''@|$(HAVE_FTELLO)|g' \ + -e 's|@''HAVE_OPEN_MEMSTREAM''@|$(HAVE_OPEN_MEMSTREAM)|g' \ -e 's|@''HAVE_RENAMEAT''@|$(HAVE_RENAMEAT)|g' \ -e 's|@''HAVE_VASPRINTF''@|$(HAVE_VASPRINTF)|g' \ -e 's|@''HAVE_VDPRINTF''@|$(HAVE_VDPRINTF)|g' \ -- 1.6.6.1