This is the mail archive of the newlib@sourceware.org mailing list for the newlib project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Implement fmemopen


POSIX 200x (the next revision of POSIX) will include fmemopen.  And while it 
can now be implemented in user-space (via my previous fopencookie addition), it 
would be nice to implement it directly in newlib.

Here's a program I was using to test it.
$ cat foo.c
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>

static void
dump(const unsigned char *buf, size_t len)
{
   if (!buf)
   {
      puts("no pointer available to buf");
      return;
   }
   printf("buf len %zu, strnlen %zu, contents \"", len, strnlen(buf, len));
   do
      switch (*buf)
      {
      case '\r':
         putchar('\\');
         putchar('r');
         break;
      case '\n':
         putchar('\\');
         putchar('n');
         break;
      case '\t':
         putchar('\\');
         putchar('t');
         break;
      case '\\':
         putchar('\\');
         putchar('\\');
         break;
      case '"':
         putchar('\\');
         putchar('"');
         break;
      default:
         if (*buf < ' ' || *buf >= '\x7f')
         {
            const char x[] = "0123456789abcdef";
            putchar('\\');
            putchar('x');
            putchar(x[*buf >> 4]);
            putchar(x[*buf & 0xf]);
         }
         else
            putchar(*buf);
      }
   while (buf++, --len);
   puts("\"");
}

int
main(int argc, char **argv)
{
   if (argc < 3)
   {
      puts("Usage: foo <size> <mode> [<buf>]");
      return 1;
   }
   char *buf = NULL;
   size_t len = atoi(argv[1]), dumplen = len;
   if (argv[3])
   {
      if (strlen(argv[3]) >= len)
      {
         buf = strdup(argv[3]);
         dumplen = strlen(buf);
      }
      else
      {
         buf = malloc(len);
         memcpy(buf, argv[3], len + 1);
      }
   }
   errno = 0;
   FILE *f = fmemopen(buf, len, argv[2]);
   printf("fmemopen status %p, errno %d %s\n",
          f, errno, strerror(errno));
   int err;
   while (1)
   {
      fputs("\
(r)ead, (w)rite, (s)eek, (d)ump, (f)lush, f(e)rror, (c)learerr, or (q)uit? ",
            stdout);
      fflush(stdout);
      int c;
      int opt = getchar();
      while (c = getchar(), c != EOF && c != '\n');
      if (!isatty(STDIN_FILENO))
         puts("");
      switch (opt)
      {
      case 'r':
      case 'R':
         errno = 0;
         c = fgetc(f);
         if (c == EOF)
            printf("fgetc returned EOF, errno %d %s\n",
                   errno, strerror(errno));
         else
            printf("fgetc read \\x%02x('%c'), errno %d %s\n",
                   c, isprint(c) ? c : '?', errno, strerror(errno));
         break;
      case 'w':
      case 'W':
         fputs("char to write? ", stdout);
         fflush(stdout);
         c = getchar();
         while (opt = getchar(), opt != EOF && opt != '\n');
         if (!isatty(STDIN_FILENO))
            puts("");
         errno = 0;
         err = fputc(c, f);
         if (err == EOF)
            printf("fputc returned EOF, errno %d %s\n",
                   errno, strerror(errno));
         else
            printf("fputc write \\x%02x('%c'), errno %d %s\n",
                   c, isprint(c) ? c : '?', errno, strerror(errno));
         break;
      case 's':
      case 'S':
         {
            int o = 0;
            int w = 0;
            fputs("offset and whence? ", stdout);
            fflush(stdout);
            scanf("%d %d", &o, &w);
            while (c = getchar(), c != EOF && c != '\n');
            if (!isatty(STDIN_FILENO))
               puts("");
            errno = 0;
            err = fseek(f, o, w == 0 ? SEEK_SET : w == 1 ? SEEK_CUR : SEEK_END);
            printf("fseek status %d, errno %d %s\n",
                   err, errno, strerror(errno));
            errno = 0;
            err = ftell(f);
            printf("ftell status %d, errno %d %s\n",
                   err, errno, strerror(errno));
         }
         break;
      case 'd':
      case 'D':
         dump(buf, dumplen);
         break;
      case 'f':
      case 'F':
         errno = 0;
         err = fflush(f);
         printf("fflush status %d, errno %d %s\n",
                err, errno, strerror(errno));
         break;
      case 'e':
      case 'E':
         printf("ferror status %d\n", ferror(f));
         break;
      case 'c':
      case 'C':
         clearerr(f);
         printf("clearerr called\n");
         break;
      case 'q':
      case 'Q':
      case EOF:
         errno = 0;
         err = fclose(f);
         printf("fclose status %d, errno %d %s\n",
                err, errno, strerror(errno));
         dump(buf, dumplen);
         free(buf);
         return 0;
      default:
         puts("unrecognized command");
      }
   }
}


My implementation of fmemopen is even more reliable than glibc (at least as of 
glibc 2.3), since on the Linux machine where I tested:

$ ./foo 2 a 123
fmemopen status 0x9edc038, errno 0 Success
(r)ead, (w)rite, (s)eek, (d)ump, (f)lush, f(e)rror, (c)learerr, or (q)uit? d
buf len 3, strnlen 3, contents "123"
(r)ead, (w)rite, (s)eek, (d)ump, (f)lush, f(e)rror, (c)learerr, or (q)uit? s
offset and whence? 0 1
fseek status -1, errno 0 Success
[oops - glibc reported failure, but didn't set errno]
ftell status -1, errno 5 Input/output error
[oops - glibc didn't report the correct offset of 2]
(r)ead, (w)rite, (s)eek, (d)ump, (f)lush, f(e)rror, (c)learerr, or (q)uit? w
char to write? a
fputc write \x61('a'), errno 0 Success
(r)ead, (w)rite, (s)eek, (d)ump, (f)lush, f(e)rror, (c)learerr, or (q)uit? f
Segmentation fault
[oops - glibc dumps core!]

OK to commit?  open_memstream will be next.

2007-07-18  Eric Blake  <ebb9@byu.net>

	Implement fmemopen.
	* libc/stdio/Makefile.am (ELIX_4_SOURCES, CHEWOUT_FILES): Add
	fmemopen.
	* libc/stdio/fmemopen.c (_fmemopen_r, fmemopen): New file.
	* libc/include/stdio.h (fmemopen): Declare it.
	* libc/stdio/stdio.tex: Document it.

Index: libc/include/stdio.h
===================================================================
RCS file: /cvs/src/src/newlib/libc/include/stdio.h,v
retrieving revision 1.47
diff -u -p -r1.47 stdio.h
--- libc/include/stdio.h	13 Jul 2007 20:37:53 -0000	1.47
+++ libc/include/stdio.h	18 Jul 2007 22:11:40 -0000
@@ -306,7 +306,7 @@ int	_EXFUN(vsscanf, (const char *, const
 #endif /* !__STRICT_ANSI__ */
 
 /*
- * Routines in POSIX 1003.1.
+ * Routines in POSIX 1003.1:2001.
  */
 
 #ifndef __STRICT_ANSI__
@@ -330,6 +330,16 @@ int	_EXFUN(putchar_unlocked, (int));
 #endif /* ! __STRICT_ANSI__ */
 
 /*
+ * Routines in POSIX 1003.1:200x.
+ */
+
+#ifndef __STRICT_ANSI__
+# ifndef _REENT_ONLY
+FILE *	_EXFUN(fmemopen, (void *, size_t, const char *));
+# endif
+#endif
+
+/*
  * Recursive versions of the above.
  */
 
@@ -354,6 +364,7 @@ int	_EXFUN(_fiprintf_r, (struct _reent *
                _ATTRIBUTE ((__format__ (__printf__, 3, 4))));
 int	_EXFUN(_fiscanf_r, (struct _reent *, FILE *, const char *, ...)
                _ATTRIBUTE ((__format__ (__scanf__, 3, 4))));
+FILE *	_EXFUN(_fmemopen_r, (struct _reent *, void *, size_t, const char *));
 FILE *	_EXFUN(_fopen_r, (struct _reent *, const char *, const char *));
 int	_EXFUN(_fprintf_r, (struct _reent *, FILE *, const char *, ...)
                _ATTRIBUTE ((__format__ (__printf__, 3, 4))));
Index: libc/stdio/Makefile.am
===================================================================
RCS file: /cvs/src/src/newlib/libc/stdio/Makefile.am,v
retrieving revision 1.26
diff -u -p -r1.26 Makefile.am
--- libc/stdio/Makefile.am	13 Jul 2007 17:07:28 -0000	1.26
+++ libc/stdio/Makefile.am	18 Jul 2007 22:11:40 -0000
@@ -117,6 +117,7 @@ ELIX_4_SOURCES = \
 	asnprintf.c		\
 	diprintf.c		\
 	dprintf.c		\
+	fmemopen.c		\
 	fopencookie.c		\
 	funopen.c		\
 	vasniprintf.c		\
@@ -179,6 +180,7 @@ CHEWOUT_FILES = \
 	fgetpos.def		\
 	fgets.def		\
 	fileno.def		\
+	fmemopen.def		\
 	fopen.def		\
 	fopencookie.def		\
 	fputc.def		\
@@ -244,6 +246,7 @@ $(lpfx)fclose.$(oext): local.h
 $(lpfx)fdopen.$(oext): local.h
 $(lpfx)fflush.$(oext): local.h
 $(lpfx)findfp.$(oext): local.h
+$(lpfx)fmemopen.$(oext): local.h
 $(lpfx)fopen.$(oext): local.h
 $(lpfx)fopencookie.$(oext): local.h
 $(lpfx)fputs.$(oext): fvwrite.h
Index: libc/stdio/fmemopen.c
===================================================================
RCS file: libc/stdio/fmemopen.c
diff -N libc/stdio/fmemopen.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ libc/stdio/fmemopen.c	18 Jul 2007 22:11:40 -0000
@@ -0,0 +1,330 @@
+/* Copyright (C) 2007 Eric Blake
+ * Permission to use, copy, modify, and distribute this software
+ * is freely granted, provided that this notice is preserved.
+ */
+
+/*
+FUNCTION
+<<fmemopen>>---open a stream around a fixed-length string
+
+INDEX
+	fmemopen
+
+ANSI_SYNOPSIS
+	#include <stdio.h>
+	FILE *fmemopen(void *restrict <[buf]>, size_t <[size]>,
+		       const char *restrict <[mode]>);
+
+DESCRIPTION
+<<fmemopen>> creates a seekable <<FILE>> stream that wraps a
+fixed-length buffer of <[size]> bytes starting at <[buf]>.  The stream
+is opened with <[mode]> treated as in <<fopen>>, where append mode
+starts writing at the first NUL byte.  If <[buf]> is NULL, then
+<[size]> bytes are automatically provided as if by <<malloc>>, with
+the initial size of 0, and <[mode]> must contain <<+>> so that data
+can be read after it is written.
+
+The stream maintains a current position, which moves according to
+bytes read or written, and which can be one past the end of the array.
+The stream also maintains a current file size, which is never greater
+than <[size]>.  If <[mode]> starts with <<r>>, the position starts at
+<<0>>, and file size starts at <[size]> if <[buf]> was provided.  If
+<[mode]> starts with <<w>>, the position and file size start at <<0>>,
+and if <[buf]> was provided, the first byte is set to NUL.  If
+<[mode]> starts with <<a>>, the position and file size start at the
+location of the first NUL byte, or else <[size]> if <[buf]> was
+provided.
+
+When reading, NUL bytes have no significance, and reads cannot exceed
+the current file size.  When writing, the file size can increase up to
+<[size]> as needed, and NUL bytes may be embedded in the stream.  When
+the stream is flushed or closed after a write that changed the file
+size, a NUL byte is written at the current position if there is still
+room; if the stream is not also open for reading, a NUL byte is
+additionally written at the last byte of <[buf]> when the stream has
+exceeded <[size]>, so that a write-only <[buf]> is always
+NUL-terminated when the stream is flushed or closed.  It is not
+possible to seek outside the bounds of <[size]>.
+
+RETURNS
+The return value is an open FILE pointer on success.  On error,
+<<NULL>> is returned, and <<errno>> will be set to EINVAL if <[size]>
+is zero or <[mode]> is invalid, ENOMEM if <[buf]> was NULL and memory
+could not be allocated, or EMFILE if too many streams are already
+open.
+
+PORTABILITY
+This function is being added to POSIX 200x, but is not in POSIX 2001.
+
+Supporting OS subroutines required: <<sbrk>>.
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/lock.h>
+#include "local.h"
+
+/* Describe details of an open memstream.  */
+typedef struct fmemcookie {
+  void *storage; /* storage to free on close */
+  char *buf; /* buffer start */
+  size_t pos; /* current position */
+  size_t eof; /* current file size */
+  size_t max; /* maximum file size */
+  char append; /* nonzero if appending */
+  char writeonly; /* nonzero if write-only */
+} fmemcookie;
+
+/* Read up to non-zero N bytes into BUF from stream described by
+   COOKIE; return number of bytes read (0 on EOF).  */
+static _READ_WRITE_RETURN_TYPE
+_DEFUN(fmemreader, (ptr, cookie, buf, n),
+       struct _reent *ptr _AND
+       void *cookie _AND
+       char *buf _AND
+       int n)
+{
+  fmemcookie *c = (fmemcookie *) cookie;
+  /* Can't read beyond current size, but EOF condition is not an error.  */
+  if (n >= c->eof - c->pos)
+    n = c->eof - c->pos;
+  memcpy (buf, c->buf + c->pos, n);
+  c->pos += n;
+  return n;
+}
+
+/* Write up to non-zero N bytes of BUF into the stream described by COOKIE,
+   returning the number of bytes written or EOF on failure.  */
+static _READ_WRITE_RETURN_TYPE
+_DEFUN(fmemwriter, (ptr, cookie, buf, n),
+       struct _reent *ptr _AND
+       void *cookie _AND
+       const char *buf _AND
+       int n)
+{
+  fmemcookie *c = (fmemcookie *) cookie;
+  int adjust = 0; /* true if at EOF, but still need to write NUL.  */
+  if (c->append)
+    c->pos = c->eof;
+  /* Do not write beyond EOF; saving room for NUL on write-only stream.  */
+  if (c->pos + n > c->max - c->writeonly)
+    {
+      adjust = c->writeonly;
+      n = c->max - c->pos;
+    }
+  /* Now n is the number of bytes being modified, and adjust is 1 if the
+     last byte is NUL instead of from buf.  Write a NUL if write-only;
+     or if read-write, eof changed, and there is still room.  */
+  if (c->pos + n > c->eof)
+    {
+      c->eof = c->pos + n;
+      if (c->eof - adjust < c->max)
+	c->buf[c->eof - adjust] = '\0';
+    }
+  else if (c->writeonly)
+    {
+      if (n)
+	c->buf[c->pos + n - adjust] = '\0';
+      else
+	adjust = 0;
+    }
+  c->pos += n;
+  if (n - adjust)
+    memcpy (c->buf + c->pos - n, buf, n - adjust);
+  else
+    {
+      ptr->_errno = ENOSPC;
+      return EOF;
+    }
+  return n;
+}
+
+/* Seek to position POS relative to WHENCE within stream described by
+   COOKIE; return resulting position or fail with EOF.  */
+static _fpos_t
+_DEFUN(fmemseeker, (ptr, cookie, pos, whence),
+       struct _reent *ptr _AND
+       void *cookie _AND
+       _fpos_t pos _AND
+       int whence)
+{
+  fmemcookie *c = (fmemcookie *) cookie;
+#ifndef __LARGE64_FILES
+  off_t offset = (off_t) pos;
+#else /* __LARGE64_FILES */
+  _off64_t offset = (_off64_t) pos;
+#endif /* __LARGE64_FILES */
+
+  if (whence == SEEK_CUR)
+    offset += c->pos;
+  else if (whence == SEEK_END)
+    offset += c->eof;
+  if (offset < 0)
+    {
+      ptr->_errno = EINVAL;
+      offset = -1;
+    }
+  else if (offset > c->max)
+    {
+      ptr->_errno = ENOSPC;
+      offset = -1;
+    }
+  else
+    c->pos = offset;
+#ifdef __LARGE64_FILES
+  if ((_fpos_t)offset != offset)
+    {
+      ptr->_errno = EOVERFLOW;
+      offset = -1;
+    }
+#endif /* __LARGE64_FILES */
+  return (_fpos_t) offset;
+}
+
+/* Seek to position POS relative to WHENCE within stream described by
+   COOKIE; return resulting position or fail with EOF.  */
+#ifdef __LARGE64_FILES
+static _fpos64_t
+_DEFUN(fmemseeker64, (ptr, cookie, pos, whence),
+       struct _reent *ptr _AND
+       void *cookie _AND
+       _fpos64_t pos _AND
+       int whence)
+{
+  _off64_t offset = (_off64_t) pos;
+  fmemcookie *c = (fmemcookie *) cookie;
+  if (whence == SEEK_CUR)
+    offset += c->pos;
+  else if (whence == SEEK_END)
+    offset += c->eof;
+  if (offset < 0)
+    {
+      ptr->_errno = EINVAL;
+      offset = -1;
+    }
+  else if (offset > c->max)
+    {
+      ptr->_errno = ENOSPC;
+      offset = -1;
+    }
+  else
+    c->pos = offset;
+  return (_fpos64_t) offset;
+}
+#endif /* __LARGE64_FILES */
+
+/* Reclaim resources used by stream described by COOKIE.  */
+static int
+_DEFUN(fmemcloser, (ptr, cookie),
+       struct _reent *ptr _AND
+       void *cookie)
+{
+  fmemcookie *c = (fmemcookie *) cookie;
+  _free_r (ptr, c->storage);
+  return 0;
+}
+
+/* Open a memstream around buffer BUF of SIZE bytes, using MODE.
+   Return the new stream, or fail with NULL.  */
+FILE *
+_DEFUN(_fmemopen_r, (ptr, buf, size, mode),
+       struct _reent *ptr _AND
+       void *buf _AND
+       size_t size _AND
+       const char *mode)
+{
+  FILE *fp;
+  fmemcookie *c;
+  int flags;
+  int dummy;
+
+  if ((flags = __sflags (ptr, mode, &dummy)) == 0)
+    return NULL;
+  if (!size || !(buf || flags & __SAPP))
+    {
+      ptr->_errno = EINVAL;
+      return NULL;
+    }
+  if ((fp = __sfp (ptr)) == NULL)
+    return NULL;
+  if ((c = (fmemcookie *) _malloc_r (ptr, sizeof *c + (buf ? 0 : size)))
+      == NULL)
+    {
+      __sfp_lock_acquire ();
+      fp->_flags = 0;		/* release */
+#ifndef __SINGLE_THREAD__
+      __lock_close_recursive (fp->_lock);
+#endif
+      __sfp_lock_release ();
+      return NULL;
+    }
+
+  c->storage = c;
+  c->max = size;
+  /* 9 modes to worry about.  */
+  /* w/a, buf or no buf: Guarantee a NUL after any file writes.  */
+  c->writeonly = (flags & __SWR) != 0;
+  if (!buf)
+    {
+      /* r+/w+/a+, and no buf: file starts empty.  */
+      c->buf = (char *) (c + 1);
+      *(char *) buf = '\0';
+      c->pos = c->eof = 0;
+      c->append = (flags & __SAPP) != 0;
+    }
+  else
+    {
+      c->buf = (char *) buf;
+      switch (*mode)
+	{
+	case 'a':
+	  /* a/a+ and buf: position and size at first NUL.  */
+	  buf = memchr (c->buf, '\0', size);
+	  c->eof = c->pos = buf ? (char *) buf - c->buf : size;
+	  if (!buf && c->writeonly)
+	    /* a: guarantee a NUL within size even if no writes.  */
+	    c->buf[size - 1] = '\0';
+	  c->append = 1;
+	  break;
+	case 'r':
+	  /* r/r+ and buf: read at beginning, full size available.  */
+	  c->pos = c->append = 0;
+	  c->eof = size;
+	  break;
+	case 'w':
+	  /* w/w+ and buf: write at beginning, truncate to empty.  */
+	  c->pos = c->append = c->eof = 0;
+	  *c->buf = '\0';
+	  break;
+	default:
+	  abort ();
+	}
+    }
+
+  _flockfile (fp);
+  fp->_file = -1;
+  fp->_flags = flags;
+  fp->_cookie = c;
+  fp->_read = fmemreader;
+  fp->_write = fmemwriter;
+  fp->_seek = fmemseeker;
+#ifdef __LARGE64_FILES
+  fp->_seek64 = fmemseeker64;
+  fp->_flags |= __SL64;
+#endif
+  fp->_close = fmemcloser;
+  _funlockfile (fp);
+  return fp;
+}
+
+#ifndef _REENT_ONLY
+FILE *
+_DEFUN(fmemopen, (buf, size, mode),
+       void *buf _AND
+       size_t size _AND
+       const char *mode)
+{
+  return _fmemopen_r (_REENT, buf, size, mode);
+}
+#endif /* !_REENT_ONLY */
Index: libc/stdio/stdio.tex
===================================================================
RCS file: /cvs/src/src/newlib/libc/stdio/stdio.tex,v
retrieving revision 1.8
diff -u -p -r1.8 stdio.tex
--- libc/stdio/stdio.tex	13 Jul 2007 17:07:28 -0000	1.8
+++ libc/stdio/stdio.tex	18 Jul 2007 22:11:40 -0000
@@ -37,6 +37,7 @@ structure.
 * fgetpos::     Record position in a stream or file
 * fgets::       Get character string from a file or stream
 * fileno::      Get file descriptor associated with stream
+* fmemopen::    Open a stream around a fixed-length buffer
 * fopen::       Open a file
 * fopencookie:: Open a stream with custom callbacks
 * fputc::       Write a character on a stream or file
@@ -124,6 +125,9 @@ structure.
 @include stdio/fileno.def
 
 @page
+@include stdio/fmemopen.def
+
+@page
 @include stdio/fopen.def
 
 @page



Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]