| From 061843340fbf2493bb615e20e66f60c5d1ef0455 Mon Sep 17 00:00:00 2001 |
| From: William Pitcock <nenolod@dereferenced.org> |
| Date: Tue, 5 Dec 2017 16:04:43 -0500 |
| Subject: implement the fopencookie extension to stdio |
| |
| notes added by maintainer: |
| |
| this function is a GNU extension. it was chosen over the similar BSD |
| function funopen because the latter depends on fpos_t being an |
| arithmetic type as part of its public API, conflicting with our |
| definition of fpos_t and with the intent that it be an opaque type. it |
| was accepted for inclusion because, despite not being widely used, it |
| is usually very difficult to extricate software using it from the |
| dependency on it. |
| |
| calling pattern for the read and write callbacks is not likely to |
| match glibc or other implementations, but should work with any |
| reasonable callbacks. in particular the read function is never called |
| without at least one byte being needed to satisfy its caller, so that |
| spurious blocking is not introduced. |
| |
| contracts for what callbacks called from inside libc/stdio can do are |
| always complicated, and at some point still need to be specified |
| explicitly. at the very least, the callbacks must return or block |
| indefinitely (they cannot perform nonlocal exits) and they should not |
| make calls to stdio using their own FILE as an argument. |
| |
| include/stdio.h | 14 +++++ |
| src/stdio/fopencookie.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++ |
| 2 files changed, 152 insertions(+) |
| create mode 100644 src/stdio/fopencookie.c |
| |
| diff --git a/include/stdio.h b/include/stdio.h |
| index 884d2e6..2932c76 100644 |
| |
| |
| @@ -182,6 +182,20 @@ int vasprintf(char **, const char *, __isoc_va_list); |
| #ifdef _GNU_SOURCE |
| char *fgets_unlocked(char *, int, FILE *); |
| int fputs_unlocked(const char *, FILE *); |
| + |
| +typedef ssize_t (cookie_read_function_t)(void *, char *, size_t); |
| +typedef ssize_t (cookie_write_function_t)(void *, const char *, size_t); |
| +typedef int (cookie_seek_function_t)(void *, off_t *, int); |
| +typedef int (cookie_close_function_t)(void *); |
| + |
| +typedef struct { |
| + cookie_read_function_t *read; |
| + cookie_write_function_t *write; |
| + cookie_seek_function_t *seek; |
| + cookie_close_function_t *close; |
| +} cookie_io_functions_t; |
| + |
| +FILE *fopencookie(void *, const char *, cookie_io_functions_t); |
| #endif |
| |
| #if defined(_LARGEFILE64_SOURCE) || defined(_GNU_SOURCE) |
| diff --git a/src/stdio/fopencookie.c b/src/stdio/fopencookie.c |
| new file mode 100644 |
| index 0000000..2f46dd5 |
| |
| |
| @@ -0,0 +1,138 @@ |
| +#define _GNU_SOURCE |
| +#include "stdio_impl.h" |
| +#include <stdlib.h> |
| +#include <sys/ioctl.h> |
| +#include <fcntl.h> |
| +#include <errno.h> |
| +#include <string.h> |
| + |
| +struct fcookie { |
| + void *cookie; |
| + cookie_io_functions_t iofuncs; |
| +}; |
| + |
| +struct cookie_FILE { |
| + FILE f; |
| + struct fcookie fc; |
| + unsigned char buf[UNGET+BUFSIZ]; |
| +}; |
| + |
| +static size_t cookieread(FILE *f, unsigned char *buf, size_t len) |
| +{ |
| + struct fcookie *fc = f->cookie; |
| + ssize_t ret = -1; |
| + size_t remain = len, readlen = 0; |
| + size_t len2 = len - !!f->buf_size; |
| + |
| + if (!fc->iofuncs.read) goto bail; |
| + |
| + if (len2) { |
| + ret = fc->iofuncs.read(fc->cookie, (char *) buf, len2); |
| + if (ret <= 0) goto bail; |
| + |
| + readlen += ret; |
| + remain -= ret; |
| + } |
| + |
| + if (!f->buf_size || remain > !!f->buf_size) return readlen; |
| + |
| + f->rpos = f->buf; |
| + ret = fc->iofuncs.read(fc->cookie, (char *) f->rpos, f->buf_size); |
| + if (ret <= 0) goto bail; |
| + f->rend = f->rpos + ret; |
| + |
| + buf[readlen++] = *f->rpos++; |
| + |
| + return readlen; |
| + |
| +bail: |
| + f->flags |= ret == 0 ? F_EOF : F_ERR; |
| + f->rpos = f->rend = f->buf; |
| + return readlen; |
| +} |
| + |
| +static size_t cookiewrite(FILE *f, const unsigned char *buf, size_t len) |
| +{ |
| + struct fcookie *fc = f->cookie; |
| + ssize_t ret; |
| + size_t len2 = f->wpos - f->wbase; |
| + if (!fc->iofuncs.write) return len; |
| + if (len2) { |
| + f->wpos = f->wbase; |
| + if (cookiewrite(f, f->wpos, len2) < len2) return 0; |
| + } |
| + ret = fc->iofuncs.write(fc->cookie, (const char *) buf, len); |
| + if (ret < 0) { |
| + f->wpos = f->wbase = f->wend = 0; |
| + f->flags |= F_ERR; |
| + return 0; |
| + } |
| + return ret; |
| +} |
| + |
| +static off_t cookieseek(FILE *f, off_t off, int whence) |
| +{ |
| + struct fcookie *fc = f->cookie; |
| + int res; |
| + if (whence > 2U) { |
| + errno = EINVAL; |
| + return -1; |
| + } |
| + if (!fc->iofuncs.seek) { |
| + errno = ENOTSUP; |
| + return -1; |
| + } |
| + res = fc->iofuncs.seek(fc->cookie, &off, whence); |
| + if (res < 0) |
| + return res; |
| + return off; |
| +} |
| + |
| +static int cookieclose(FILE *f) |
| +{ |
| + struct fcookie *fc = f->cookie; |
| + if (fc->iofuncs.close) return fc->iofuncs.close(fc->cookie); |
| + return 0; |
| +} |
| + |
| +FILE *fopencookie(void *cookie, const char *mode, cookie_io_functions_t iofuncs) |
| +{ |
| + struct cookie_FILE *f; |
| + |
| + /* Check for valid initial mode character */ |
| + if (!strchr("rwa", *mode)) { |
| + errno = EINVAL; |
| + return 0; |
| + } |
| + |
| + /* Allocate FILE+fcookie+buffer or fail */ |
| + if (!(f=malloc(sizeof *f))) return 0; |
| + |
| + /* Zero-fill only the struct, not the buffer */ |
| + memset(&f->f, 0, sizeof f->f); |
| + |
| + /* Impose mode restrictions */ |
| + if (!strchr(mode, '+')) f->f.flags = (*mode == 'r') ? F_NOWR : F_NORD; |
| + |
| + /* Set up our fcookie */ |
| + f->fc.cookie = cookie; |
| + f->fc.iofuncs.read = iofuncs.read; |
| + f->fc.iofuncs.write = iofuncs.write; |
| + f->fc.iofuncs.seek = iofuncs.seek; |
| + f->fc.iofuncs.close = iofuncs.close; |
| + |
| + f->f.fd = -1; |
| + f->f.cookie = &f->fc; |
| + f->f.buf = f->buf + UNGET; |
| + f->f.buf_size = BUFSIZ; |
| + f->f.lbf = EOF; |
| + |
| + /* Initialize op ptrs. No problem if some are unneeded. */ |
| + f->f.read = cookieread; |
| + f->f.write = cookiewrite; |
| + f->f.seek = cookieseek; |
| + f->f.close = cookieclose; |
| + |
| + /* Add new FILE to open file list */ |
| + return __ofl_add(&f->f); |
| +} |
| -- |
| cgit v0.11.2 |
| |