diff options
author | Robert Mustacchi <rm@fingolfin.org> | 2020-03-02 05:43:45 +0000 |
---|---|---|
committer | Robert Mustacchi <rm@fingolfin.org> | 2020-03-26 07:42:53 +0000 |
commit | cd62a92d4a964bfe61d35ba2301b69e65e22a509 (patch) | |
tree | 8e346d9037f7c654ffe58ed0d5e27f34025dd672 /usr/src/lib/libc/port/stdio/fmemopen.c | |
parent | 1470234269f4edea4cbf270cb2475e4988b788d5 (diff) | |
download | illumos-gate-cd62a92d4a964bfe61d35ba2301b69e65e22a509.tar.gz |
7092 Want support for stdio memory streams
12360 fwrite can loop forever on zero byte write
12392 ftello64 doesn't handle ungetc() correctly when unbuffered
Reviewed by: John Levon <john.levon@joyent.com>
Reviewed by: Yuri Pankov <ypankov@fastmail.com>
Approved by: Dan McDonald <danmcd@joyent.com>
Diffstat (limited to 'usr/src/lib/libc/port/stdio/fmemopen.c')
-rw-r--r-- | usr/src/lib/libc/port/stdio/fmemopen.c | 270 |
1 files changed, 270 insertions, 0 deletions
diff --git a/usr/src/lib/libc/port/stdio/fmemopen.c b/usr/src/lib/libc/port/stdio/fmemopen.c new file mode 100644 index 0000000000..e2321c9632 --- /dev/null +++ b/usr/src/lib/libc/port/stdio/fmemopen.c @@ -0,0 +1,270 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Robert Mustacchi + */ + +/* + * Implements fmemopen(3C). + */ + +#include "mtlib.h" +#include "file64.h" +#include <stdio.h> +#include "stdiom.h" +#include <errno.h> +#include <stdlib.h> +#include <fcntl.h> +#include <sys/sysmacros.h> +#include <limits.h> + +typedef enum fmemopen_flags { + /* + * Indicates that the user gave us the buffer and so we shouldn't free + * it. + */ + FMO_F_USER_BUFFER = 1 << 0, + /* + * When the stream is open for update (a, a+) then we have to have + * slightly different behavior on write and zeroing the buffer. + */ + FMO_F_APPEND = 1 << 1 +} fmemopen_flags_t; + +typedef struct fmemopen { + /* + * Pointer to the underlying memory stream. + */ + char *fmo_buf; + /* + * Allocated length of the buffer. + */ + size_t fmo_alloc; + /* + * Current position of the buffer. + */ + size_t fmo_pos; + /* + * Current 'size' of the buffer. POSIX describes a size that the buffer + * has which is separate from the allocated size, but cannot exceed it. + */ + size_t fmo_lsize; + fmemopen_flags_t fmo_flags; +} fmemopen_t; + +static ssize_t +fmemopen_read(FILE *iop, char *buf, size_t nbytes) +{ + fmemopen_t *fmp = _xdata(iop); + + nbytes = MIN(nbytes, fmp->fmo_lsize - fmp->fmo_pos); + if (nbytes == 0) { + return (0); + } + + (void) memcpy(buf, fmp->fmo_buf, nbytes); + fmp->fmo_pos += nbytes; + + return (nbytes); +} + +static ssize_t +fmemopen_write(FILE *iop, const char *buf, size_t nbytes) +{ + size_t npos; + fmemopen_t *fmp = _xdata(iop); + + if ((fmp->fmo_flags & FMO_F_APPEND) != 0) { + /* + * POSIX says that if append mode is in effect, we must always + * seek to the logical size. This effectively is mimicking the + * O_APPEND behavior. + */ + fmp->fmo_pos = fmp->fmo_lsize; + } + + if (nbytes == 0) { + return (0); + } else if (nbytes >= SSIZE_MAX) { + errno = EINVAL; + return (-1); + } + + npos = fmp->fmo_pos + nbytes; + if (npos < nbytes) { + errno = EOVERFLOW; + return (-1); + } else if (npos > fmp->fmo_alloc) { + nbytes = fmp->fmo_alloc - fmp->fmo_pos; + } + + (void) memcpy(&fmp->fmo_buf[fmp->fmo_pos], buf, nbytes); + fmp->fmo_pos += nbytes; + + if (fmp->fmo_pos > fmp->fmo_lsize) { + fmp->fmo_lsize = fmp->fmo_pos; + + /* + * POSIX distinguishes behavior for writing a NUL in these + * streams. Basically if we are open for update and we are at + * the end of the buffer, we don't place a NUL. Otherwise, we + * always place one at the current position (or the end if we + * were over the edge). + */ + if (fmp->fmo_lsize < fmp->fmo_alloc) { + fmp->fmo_buf[fmp->fmo_lsize] = '\0'; + } else if ((fmp->fmo_flags & FMO_F_APPEND) == 0) { + fmp->fmo_buf[fmp->fmo_alloc - 1] = '\0'; + } + } + + return (nbytes); +} + +static off_t +fmemopen_seek(FILE *iop, off_t off, int whence) +{ + fmemopen_t *fmp = _xdata(iop); + size_t base, npos; + + switch (whence) { + case SEEK_SET: + base = 0; + break; + case SEEK_CUR: + base = fmp->fmo_pos; + break; + case SEEK_END: + base = fmp->fmo_lsize; + break; + default: + errno = EINVAL; + return (-1); + } + + if (!memstream_seek(base, off, fmp->fmo_alloc, &npos)) { + errno = EINVAL; + return (-1); + } + fmp->fmo_pos = npos; + + return ((off_t)fmp->fmo_pos); +} + +static void +fmemopen_free(fmemopen_t *fmp) +{ + if (fmp->fmo_buf != NULL && + (fmp->fmo_flags & FMO_F_USER_BUFFER) == 0) { + free(fmp->fmo_buf); + } + + free(fmp); +} + +static int +fmemopen_close(FILE *iop) +{ + fmemopen_t *fmp = _xdata(iop); + fmemopen_free(fmp); + _xunassoc(iop); + return (0); +} + +FILE * +fmemopen(void *_RESTRICT_KYWD buf, size_t size, + const char *_RESTRICT_KYWD mode) +{ + int oflags, fflags, err; + fmemopen_t *fmp; + FILE *iop; + + if (size == 0 || mode == NULL) { + errno = EINVAL; + return (NULL); + } + + if (_stdio_flags(mode, &oflags, &fflags) != 0) { + /* errno set for us */ + return (NULL); + } + + /* + * buf is only allowed to be NULL if the '+' is specified. If the '+' + * mode was specified, then we'll have fflags set to _IORW. + */ + if (buf == NULL && fflags != _IORW) { + errno = EINVAL; + return (NULL); + } + + if ((fmp = calloc(1, sizeof (fmemopen_t))) == NULL) { + errno = ENOMEM; + return (NULL); + } + + if (buf == NULL) { + fmp->fmo_buf = calloc(size, sizeof (uint8_t)); + if (fmp->fmo_buf == NULL) { + errno = ENOMEM; + goto cleanup; + } + } else { + fmp->fmo_buf = buf; + fmp->fmo_flags |= FMO_F_USER_BUFFER; + } + + fmp->fmo_alloc = size; + + /* + * Set the initial logical size and position depending on whether we're + * using r(+), w(+), and a(+). The latter two are identified by O_TRUNC + * and O_APPEND in oflags. + */ + if ((oflags & O_APPEND) != 0) { + fmp->fmo_pos = strnlen(fmp->fmo_buf, fmp->fmo_alloc); + fmp->fmo_lsize = fmp->fmo_pos; + fmp->fmo_flags |= FMO_F_APPEND; + } else if ((oflags & O_TRUNC) != 0) { + fmp->fmo_buf[0] = '\0'; + fmp->fmo_pos = 0; + fmp->fmo_lsize = 0; + } else { + fmp->fmo_pos = 0; + fmp->fmo_lsize = size; + } + + iop = _findiop(); + if (iop == NULL) { + goto cleanup; + } + +#ifdef _LP64 + iop->_flag = (iop->_flag & ~_DEF_FLAG_MASK) | fflags; +#else + iop->_flag = fflags; +#endif + if (_xassoc(iop, fmemopen_read, fmemopen_write, fmemopen_seek, + fmemopen_close, fmp) != 0) { + goto cleanup; + } + + SET_SEEKABLE(iop); + + return (iop); + +cleanup: + err = errno; + fmemopen_free(fmp); + errno = err; + return (NULL); +} |