From f35669d86c66ccad84b843a514ea7e05a767b6dc Mon Sep 17 00:00:00 2001 From: Neal Gompa (ニール・ゴンパ) Date: Mar 14 2016 03:21:25 +0000 Subject: Initial commit of portable-fopencookie --- diff --git a/CMakeLists.sample b/CMakeLists.sample new file mode 100644 index 0000000..65bfaa0 --- /dev/null +++ b/CMakeLists.sample @@ -0,0 +1,23 @@ +# Sample CMakeLists snippets for configuring and using +# the portable fopencookie() implementation. +# +# Please be sure to to appropriately modify your +# CMakeLists.txt and source code to use this. + +# Enable checking for functions +include(CheckIncludeFiles) + +# Check if Linux fopencookie() or BSD funopen() exists +# This also adds compiler definitions depending on +# the availability of these functions +CHECK_FUNCTION_EXISTS (fopencookie HAVE_FOPENCOOKIE) +CHECK_FUNCTION_EXISTS (funopen HAVE_FUNOPEN) + +# If neither fopencookie() nor funopen() exist, +# add our fopencookie() implementation +IF (NOT HAVE_FOPENCOOKIE AND NOT HAVE_FUNOPEN) + SET (mainproject_SRCS ${mainproject_SRCS} + src/portable_fopencookie.c) + SET (mainproject_HEADERS ${mainproject_HEADERS} + include/portable_fopencookie.h) +ENDIF (NOT HAVE_FOPENCOOKIE AND NOT HAVE_FUNOPEN) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..63b2b4c --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015 Neal Gompa. All rights reserved. + +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. Neither the name of the copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce9aad9 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# Portable `fopencookie()` +This is the code for a *very limited* implementation of `fopencookie()` for projects +that need it to function in environments where the standard C runtime library +(often known as a "libc") does not implement `fopencookie()` and there are no +alternatives available. + +This is explicitly designed as a "copylib", which means that you put a copy of +this code into your project in order to use it. Due to this particular nature, +it is licensed under [3-clause BSD license](LICENSE) for broad applicability. + +## Limitations +* No seeking support +* No r+/w+ support +* All shared data access must be synchronized (as the accessor runs in another thread now) +* Callbacks are called asynchronously in another thread so it observably behaves differently than the glibc `fopencookie()` implementation + +## How to use +As mentioned earlier, this is a "copylib" and should be copied into your project for use. +Additionally, you *should* check using your build system to see if `fopencookie()`, `funopen()`, +or any similar alternative is available for your code to use, as you'll certainly want to use +those if they are available. A sample set of steps for checking for these functions and conditionally +using this code using CMake is included in the project. + +## Authors +I did not write this. In fact, the credit to this code belongs solely to `zhasha` and `nsz` in +`#musl` on Freenode IRC. They graciously released the rights to the code in its entirety for me to use, +so I put it under the 3-clause BSD license and set up this project for others to be able to use and improve it. + +## Why it exists +This code was originally written to allow [libsolv](https://github.com/openSUSE/libsolv) to function on [musl libc](http://www.musl-libc.org/). +Because the code would likely be quite useful for others who may want to use this function in a +similarly constrained environment (such as Bionic libc), I set up this project to host it +and allow others to improve it. + +## Contributing +### Code contributions +Improvements to the code are highly welcome in the form of pull requests. + +### Non-code contributions +If you wish to show your appreciation for this code with money, please consider becoming +[a patron of musl libc on Patreon](https://www.patreon.com/musl). This code would not exist +if it weren't for the kind members of the musl libc community, and they are doing great work +to make an excellent libc that is generally useful in a wide range of environments and well-designed. diff --git a/include/portable_fopencookie.h b/include/portable_fopencookie.h new file mode 100644 index 0000000..298c444 --- /dev/null +++ b/include/portable_fopencookie.h @@ -0,0 +1,29 @@ +/* + * Provides a very limited fopencookie() for environments with a libc + * that lacks it. + * + * Authors: zhasha & nsz + * Modified for libsolv by Neal Gompa + * Modified for external use by Neal Gompa + * + * This program is licensed under the BSD license, see LICENSE + * for further information. + * + */ + +#ifndef PORTABLE_FOPENCOOKIE_H +#define PORTABLE_FOPENCOOKIE_H + +#include +#include + +typedef struct cookie_io_functions_t { + ssize_t (*read)(void *, char *, size_t); + ssize_t (*write)(void *, const char *, size_t); + int (*seek)(void *, off64_t, int); + int (*close)(void *); +} cookie_io_functions_t; + +FILE *fopencookie(void *cookie, const char *mode, struct cookie_io_functions_t io); + +#endif diff --git a/src/portable_fopencookie.c b/src/portable_fopencookie.c new file mode 100644 index 0000000..5c4c114 --- /dev/null +++ b/src/portable_fopencookie.c @@ -0,0 +1,125 @@ +/* + * Provides a very limited fopencookie() for environments with a libc + * that lacks it. + * + * Authors: zhasha & nsz + * Modified for libsolv by Neal Gompa + * Modified for external use by Neal Gompa + * + * This program is licensed under the BSD license, see LICENSE + * for further information. + * + */ + +#define _LARGEFILE64_SOURCE 1 +#include +#include +#include +#include +#include +#include +#include +#include "portable_fopencookie.h" + +extern int pipe2(int[2], int); + +struct ctx { + int fd; + void *cookie; + struct cookie_io_functions_t io; + char buf[1024]; +}; + +static void *proxy(void *arg) +{ + struct ctx *ctx = arg; + ssize_t r; + size_t n, k; + + pthread_detach(pthread_self()); + + while (1) { + r = ctx->io.read ? + (ctx->io.read)(ctx->cookie, ctx->buf, sizeof(ctx->buf)) : + read(ctx->fd, ctx->buf, sizeof(ctx->buf)); + if (r < 0) { + if (errno != EINTR) { break; } + continue; + } + if (r == 0) { break; } + + n=r,k=0; + while (n > 0) { + r = ctx->io.write ? + (ctx->io.write)(ctx->cookie, ctx->buf + k, n) : + write(ctx->fd, ctx->buf + k, n); + if (r < 0) { + if (errno != EINTR) { break; } + continue; + } + if (r == 0) { break; } + + n-=r,k+=r; + } + if (n > 0) { break; } + } + + if (ctx->io.close) { (ctx->io.close)(ctx->cookie); } + close(ctx->fd); + return NULL; +} + +FILE *fopencookie(void *cookie, const char *mode, struct cookie_io_functions_t io) +{ + struct ctx *ctx = NULL; + int rd = 0, wr = 0; + int p[2] = { -1, -1 }; + FILE *f = NULL; + pthread_t dummy; + + switch (mode[0]) { + case 'a': + case 'w': wr = 1; break; + case 'r': rd = 1; break; + default: + errno = EINVAL; + return NULL; + } + switch (mode[1]) { + case '\0': break; + case '+': + if (mode[2] == '\0') { + errno = ENOTSUP; + return NULL; + } + default: + errno = EINVAL; + return NULL; + } + if (io.seek) { + errno = ENOTSUP; + return NULL; + } + + ctx = malloc(sizeof(*ctx)); + if (!ctx) { return NULL; } + if (pipe2(p, O_CLOEXEC) != 0) { goto err; } + if ((f = fdopen(p[wr], mode)) == NULL) { goto err; } + p[wr] = -1; + ctx->fd = p[rd]; + ctx->cookie = cookie; + ctx->io.read = rd ? io.read : NULL; + ctx->io.write = wr ? io.write : NULL; + ctx->io.seek = NULL; + ctx->io.close = io.close; + if (pthread_create(&dummy, NULL, proxy, ctx) != 0) { goto err; } + + return f; + +err: + if (p[0] >= 0) { close(p[0]); } + if (p[1] >= 0) { close(p[1]); } + if (f) { fclose(f); } + free(ctx); + return NULL; +}