From 96c95412cafecdd39aaf1539ce712e4a24975c52 Mon Sep 17 00:00:00 2001 From: Pietro Cerutti Date: Wed, 30 Jan 2013 14:59:26 +0000 Subject: [PATCH] Add fmemopen(3), an interface to get a FILE * from a buffer in memory, along with the respective regression test. See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fmemopen.html Reviewed by: cognet Approved by: cognet --- include/stdio.h | 1 + lib/libc/stdio/Makefile.inc | 5 +- lib/libc/stdio/Symbol.map | 1 + lib/libc/stdio/fmemopen.c | 182 ++++++++++++++++++ lib/libc/stdio/fopen.3 | 43 ++++- .../regression/lib/libc/stdio/test-fmemopen.c | 143 ++++++++++++++ .../regression/lib/libc/stdio/test-fmemopen.t | 10 + 7 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 lib/libc/stdio/fmemopen.c create mode 100644 tools/regression/lib/libc/stdio/test-fmemopen.c create mode 100644 tools/regression/lib/libc/stdio/test-fmemopen.t diff --git a/include/stdio.h b/include/stdio.h index 2fd91259e800..4fc78b833abd 100644 --- a/include/stdio.h +++ b/include/stdio.h @@ -343,6 +343,7 @@ char *tempnam(const char *, const char *); #endif #if __BSD_VISIBLE || __POSIX_VISIBLE >= 200809 +FILE *fmemopen(void * __restrict, size_t, const char * __restrict); ssize_t getdelim(char ** __restrict, size_t * __restrict, int, FILE * __restrict); int renameat(int, const char *, int, const char *); diff --git a/lib/libc/stdio/Makefile.inc b/lib/libc/stdio/Makefile.inc index 657005197fbb..bc3b9e54801d 100644 --- a/lib/libc/stdio/Makefile.inc +++ b/lib/libc/stdio/Makefile.inc @@ -8,7 +8,8 @@ SRCS+= _flock_stub.c asprintf.c clrerr.c dprintf.c \ fclose.c fcloseall.c fdopen.c \ feof.c ferror.c fflush.c fgetc.c fgetln.c fgetpos.c fgets.c fgetwc.c \ fgetwln.c fgetws.c \ - fileno.c findfp.c flags.c fopen.c fprintf.c fpurge.c fputc.c fputs.c \ + fileno.c findfp.c flags.c fmemopen.c fopen.c fprintf.c fpurge.c \ + fputc.c fputs.c \ fputwc.c fputws.c fread.c freopen.c fscanf.c fseek.c fsetpos.c \ ftell.c funopen.c fvwrite.c fwalk.c fwide.c fwprintf.c fwscanf.c \ fwrite.c getc.c getchar.c getdelim.c getline.c \ @@ -48,7 +49,7 @@ MLINKS+=ferror.3 ferror_unlocked.3 \ MLINKS+=fflush.3 fpurge.3 MLINKS+=fgets.3 gets.3 MLINKS+=flockfile.3 ftrylockfile.3 flockfile.3 funlockfile.3 -MLINKS+=fopen.3 fdopen.3 fopen.3 freopen.3 +MLINKS+=fopen.3 fdopen.3 fopen.3 freopen.3 fopen.3 fmemopen.3 MLINKS+=fputs.3 puts.3 MLINKS+=fread.3 fwrite.3 MLINKS+=fseek.3 fgetpos.3 fseek.3 fseeko.3 fseek.3 fsetpos.3 fseek.3 ftell.3 \ diff --git a/lib/libc/stdio/Symbol.map b/lib/libc/stdio/Symbol.map index cafb0c64df7c..b78de5a4d812 100644 --- a/lib/libc/stdio/Symbol.map +++ b/lib/libc/stdio/Symbol.map @@ -155,6 +155,7 @@ FBSD_1.3 { getwchar_l; putwc_l; putwchar_l; + fmemopen; }; FBSDprivate_1.0 { diff --git a/lib/libc/stdio/fmemopen.c b/lib/libc/stdio/fmemopen.c new file mode 100644 index 000000000000..2cdf32480f16 --- /dev/null +++ b/lib/libc/stdio/fmemopen.c @@ -0,0 +1,182 @@ +/*- +Copyright (C) 2013 Pietro Cerutti + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +*/ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include + +struct __fmemopen_cookie +{ + char *buf; /* pointer to the memory region */ + char own; /* did we allocate the buffer ourselves? */ + long len; /* buffer length in bytes */ + long off; /* current offset into the buffer */ +}; + +static int fmemopen_read (void *cookie, char *buf, int nbytes); +static int fmemopen_write (void *cookie, const char *buf, int nbytes); +static fpos_t fmemopen_seek (void *cookie, fpos_t offset, int whence); +static int fmemopen_close (void *cookie); + +FILE * +fmemopen (void * __restrict buf, size_t size, const char * __restrict mode) +{ + /* allocate cookie */ + struct __fmemopen_cookie *ck = malloc (sizeof (struct __fmemopen_cookie)); + if (ck == NULL) { + errno = ENOMEM; + return (NULL); + } + + ck->off = 0; + ck->len = size; + + /* do we have to allocate the buffer ourselves? */ + ck->own = ((ck->buf = buf) == NULL); + if (ck->own) { + ck->buf = malloc (size); + if (ck->buf == NULL) { + free (ck); + errno = ENOMEM; + return (NULL); + } + ck->buf[0] = '\0'; + } + + if (mode[0] == 'a') + ck->off = strnlen(ck->buf, ck->len); + + /* actuall wrapper */ + FILE *f = funopen ((void *)ck, fmemopen_read, fmemopen_write, + fmemopen_seek, fmemopen_close); + + if (f == NULL) { + if (ck->own) + free (ck->buf); + free (ck); + return (NULL); + } + + /* turn off buffering, so a write past the end of the buffer + * correctly returns a short object count */ + setvbuf (f, (char *) NULL, _IONBF, 0); + + return (f); +} + +static int +fmemopen_read (void *cookie, char *buf, int nbytes) +{ + struct __fmemopen_cookie *ck = cookie; + + if (nbytes > ck->len - ck->off) + nbytes = ck->len - ck->off; + + if (nbytes == 0) + return (0); + + memcpy (buf, ck->buf + ck->off, nbytes); + + ck->off += nbytes; + + return (nbytes); +} + +static int +fmemopen_write (void *cookie, const char *buf, int nbytes) +{ + struct __fmemopen_cookie *ck = cookie; + + if (nbytes > ck->len - ck->off) + nbytes = ck->len - ck->off; + + if (nbytes == 0) + return (0); + + memcpy (ck->buf + ck->off, buf, nbytes); + + ck->off += nbytes; + + if (ck->off < ck->len && ck->buf[ck->off - 1] != '\0') + ck->buf[ck->off] = '\0'; + + return (nbytes); +} + +static fpos_t +fmemopen_seek (void *cookie, fpos_t offset, int whence) +{ + struct __fmemopen_cookie *ck = cookie; + + + switch (whence) { + case SEEK_SET: + if (offset > ck->len) { + errno = EINVAL; + return (-1); + } + ck->off = offset; + break; + + case SEEK_CUR: + if (ck->off + offset > ck->len) { + errno = EINVAL; + return (-1); + } + ck->off += offset; + break; + + case SEEK_END: + if (offset > 0 || -offset > ck->len) { + errno = EINVAL; + return (-1); + } + ck->off = ck->len + offset; + break; + + default: + errno = EINVAL; + return (-1); + } + + return (ck->off); +} + +static int +fmemopen_close (void *cookie) +{ + struct __fmemopen_cookie *ck = cookie; + + if (ck->own) + free (ck->buf); + + free (ck); + + return (0); +} diff --git a/lib/libc/stdio/fopen.3 b/lib/libc/stdio/fopen.3 index da89ed48c8fb..41d66b7157a7 100644 --- a/lib/libc/stdio/fopen.3 +++ b/lib/libc/stdio/fopen.3 @@ -32,13 +32,14 @@ .\" @(#)fopen.3 8.1 (Berkeley) 6/4/93 .\" $FreeBSD$ .\" -.Dd November 30, 2012 +.Dd January 30, 2013 .Dt FOPEN 3 .Os .Sh NAME .Nm fopen , .Nm fdopen , -.Nm freopen +.Nm freopen , +.Nm fmemopen .Nd stream open functions .Sh LIBRARY .Lb libc @@ -50,6 +51,8 @@ .Fn fdopen "int fildes" "const char *mode" .Ft FILE * .Fn freopen "const char *path" "const char *mode" "FILE *stream" +.Ft FILE * +.Fn fmemopen "void *restrict *buf" "size_t size" "const char * restrict mode" .Sh DESCRIPTION The .Fn fopen @@ -202,6 +205,29 @@ standard text stream .Dv ( stderr , stdin , or .Dv stdout ) . +.Pp +The +.Fn fmemopen +function +associates the buffer given by the +.Fa buf +and +.Fa size +arguments with a stream. +The +.Fa buf +argument shall be either a null pointer or point to a buffer that +is at least +.Fa size +bytes long. +If a null pointer is specified as the +.Fa buf +argument, +.Fn fmemopen +shall allocate +.Fa size +bytes of memory. This buffer shall be automatically freed when the +stream is closed. .Sh RETURN VALUES Upon successful completion .Fn fopen , @@ -225,16 +251,18 @@ argument to .Fn fopen , .Fn fdopen , +.Fn freopen , or -.Fn freopen +.Fn fmemopen was invalid. .El .Pp The .Fn fopen , -.Fn fdopen -and +.Fn fdopen , .Fn freopen +and +.Fn fmemopen functions may also fail and set .Va errno @@ -294,3 +322,8 @@ The .Dq Li e mode option does not conform to any standard but is also supported by glibc. +The +.Fn fmemopen +function +conforms to +.St -p1003.1-2008 . diff --git a/tools/regression/lib/libc/stdio/test-fmemopen.c b/tools/regression/lib/libc/stdio/test-fmemopen.c new file mode 100644 index 000000000000..d9c12765f5a2 --- /dev/null +++ b/tools/regression/lib/libc/stdio/test-fmemopen.c @@ -0,0 +1,143 @@ +/*- +Copyright (C) 2013 Pietro Cerutti + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. +*/ + +/* + * Test basic FILE * functions (fread, fwrite, fseek, fclose) against + * a FILE * retrieved using fmemopen() + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +void +test_preexisting () +{ + /* + * use a pre-existing buffer + */ + + char buf[512]; + char buf2[512]; + char str[] = "Test writing some stuff"; + char str2[] = "AAAAAAAAA"; + char str3[] = "AAAA writing some stuff"; + FILE *fp; + size_t nofw, nofr; + int rc; + + /* open a FILE * using fmemopen */ + fp = fmemopen (buf, sizeof buf, "w"); + assert (fp != NULL); + + /* write to the buffer */ + nofw = fwrite (str, 1, sizeof str, fp); + assert (nofw == sizeof str); + + /* close the FILE * */ + rc = fclose (fp); + assert (rc == 0); + + /* re-open the FILE * to read back the data */ + fp = fmemopen (buf, sizeof buf, "r"); + assert (fp != NULL); + + /* read from the buffer */ + bzero (buf2, sizeof buf2); + nofr = fread (buf2, 1, sizeof buf2, fp); + assert (nofr == sizeof buf2); + + /* since a write on a FILE * retrieved by fmemopen + * will add a '\0' (if there's space), we can check + * the strings for equality */ + assert (strcmp(str, buf2) == 0); + + /* close the FILE * */ + rc = fclose (fp); + assert (rc == 0); + + /* now open a FILE * on the first 4 bytes of the string */ + fp = fmemopen (str, 4, "w"); + assert (fp != NULL); + + /* try to write more bytes than we shoud, we'll get a short count (4) */ + nofw = fwrite (str2, 1, sizeof str2, fp); + assert (nofw == 4); + + /* close the FILE * */ + rc = fclose (fp); + + /* check that the string was not modified after the first 4 bytes */ + assert (strcmp (str, str3) == 0); +} + +void +test_autoalloc () +{ + /* + * let fmemopen allocate the buffer + */ + + char str[] = "A quick test"; + FILE *fp; + long pos; + size_t nofw, nofr, i; + int rc; + + /* open a FILE * using fmemopen */ + fp = fmemopen (NULL, 512, "w"); + assert (fp != NULL); + + /* fill the buffer */ + for (i = 0; i < 512; i++) { + nofw = fwrite ("a", 1, 1, fp); + assert (nofw == 1); + } + + /* get the current position into the stream */ + pos = ftell (fp); + assert (pos == 512); + + /* try to write past the end, we should get a short object count (0) */ + nofw = fwrite ("a", 1, 1, fp); + assert (nofw == 0); + + /* close the FILE * */ + rc = fclose (fp); + assert (rc == 0); +} + +int +main (void) +{ + test_autoalloc (); + test_preexisting (); + return (0); +} diff --git a/tools/regression/lib/libc/stdio/test-fmemopen.t b/tools/regression/lib/libc/stdio/test-fmemopen.t new file mode 100644 index 000000000000..8bdfd03be81b --- /dev/null +++ b/tools/regression/lib/libc/stdio/test-fmemopen.t @@ -0,0 +1,10 @@ +#!/bin/sh +# $FreeBSD$ + +cd `dirname $0` + +executable=`basename $0 .t` + +make $executable 2>&1 > /dev/null + +exec ./$executable