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
This commit is contained in:
Pietro Cerutti 2013-01-30 14:59:26 +00:00
parent 9005607c8f
commit 96c95412ca
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=246120
7 changed files with 378 additions and 7 deletions

View file

@ -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 *);

View file

@ -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 \

View file

@ -155,6 +155,7 @@ FBSD_1.3 {
getwchar_l;
putwc_l;
putwchar_l;
fmemopen;
};
FBSDprivate_1.0 {

182
lib/libc/stdio/fmemopen.c Normal file
View file

@ -0,0 +1,182 @@
/*-
Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
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);
}

View file

@ -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 .

View file

@ -0,0 +1,143 @@
/*-
Copyright (C) 2013 Pietro Cerutti <gahr@FreeBSD.org>
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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
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);
}

View file

@ -0,0 +1,10 @@
#!/bin/sh
# $FreeBSD$
cd `dirname $0`
executable=`basename $0 .t`
make $executable 2>&1 > /dev/null
exec ./$executable