This is the mail archive of the
newlib@sourceware.org
mailing list for the newlib project.
Implement fmemopen
- From: Eric Blake <ebb9 at byu dot net>
- To: newlib at sources dot redhat dot com
- Date: Wed, 18 Jul 2007 23:04:35 +0000 (UTC)
- Subject: 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