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 + * 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 +#include +#include +#include +#include +#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 + * + * 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 + +#include +#include +#include +#include +#include +#include +#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 + * + * 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 + +#include +#include +#include +#include +#include +#include +#include +#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("\n", buf); + + rewind(fp); + + char line[16]; + char* s = fgets(line, sizeof(line), fp); + ASSERT_TRUE(s != NULL); + ASSERT_STREQ("\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 +}