From 6b841db2baa24ffcf2a4e5f975d1d07f1699b918 Mon Sep 17 00:00:00 2001
From: Elliott Hughes <enh@google.com>
Date: Wed, 20 Aug 2014 16:10:49 -0700
Subject: [PATCH] Add POSIX-2008 fmemopen, open_memstream, and open_wmemstream.

Bug: 17164505
Change-Id: I59e28a08ff8b6ab632230b11a5807cfd5278aeb5
---
 libc/Android.mk                               |   3 +
 libc/include/stdio.h                          |   5 +
 libc/include/wchar.h                          |   1 +
 .../lib/libc/stdio/fmemopen.c                 | 183 ++++++++++++++++++
 .../lib/libc/stdio/open_memstream.c           | 158 +++++++++++++++
 .../lib/libc/stdio/open_wmemstream.c          | 169 ++++++++++++++++
 tests/stdio_test.cpp                          |  79 ++++++++
 tests/wchar_test.cpp                          |  31 +++
 8 files changed, 629 insertions(+)
 create mode 100644 libc/upstream-openbsd/lib/libc/stdio/fmemopen.c
 create mode 100644 libc/upstream-openbsd/lib/libc/stdio/open_memstream.c
 create mode 100644 libc/upstream-openbsd/lib/libc/stdio/open_wmemstream.c

diff --git a/libc/Android.mk b/libc/Android.mk
index c268a87b8..487cbd80e 100644
--- a/libc/Android.mk
+++ b/libc/Android.mk
@@ -391,6 +391,7 @@ libc_upstream_openbsd_src_files := \
     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 @@ libc_upstream_openbsd_src_files := \
     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 8727a9fa2..ce60fd70e 100644
--- a/libc/include/stdio.h
+++ b/libc/include/stdio.h
@@ -325,6 +325,11 @@ int	 putc_unlocked(int, FILE *);
 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 1898c7e93..ae10d93e6 100644
--- a/libc/include/wchar.h
+++ b/libc/include/wchar.h
@@ -166,6 +166,7 @@ extern wint_t towctrans(wint_t, wctrans_t);
 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 000000000..8cda04763
--- /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 000000000..46105358d
--- /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 000000000..94141878f
--- /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 bb86509c2..2cd0df25a 100644
--- a/tests/stdio_test.cpp
+++ b/tests/stdio_test.cpp
@@ -676,3 +676,82 @@ TEST(stdio, fpos_t_and_seek) {
 
   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 d02c4bf2f..760475fa1 100644
--- a/tests/wchar_test.cpp
+++ b/tests/wchar_test.cpp
@@ -489,3 +489,34 @@ TEST(wchar, mbrtowc_15439554) {
   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
+}