]> git.ipfire.org Git - thirdparty/libbsd.git/commitdiff
Add funopen() function
authorGuillem Jover <guillem@hadrons.org>
Mon, 21 Oct 2013 03:07:56 +0000 (05:07 +0200)
committerGuillem Jover <guillem@hadrons.org>
Mon, 21 Oct 2013 03:35:44 +0000 (05:35 +0200)
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.

configure.ac
include/bsd/stdio.h
man/Makefile.am
man/funopen.3 [new file with mode: 0644]
src/Makefile.am
src/funopen.c [new file with mode: 0644]
src/libbsd.map
test/.gitignore
test/Makefile.am
test/funopen.c [new file with mode: 0644]

index ad5fa25f6763563e79a96d022eeda503f82731d3..1c1b9622c46fbe0fcd728f5c3a41bcf0c7cdb3fd 100644 (file)
@@ -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
index 960df1bc8bd6bf9bedd4064f2cc301b4eef1e2f7..e1f8fc3a88267123f672e6308209c1d44cabb35a 100644 (file)
@@ -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
 
index eb9c133d99c9575fee8aab684a5f975f785acc63..cbd71e4468a702c61f962b6a0f36f3b066c167a1 100644 (file)
@@ -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 (file)
index 0000000..123c124
--- /dev/null
@@ -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.
index 46f86346348f3863156ee57f4e0e4bea846f150c..5573520713c572633faafa477226b85e8b1e888c 100644 (file)
@@ -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 (file)
index 0000000..7d6ae31
--- /dev/null
@@ -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 <sys/cdefs.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#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
index 52df212cd9216e2adea13361d9361844ca2d6af1..de1b2902a75e8da8daeffc2f672afc937d1b5427 100644 (file)
@@ -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;
index 9a927b0b225d0a21ec0e5f7166b1b346b0db83b9..60e0e695e0397094f27f4f39d49e3572f17bdd32 100644 (file)
@@ -1,5 +1,6 @@
 endian
 fgetln
+funopen
 headers
 humanize
 overlay
index d0e0f0b71263defa4044ab8c5f947851a6f0dea1..6ace5a686ff8a5ceabd5a1b24ed8e1f8f766a53e 100644 (file)
@@ -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 (file)
index 0000000..aae558c
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright © 2013 Guillem Jover <guillem@hadrons.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.
+ * 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 <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+
+#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;
+}