From: Guillem Jover Date: Mon, 21 Oct 2013 03:07:56 +0000 (+0200) Subject: Add funopen() function X-Git-Tag: 0.7.0~7 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=f41fdcf186abbe26b11ba10fb3d4d0c1f109501e;p=thirdparty%2Flibbsd.git Add funopen() function This is a wrapper over the glibc fopencookie() function. We diverge from the FreeBSD, OpenBSD and DragonFlyBSD declarations, because seekfn() there wrongly uses fpos_t, assuming it's an integral type, and any code using that on a system where fpos_t is a struct (such as GNU-based systems or NetBSD) will fail to build. In which case, as the code has to be modified anyway, we might just as well use the correct declaration. --- diff --git a/configure.ac b/configure.ac index ad5fa25..1c1b962 100644 --- a/configure.ac +++ b/configure.ac @@ -124,7 +124,7 @@ AC_LINK_IFELSE( AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])]) -AC_CHECK_FUNCS([clearenv dirfd __fpurge getexecname getline sysconf]) +AC_CHECK_FUNCS([clearenv dirfd fopencookie __fpurge getexecname getline sysconf]) AC_CONFIG_FILES([ Makefile diff --git a/include/bsd/stdio.h b/include/bsd/stdio.h index 960df1b..e1f8fc3 100644 --- a/include/bsd/stdio.h +++ b/include/bsd/stdio.h @@ -46,6 +46,23 @@ const char *fmtcheck(const char *, const char *); char *fgetln(FILE *fp, size_t *lenp); +/* + * Note: We diverge from the FreeBSD, OpenBSD and DragonFlyBSD declarations, + * because seekfn() there wrongly uses fpos_t, assuming it's an integral + * type, and any code using that on a system where fpos_t is a struct + * (such as GNU-based systems or NetBSD) will fail to build. In which case, + * as the code has to be modified anyway, we might just as well use the + * correct declaration here. + */ +FILE *funopen(const void *cookie, + int (*readfn)(void *cookie, char *buf, int size), + int (*writefn)(void *cookie, const char *buf, int size), + off_t (*seekfn)(void *cookie, off_t offset, int whence), + int (*closefn)(void *cookie)); + +#define fropen(cookie, fn) funopen(cookie, fn, NULL, NULL, NULL) +#define fwopen(cookie, fn) funopen(cookie, NULL, fn, NULL, NULL) + int fpurge(FILE *fp); __END_DECLS diff --git a/man/Makefile.am b/man/Makefile.am index eb9c133..cbd71e4 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -28,6 +28,7 @@ dist_man_MANS = \ flopen.3 \ fmtcheck.3 \ fparseln.3 \ + funopen.3 \ getmode.3 \ getpeereid.3 \ getprogname.3 \ diff --git a/man/funopen.3 b/man/funopen.3 new file mode 100644 index 0000000..123c124 --- /dev/null +++ b/man/funopen.3 @@ -0,0 +1,191 @@ +.\" Copyright (c) 1990, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Chris Torek. +.\" 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. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 THE REGENTS 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. +.\" +.\" @(#)funopen.3 8.1 (Berkeley) 6/9/93 +.\" $FreeBSD$ +.\" +.Dd March 19, 2004 +.Dt FUNOPEN 3 +.Os +.Sh NAME +.Nm funopen , +.Nm fropen , +.Nm fwopen +.Nd open a stream +.Sh LIBRARY +.ds str-Lb-libbsd Utility functions from BSD systems (libbsd, \-lbsd) +.Lb libbsd +.Sh SYNOPSIS +.In bsd/stdio.h +.Ft FILE * +.Fn funopen "const void *cookie" "int (*readfn)(void *, char *, int)" "int (*writefn)(void *, const char *, int)" "off_t (*seekfn)(void *, off_t, int)" "int (*closefn)(void *)" +.Ft FILE * +.Fn fropen "void *cookie" "int (*readfn)(void *, char *, int)" +.Ft FILE * +.Fn fwopen "void *cookie" "int (*writefn)(void *, const char *, int)" +.Sh DESCRIPTION +The +.Fn funopen +function +associates a stream with up to four +.Dq Tn I/O No functions . +Either +.Fa readfn +or +.Fa writefn +must be specified; +the others can be given as an appropriately-typed +.Dv NULL +pointer. +These +.Tn I/O +functions will be used to read, write, seek and +close the new stream. +.Pp +In general, omitting a function means that any attempt to perform the +associated operation on the resulting stream will fail. +If the close function is omitted, closing the stream will flush +any buffered output and then succeed. +.Pp +The calling conventions of +.Fa readfn , +.Fa writefn , +.Fa seekfn +and +.Fa closefn +must match those, respectively, of +.Xr read 2 , +.Xr write 2 , +.Xr lseek 2 , +and +.Xr close 2 +with the single exception that they are passed the +.Fa cookie +argument specified to +.Fn funopen +in place of the traditional file descriptor argument. +.Pp +Read and write +.Tn I/O +functions are allowed to change the underlying buffer +on fully buffered or line buffered streams by calling +.Xr setvbuf 3 . +They are also not required to completely fill or empty the buffer. +They are not, however, allowed to change streams from unbuffered to buffered +or to change the state of the line buffering flag. +They must also be prepared to have read or write calls occur on buffers other +than the one most recently specified. +.Pp +All user +.Tn I/O +functions can report an error by returning \-1. +Additionally, all of the functions should set the external variable +.Va errno +appropriately if an error occurs. +.Pp +An error on +.Fn closefn +does not keep the stream open. +.Pp +As a convenience, the include file +.In stdio.h +defines the macros +.Fn fropen +and +.Fn fwopen +as calls to +.Fn funopen +with only a read or write function specified. +.Sh RETURN VALUES +Upon successful completion, +.Fn funopen +returns a +.Dv FILE +pointer. +Otherwise, +.Dv NULL +is returned and the global variable +.Va errno +is set to indicate the error. +.Sh ERRORS +.Bl -tag -width Er +.It Bq Er EINVAL +The +.Fn funopen +function +was called without either a read or write function. +The +.Fn funopen +function +may also fail and set +.Va errno +for any of the errors +specified for the routine +.Xr malloc 3 . +.El +.Sh SEE ALSO +.Xr fcntl 2 , +.Xr open 2 , +.Xr fclose 3 , +.Xr fopen 3 , +.Xr fseek 3 , +.Xr setbuf 3 +.Sh HISTORY +The +.Fn funopen +functions first appeared in +.Bx 4.4 . +.Sh BUGS +The +.Fn funopen +function +may not be portable to systems other than +.Bx . +.Pp +On +.Fx , +.Ox +and +.Dx +the +.Fn funopen +interface erroneously assumes that +.Vt fpos_t +is an integral type, and uses it in the +.Fa seekfn +hook; but because code using a +.Fa seekfn +hook will fail to build on systems where +.Vt fpos_t +is a struct, and it will need to be slightly fixed anyway, the +implementation provided by libbsd (in the same way as +.Nx ) +uses the correct +.Vt off_t +types. diff --git a/src/Makefile.am b/src/Makefile.am index 46f8634..5573520 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ libbsd_la_SOURCES = \ fmtcheck.c \ fparseln.c \ fpurge.c \ + funopen.c \ getpeereid.c \ hash/md5.c \ hash/md5hl.c \ diff --git a/src/funopen.c b/src/funopen.c new file mode 100644 index 0000000..7d6ae31 --- /dev/null +++ b/src/funopen.c @@ -0,0 +1,142 @@ +/* + * Copyright © 2011, 2013 Guillem Jover + * + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 + * THE AUTHOR 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 +#include +#include +#include +#include + +#ifdef HAVE_FOPENCOOKIE +struct funopen_cookie { + void *orig_cookie; + + int (*readfn)(void *cookie, char *buf, int size); + int (*writefn)(void *cookie, const char *buf, int size); + off_t (*seekfn)(void *cookie, off_t offset, int whence); + int (*closefn)(void *cookie); +}; + +static ssize_t +funopen_read(void *cookie, char *buf, size_t size) +{ + struct funopen_cookie *cookiewrap = cookie; + + if (cookiewrap->readfn == NULL) { + errno = EBADF; + return -1; + } + + return cookiewrap->readfn(cookiewrap->orig_cookie, buf, size); +} + +static ssize_t +funopen_write(void *cookie, const char *buf, size_t size) +{ + struct funopen_cookie *cookiewrap = cookie; + + if (cookiewrap->writefn == NULL) + return EOF; + + return cookiewrap->writefn(cookiewrap->orig_cookie, buf, size); +} + +static int +funopen_seek(void *cookie, off64_t *offset, int whence) +{ + struct funopen_cookie *cookiewrap = cookie; + off_t soff = *offset; + + if (cookiewrap->seekfn == NULL) { + errno = ESPIPE; + return -1; + } + + soff = cookiewrap->seekfn(cookiewrap->orig_cookie, soff, whence); + *offset = soff; + + return *offset; +} + +static int +funopen_close(void *cookie) +{ + struct funopen_cookie *cookiewrap = cookie; + int rc; + + if (cookiewrap->closefn == NULL) + return 0; + + rc = cookiewrap->closefn(cookiewrap->orig_cookie); + + free(cookiewrap); + + return rc; +} + +FILE * +funopen(const void *cookie, + int (*readfn)(void *cookie, char *buf, int size), + int (*writefn)(void *cookie, const char *buf, int size), + off_t (*seekfn)(void *cookie, off_t offset, int whence), + int (*closefn)(void *cookie)) +{ + struct funopen_cookie *cookiewrap; + cookie_io_functions_t funcswrap = { + .read = funopen_read, + .write = funopen_write, + .seek = funopen_seek, + .close = funopen_close, + }; + const char *mode; + + if (readfn) { + if (writefn == NULL) + mode = "r"; + else + mode = "r+"; + } else if (writefn) { + mode = "w"; + } else { + errno = EINVAL; + return NULL; + } + + cookiewrap = malloc(sizeof(*cookiewrap)); + if (cookiewrap == NULL) + return NULL; + + cookiewrap->orig_cookie = (void *)cookie; + cookiewrap->readfn = readfn; + cookiewrap->writefn = writefn; + cookiewrap->seekfn = seekfn; + cookiewrap->closefn = closefn; + + return fopencookie(cookiewrap, mode, funcswrap); +} +#else +#error "Function funopen() needs to be ported." +#endif diff --git a/src/libbsd.map b/src/libbsd.map index 52df212..de1b290 100644 --- a/src/libbsd.map +++ b/src/libbsd.map @@ -110,3 +110,7 @@ LIBBSD_0.6 { /* Exported to cope with the constructor+dlopen+threads mess. */ setproctitle_init; } LIBBSD_0.5; + +LIBBSD_0.7 { + funopen; +} LIBBSD_0.6; diff --git a/test/.gitignore b/test/.gitignore index 9a927b0..60e0e69 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,5 +1,6 @@ endian fgetln +funopen headers humanize overlay diff --git a/test/Makefile.am b/test/Makefile.am index d0e0f0b..6ace5a6 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -13,11 +13,13 @@ check_PROGRAMS = \ endian \ humanize \ fgetln \ + funopen \ proctitle \ $(nil) humanize_LDFLAGS = $(top_builddir)/src/libbsd.la fgetln_LDFLAGS = $(top_builddir)/src/libbsd.la +funopen_LDFLAGS = $(top_builddir)/src/libbsd.la proctitle_LDFLAGS = \ -Wl,-u,libbsd_init_func \ $(top_builddir)/src/libbsd-ctor.a \ diff --git a/test/funopen.c b/test/funopen.c new file mode 100644 index 0000000..aae558c --- /dev/null +++ b/test/funopen.c @@ -0,0 +1,178 @@ +/* + * Copyright © 2013 Guillem Jover + * + * 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. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``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 + * THE AUTHOR 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 +#include +#include +#include + +#define ARRAY_SIZE 100 +#define TEST_SIZE 50 + +struct test_cookie { + char array[ARRAY_SIZE]; + int index; +}; + +int +test_readfn(void *cookie, char *buf, int size) +{ + struct test_cookie *tc = cookie; + int left_size = sizeof(tc->array) - tc->index; + + if (left_size < 0) + size = 0; + else if (left_size < size) + size = left_size; + + if (size > 0) { + memcpy(buf, tc->array + tc->index, size); + tc->index += size; + } + + return size; +} + +int +test_writefn(void *cookie, const char *buf, int size) +{ + struct test_cookie *tc = cookie; + int left_size = sizeof(tc->array) - tc->index; + + if (left_size < 0) + size = 0; + else if (left_size < size) + size = left_size; + + if (size > 0) { + memcpy(tc->array + tc->index, buf, size); + tc->index += size; + } + + return size; +} + +off_t +test_seekfn(void *cookie, off_t offset, int whence) +{ + struct test_cookie *tc = cookie; + + switch (whence) { + case SEEK_SET: + tc->index = offset; + break; + case SEEK_CUR: + tc->index += offset; + break; + case SEEK_END: + tc->index = sizeof(tc->array) + offset; + break; + } + + return tc->index; +} + +int +test_closefn(void *cookie) +{ + struct test_cookie *tc = cookie; + + memset(tc->array, 0x7f, sizeof(tc->array)); + + return 0; +} + +int +main(int argc, char **argv) +{ + struct test_cookie tc; + char data[ARRAY_SIZE]; + FILE *fp; + size_t i; + + /* Test invalid hooks. */ + fp = funopen(&tc, NULL, NULL, NULL, NULL); + assert(fp == NULL); + assert(errno == EINVAL); + + /* Test read-only file. */ + tc.index = 0; + for (i = 0; i < sizeof(tc.array); i++) + tc.array[i] = i; + + fp = fropen(&tc, test_readfn); + assert(fp); + + assert(fread(data, 1, TEST_SIZE, fp) == TEST_SIZE); + assert(memcmp(tc.array, data, TEST_SIZE) == 0); + + assert(fwrite(data, 1, TEST_SIZE, fp) == 0); + + assert(fclose(fp) == 0); + + /* Test write-only file. */ + memset(&tc, 0, sizeof(tc)); + + fp = fwopen(&tc, test_writefn); + assert(fp); + + setvbuf(fp, NULL, _IONBF, 0); + + assert(fwrite(data, 1, TEST_SIZE, fp) == TEST_SIZE); + assert(memcmp(tc.array, data, TEST_SIZE) == 0); + + assert(fread(data, 1, TEST_SIZE, fp) == 0); + + assert(fclose(fp) == 0); + + /* Test seekable file. */ + memset(&tc, 0, sizeof(tc)); + + fp = funopen(&tc, test_readfn, test_writefn, test_seekfn, NULL); + assert(fp); + + setvbuf(fp, NULL, _IONBF, 0); + + assert(fwrite(data, 1, TEST_SIZE, fp) == TEST_SIZE); + assert(fseek(fp, 0L, SEEK_SET) == 0); + assert(fwrite(data, 1, ARRAY_SIZE, fp) == ARRAY_SIZE); + assert(memcmp(tc.array, data, ARRAY_SIZE) == 0); + + assert(fread(data, 1, TEST_SIZE, fp) == 0); + + assert(fclose(fp) == 0); + + /* Test close hook. */ + memset(&tc, 0, sizeof(tc)); + + fp = funopen(&tc, test_readfn, test_writefn, NULL, test_closefn); + assert(fclose(fp) == 0); + + for (i = 0; i < sizeof(tc.array); i++) + assert(tc.array[i] == 0x7f); + + return 0; +}