Add POSIX-2008 fmemopen, open_memstream, and open_wmemstream.
Bug: 17164505
Change-Id: I59e28a08ff8b6ab632230b11a5807cfd5278aeb5
diff --git a/libc/Android.mk b/libc/Android.mk
index c268a87..487cbd8 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -391,6 +391,7 @@
upstream-openbsd/lib/libc/stdio/fgetws.c \
upstream-openbsd/lib/libc/stdio/fileno.c \
upstream-openbsd/lib/libc/stdio/findfp.c \
+ upstream-openbsd/lib/libc/stdio/fmemopen.c \
upstream-openbsd/lib/libc/stdio/fprintf.c \
upstream-openbsd/lib/libc/stdio/fpurge.c \
upstream-openbsd/lib/libc/stdio/fputc.c \
@@ -419,6 +420,8 @@
upstream-openbsd/lib/libc/stdio/getwchar.c \
upstream-openbsd/lib/libc/stdio/makebuf.c \
upstream-openbsd/lib/libc/stdio/mktemp.c \
+ upstream-openbsd/lib/libc/stdio/open_memstream.c \
+ upstream-openbsd/lib/libc/stdio/open_wmemstream.c \
upstream-openbsd/lib/libc/stdio/perror.c \
upstream-openbsd/lib/libc/stdio/printf.c \
upstream-openbsd/lib/libc/stdio/putc.c \
diff --git a/libc/include/stdio.h b/libc/include/stdio.h
index 8727a9f..ce60fd7 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -325,6 +325,11 @@
int putchar_unlocked(int);
#endif /* __POSIX_VISIBLE >= 199506 */
+#if __POSIX_VISIBLE >= 200809
+FILE* fmemopen(void*, size_t, const char*);
+FILE* open_memstream(char**, size_t*);
+#endif /* __POSIX_VISIBLE >= 200809 */
+
__END_DECLS
#endif /* __BSD_VISIBLE || __POSIX_VISIBLE || __XPG_VISIBLE */
diff --git a/libc/include/wchar.h b/libc/include/wchar.h
index 1898c7e..ae10d93 100644
--- a/libc/include/wchar.h
+++ b/libc/include/wchar.h
@@ -166,6 +166,7 @@
extern wctrans_t wctrans(const char*);
#if __POSIX_VISIBLE >= 200809
+FILE* open_wmemstream(wchar_t**, size_t*);
wchar_t* wcsdup(const wchar_t*);
size_t wcsnlen(const wchar_t*, size_t);
#endif
diff --git a/libc/upstream-openbsd/lib/libc/stdio/fmemopen.c b/libc/upstream-openbsd/lib/libc/stdio/fmemopen.c
new file mode 100644
index 0000000..8cda047
--- /dev/null
+++ b/libc/upstream-openbsd/lib/libc/stdio/fmemopen.c
@@ -0,0 +1,183 @@
+/* $OpenBSD: fmemopen.c,v 1.2 2013/03/27 15:06:25 mpi Exp $ */
+
+/*
+ * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
+ * Copyright (c) 2009 Ted Unangst
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "local.h"
+
+struct state {
+ char *string; /* actual stream */
+ size_t pos; /* current position */
+ size_t size; /* allocated size */
+ size_t len; /* length of the data */
+ int update; /* open for update */
+};
+
+static int
+fmemopen_read(void *v, char *b, int l)
+{
+ struct state *st = v;
+ int i;
+
+ for (i = 0; i < l && i + st->pos < st->len; i++)
+ b[i] = st->string[st->pos + i];
+ st->pos += i;
+
+ return (i);
+}
+
+static int
+fmemopen_write(void *v, const char *b, int l)
+{
+ struct state *st = v;
+ int i;
+
+ for (i = 0; i < l && i + st->pos < st->size; i++)
+ st->string[st->pos + i] = b[i];
+ st->pos += i;
+
+ if (st->pos >= st->len) {
+ st->len = st->pos;
+
+ if (st->len < st->size)
+ st->string[st->len] = '\0';
+ else if (!st->update)
+ st->string[st->size - 1] = '\0';
+ }
+
+ return (i);
+}
+
+static fpos_t
+fmemopen_seek(void *v, fpos_t off, int whence)
+{
+ struct state *st = v;
+ ssize_t base = 0;
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ base = st->pos;
+ break;
+ case SEEK_END:
+ base = st->len;
+ break;
+ }
+
+ if (off > st->size - base || off < -base) {
+ errno = EOVERFLOW;
+ return (-1);
+ }
+
+ st->pos = base + off;
+
+ return (st->pos);
+}
+
+static int
+fmemopen_close(void *v)
+{
+ free(v);
+
+ return (0);
+}
+
+static int
+fmemopen_close_free(void *v)
+{
+ struct state *st = v;
+
+ free(st->string);
+ free(st);
+
+ return (0);
+}
+
+FILE *
+fmemopen(void *buf, size_t size, const char *mode)
+{
+ struct state *st;
+ FILE *fp;
+ int flags, oflags;
+
+ if (size == 0) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((flags = __sflags(mode, &oflags)) == 0) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if (buf == NULL && ((oflags & O_RDWR) == 0)) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((st = malloc(sizeof(*st))) == NULL)
+ return (NULL);
+
+ if ((fp = __sfp()) == NULL) {
+ free(st);
+ return (NULL);
+ }
+
+ st->pos = 0;
+ st->len = (oflags & O_WRONLY) ? 0 : size;
+ st->size = size;
+ st->update = oflags & O_RDWR;
+
+ if (buf == NULL) {
+ if ((st->string = malloc(size)) == NULL) {
+ free(st);
+ fp->_flags = 0;
+ return (NULL);
+ }
+ *st->string = '\0';
+ } else {
+ st->string = (char *)buf;
+
+ if (oflags & O_TRUNC)
+ *st->string = '\0';
+
+ if (oflags & O_APPEND) {
+ char *p;
+
+ if ((p = memchr(st->string, '\0', size)) != NULL)
+ st->pos = st->len = (p - st->string);
+ else
+ st->pos = st->len = size;
+ }
+ }
+
+ fp->_flags = (short)flags;
+ fp->_file = -1;
+ fp->_cookie = (void *)st;
+ fp->_read = (flags & __SWR) ? NULL : fmemopen_read;
+ fp->_write = (flags & __SRD) ? NULL : fmemopen_write;
+ fp->_seek = fmemopen_seek;
+ fp->_close = (buf == NULL) ? fmemopen_close_free : fmemopen_close;
+
+ return (fp);
+}
diff --git a/libc/upstream-openbsd/lib/libc/stdio/open_memstream.c b/libc/upstream-openbsd/lib/libc/stdio/open_memstream.c
new file mode 100644
index 0000000..4610535
--- /dev/null
+++ b/libc/upstream-openbsd/lib/libc/stdio/open_memstream.c
@@ -0,0 +1,158 @@
+/* $OpenBSD: open_memstream.c,v 1.3 2013/04/03 03:11:53 guenther Exp $ */
+
+/*
+ * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "local.h"
+
+struct state {
+ char *string; /* actual stream */
+ char **pbuf; /* point to the stream */
+ size_t *psize; /* point to min(pos, len) */
+ size_t pos; /* current position */
+ size_t size; /* number of allocated char */
+ size_t len; /* length of the data */
+};
+
+static int
+memstream_write(void *v, const char *b, int l)
+{
+ struct state *st = v;
+ char *p;
+ size_t i, end;
+
+ end = (st->pos + l);
+
+ if (end >= st->size) {
+ /* 1.6 is (very) close to the golden ratio. */
+ size_t sz = st->size * 8 / 5;
+
+ if (sz < end + 1)
+ sz = end + 1;
+ p = realloc(st->string, sz);
+ if (!p)
+ return (-1);
+ bzero(p + st->size, sz - st->size);
+ *st->pbuf = st->string = p;
+ st->size = sz;
+ }
+
+ for (i = 0; i < l; i++)
+ st->string[st->pos + i] = b[i];
+ st->pos += l;
+
+ if (st->pos > st->len) {
+ st->len = st->pos;
+ st->string[st->len] = '\0';
+ }
+
+ *st->psize = st->pos;
+
+ return (i);
+}
+
+static fpos_t
+memstream_seek(void *v, fpos_t off, int whence)
+{
+ struct state *st = v;
+ ssize_t base = 0;
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ base = st->pos;
+ break;
+ case SEEK_END:
+ base = st->len;
+ break;
+ }
+
+ if (off > SIZE_MAX - base || off < -base) {
+ errno = EOVERFLOW;
+ return (-1);
+ }
+
+ st->pos = base + off;
+ *st->psize = MIN(st->pos, st->len);
+
+ return (st->pos);
+}
+
+static int
+memstream_close(void *v)
+{
+ struct state *st = v;
+
+ free(st);
+
+ return (0);
+}
+
+FILE *
+open_memstream(char **pbuf, size_t *psize)
+{
+ struct state *st;
+ FILE *fp;
+
+ if (pbuf == NULL || psize == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((st = malloc(sizeof(*st))) == NULL)
+ return (NULL);
+
+ if ((fp = __sfp()) == NULL) {
+ free(st);
+ return (NULL);
+ }
+
+ st->size = BUFSIZ;
+ if ((st->string = calloc(1, st->size)) == NULL) {
+ free(st);
+ fp->_flags = 0;
+ return (NULL);
+ }
+
+ *st->string = '\0';
+ st->pos = 0;
+ st->len = 0;
+ st->pbuf = pbuf;
+ st->psize = psize;
+
+ *pbuf = st->string;
+ *psize = st->len;
+
+ fp->_flags = __SWR;
+ fp->_file = -1;
+ fp->_cookie = st;
+ fp->_read = NULL;
+ fp->_write = memstream_write;
+ fp->_seek = memstream_seek;
+ fp->_close = memstream_close;
+ _SET_ORIENTATION(fp, -1);
+
+ return (fp);
+}
diff --git a/libc/upstream-openbsd/lib/libc/stdio/open_wmemstream.c b/libc/upstream-openbsd/lib/libc/stdio/open_wmemstream.c
new file mode 100644
index 0000000..9414187
--- /dev/null
+++ b/libc/upstream-openbsd/lib/libc/stdio/open_wmemstream.c
@@ -0,0 +1,169 @@
+/* $OpenBSD: open_wmemstream.c,v 1.3 2014/03/06 07:28:21 gerhard Exp $ */
+
+/*
+ * Copyright (c) 2011 Martin Pieuchot <mpi@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include "local.h"
+
+struct state {
+ wchar_t *string; /* actual stream */
+ wchar_t **pbuf; /* point to the stream */
+ size_t *psize; /* point to min(pos, len) */
+ size_t pos; /* current position */
+ size_t size; /* number of allocated wchar_t */
+ size_t len; /* length of the data */
+ mbstate_t mbs; /* conversion state of the stream */
+};
+
+static int
+wmemstream_write(void *v, const char *b, int l)
+{
+ struct state *st = v;
+ wchar_t *p;
+ size_t nmc, len, end;
+
+ end = (st->pos + l);
+
+ if (end >= st->size) {
+ /* 1.6 is (very) close to the golden ratio. */
+ size_t sz = st->size * 8 / 5;
+
+ if (sz < end + 1)
+ sz = end + 1;
+ p = realloc(st->string, sz * sizeof(wchar_t));
+ if (!p)
+ return (-1);
+ bzero(p + st->size, (sz - st->size) * sizeof(wchar_t));
+ *st->pbuf = st->string = p;
+ st->size = sz;
+ }
+
+ nmc = (st->size - st->pos) * sizeof(wchar_t);
+ len = mbsnrtowcs(st->string + st->pos, &b, nmc, l, &st->mbs);
+ if (len == (size_t)-1)
+ return (-1);
+ st->pos += len;
+
+ if (st->pos > st->len) {
+ st->len = st->pos;
+ st->string[st->len] = L'\0';
+ }
+
+ *st->psize = st->pos;
+
+ return (len);
+}
+
+static fpos_t
+wmemstream_seek(void *v, fpos_t off, int whence)
+{
+ struct state *st = v;
+ ssize_t base = 0;
+
+ switch (whence) {
+ case SEEK_SET:
+ break;
+ case SEEK_CUR:
+ base = st->pos;
+ break;
+ case SEEK_END:
+ base = st->len;
+ break;
+ }
+
+ if (off > (SIZE_MAX / sizeof(wchar_t)) - base || off < -base) {
+ errno = EOVERFLOW;
+ return (-1);
+ }
+
+ /*
+ * XXX Clearing mbs here invalidates shift state for state-
+ * dependent encodings, but they are not (yet) supported.
+ */
+ bzero(&st->mbs, sizeof(st->mbs));
+
+ st->pos = base + off;
+ *st->psize = MIN(st->pos, st->len);
+
+ return (st->pos);
+}
+
+static int
+wmemstream_close(void *v)
+{
+ struct state *st = v;
+
+ free(st);
+
+ return (0);
+}
+
+FILE *
+open_wmemstream(wchar_t **pbuf, size_t *psize)
+{
+ struct state *st;
+ FILE *fp;
+
+ if (pbuf == NULL || psize == NULL) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ if ((st = malloc(sizeof(*st))) == NULL)
+ return (NULL);
+
+ if ((fp = __sfp()) == NULL) {
+ free(st);
+ return (NULL);
+ }
+
+ st->size = BUFSIZ * sizeof(wchar_t);
+ if ((st->string = calloc(1, st->size)) == NULL) {
+ free(st);
+ fp->_flags = 0;
+ return (NULL);
+ }
+
+ *st->string = L'\0';
+ st->pos = 0;
+ st->len = 0;
+ st->pbuf = pbuf;
+ st->psize = psize;
+ bzero(&st->mbs, sizeof(st->mbs));
+
+ *pbuf = st->string;
+ *psize = st->len;
+
+ fp->_flags = __SWR;
+ fp->_file = -1;
+ fp->_cookie = st;
+ fp->_read = NULL;
+ fp->_write = wmemstream_write;
+ fp->_seek = wmemstream_seek;
+ fp->_close = wmemstream_close;
+ _SET_ORIENTATION(fp, 1);
+
+ return (fp);
+}
diff --git a/tests/stdio_test.cpp b/tests/stdio_test.cpp
index bb86509..2cd0df2 100644
--- a/tests/stdio_test.cpp
+++ b/tests/stdio_test.cpp
@@ -676,3 +676,82 @@
fclose(fp);
}
+
+TEST(stdio, fmemopen) {
+ char buf[16];
+ memset(buf, 0, sizeof(buf));
+ FILE* fp = fmemopen(buf, sizeof(buf), "r+");
+ ASSERT_EQ('<', fputc('<', fp));
+ ASSERT_NE(EOF, fputs("abc>\n", fp));
+ fflush(fp);
+
+ ASSERT_STREQ("<abc>\n", buf);
+
+ rewind(fp);
+
+ char line[16];
+ char* s = fgets(line, sizeof(line), fp);
+ ASSERT_TRUE(s != NULL);
+ ASSERT_STREQ("<abc>\n", s);
+
+ fclose(fp);
+}
+
+TEST(stdio, fmemopen_NULL) {
+ FILE* fp = fmemopen(nullptr, 128, "r+");
+ ASSERT_NE(EOF, fputs("xyz\n", fp));
+
+ rewind(fp);
+
+ char line[16];
+ char* s = fgets(line, sizeof(line), fp);
+ ASSERT_TRUE(s != NULL);
+ ASSERT_STREQ("xyz\n", s);
+
+ fclose(fp);
+}
+
+TEST(stdio, fmemopen_EINVAL) {
+ char buf[16];
+
+ // Invalid size.
+ errno = 0;
+ ASSERT_EQ(nullptr, fmemopen(buf, 0, "r+"));
+ ASSERT_EQ(EINVAL, errno);
+
+ // No '+' with NULL buffer.
+ errno = 0;
+ ASSERT_EQ(nullptr, fmemopen(nullptr, 0, "r"));
+ ASSERT_EQ(EINVAL, errno);
+}
+
+TEST(stdio, open_memstream) {
+ char* p = nullptr;
+ size_t size = 0;
+ FILE* fp = open_memstream(&p, &size);
+ ASSERT_NE(EOF, fputs("hello, world!", fp));
+ fclose(fp);
+
+ ASSERT_STREQ("hello, world!", p);
+ ASSERT_EQ(strlen("hello, world!"), size);
+ free(p);
+}
+
+TEST(stdio, open_memstream_EINVAL) {
+#if defined(__BIONIC__)
+ char* p;
+ size_t size;
+
+ // Invalid buffer.
+ errno = 0;
+ ASSERT_EQ(nullptr, open_memstream(nullptr, &size));
+ ASSERT_EQ(EINVAL, errno);
+
+ // Invalid size.
+ errno = 0;
+ ASSERT_EQ(nullptr, open_memstream(&p, nullptr));
+ ASSERT_EQ(EINVAL, errno);
+#else
+ GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}
diff --git a/tests/wchar_test.cpp b/tests/wchar_test.cpp
index d02c4bf..760475f 100644
--- a/tests/wchar_test.cpp
+++ b/tests/wchar_test.cpp
@@ -489,3 +489,34 @@
EXPECT_EQ(4U, n);
EXPECT_EQ(L'𤭢', wc);
}
+
+TEST(wchar, open_wmemstream) {
+ wchar_t* p = nullptr;
+ size_t size = 0;
+ FILE* fp = open_wmemstream(&p, &size);
+ ASSERT_NE(EOF, fputws(L"hello, world!", fp));
+ fclose(fp);
+
+ ASSERT_STREQ(L"hello, world!", p);
+ ASSERT_EQ(wcslen(L"hello, world!"), size);
+ free(p);
+}
+
+TEST(stdio, open_wmemstream_EINVAL) {
+#if defined(__BIONIC__)
+ wchar_t* p;
+ size_t size;
+
+ // Invalid buffer.
+ errno = 0;
+ ASSERT_EQ(nullptr, open_wmemstream(nullptr, &size));
+ ASSERT_EQ(EINVAL, errno);
+
+ // Invalid size.
+ errno = 0;
+ ASSERT_EQ(nullptr, open_wmemstream(&p, nullptr));
+ ASSERT_EQ(EINVAL, errno);
+#else
+ GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
+}