bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 2/2] open_memstream: new module


From: Eric Blake
Subject: [PATCH 2/2] open_memstream: new module
Date: Fri, 23 Apr 2010 16:45:23 -0600

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 <address@hidden>
---
 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  <address@hidden>

+       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
address@hidden
+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 <address@hidden>, 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 address@hidden@
+_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





reply via email to

[Prev in Thread] Current Thread [Next in Thread]