From 3706909a8374ad86e702bde80ab176b0c1fcb9b7 Mon Sep 17 00:00:00 2001 From: gahr Date: Thu, 31 Jan 2013 16:39:50 +0000 Subject: - Remove underscores from the internal structure name, as it doesn't collide with the user's namespace. - Correct size and position variables type from long to size_t. - Do not set errno to ENOMEM on malloc failure, as malloc already does so. - Implement the concept of "buffer data length", which mandates what SEEK_END refers to and the allowed extent for a read. - Use NULL as read-callback if the buffer is opened in write-only mode. Conversely, use NULL as write-callback when opened in read-only mode. - Implement the handling of the ``b'' character in the mode argument. A binary buffer differs from a text buffer (default mode if ``b'' is omitted) in that NULL bytes are never appended to writes and that the "buffer data length" equals to the size of the buffer. - Remove shall from the man page. Use indicative instead. Also, specify that the ``b'' flag does not conform with POSIX but is supported by glibc. - Update the regression test so that the ``b'' functionality and the "buffer data length" concepts are tested. - Minor style(9) corrections. Suggested by: jilles Reviewed by: cognet Approved by: cognet --- lib/libc/stdio/fmemopen.c | 131 ++++++++++++++++++++++++++++++++++++---------- lib/libc/stdio/fopen.3 | 19 +++++-- 2 files changed, 118 insertions(+), 32 deletions(-) (limited to 'lib/libc/stdio') diff --git a/lib/libc/stdio/fmemopen.c b/lib/libc/stdio/fmemopen.c index 2cdf324..dec2c86 100644 --- a/lib/libc/stdio/fmemopen.c +++ b/lib/libc/stdio/fmemopen.c @@ -26,17 +26,21 @@ SUCH DAMAGE. #include __FBSDID("$FreeBSD$"); +#include #include #include #include #include +#include "local.h" -struct __fmemopen_cookie +struct fmemopen_cookie { - char *buf; /* pointer to the memory region */ - char own; /* did we allocate the buffer ourselves? */ - long len; /* buffer length in bytes */ - long off; /* current offset into the buffer */ + char *buf; /* pointer to the memory region */ + char own; /* did we allocate the buffer ourselves? */ + char bin; /* is this a binary buffer? */ + size_t size; /* buffer length in bytes */ + size_t len; /* data length in bytes */ + size_t off; /* current offset into the buffer */ }; static int fmemopen_read (void *cookie, char *buf, int nbytes); @@ -47,33 +51,95 @@ static int fmemopen_close (void *cookie); FILE * fmemopen (void * __restrict buf, size_t size, const char * __restrict mode) { - /* allocate cookie */ - struct __fmemopen_cookie *ck = malloc (sizeof (struct __fmemopen_cookie)); + struct fmemopen_cookie *ck; + FILE *f; + int flags, rc; + + /* + * Retrieve the flags as used by open(2) from the mode argument, and + * validate them. + * */ + rc = __sflags (mode, &flags); + if (rc == 0) { + errno = EINVAL; + return (NULL); + } + + /* + * There's no point in requiring an automatically allocated buffer + * in write-only mode. + */ + if (!(flags & O_RDWR) && buf == NULL) { + errno = EINVAL; + return (NULL); + } + + /* Allocate a cookie. */ + ck = malloc (sizeof (struct fmemopen_cookie)); if (ck == NULL) { - errno = ENOMEM; return (NULL); } - ck->off = 0; - ck->len = size; + ck->off = 0; + ck->size = size; - /* do we have to allocate the buffer ourselves? */ + /* Check whether we have to allocate the buffer ourselves. */ ck->own = ((ck->buf = buf) == NULL); if (ck->own) { ck->buf = malloc (size); if (ck->buf == NULL) { free (ck); - errno = ENOMEM; return (NULL); } + } + + /* + * POSIX distinguishes between w+ and r+, in that w+ is supposed to + * truncate the buffer. + */ + if (ck->own || mode[0] == 'w') { ck->buf[0] = '\0'; } - if (mode[0] == 'a') - ck->off = strnlen(ck->buf, ck->len); + /* Check for binary mode. */ + ck->bin = strchr(mode, 'b') != NULL; + + /* + * The size of the current buffer contents is set depending on the + * mode: + * + * for append (text-mode), the position of the first NULL byte, or the + * size of the buffer if none is found + * + * for append (binary-mode), the size of the buffer + * + * for read, the size of the buffer + * + * for write, 0 + */ + switch (mode[0]) { + case 'a': + if (ck->bin) { + /* + * This isn't useful, since the buffer isn't + * allowed to grow. + */ + ck->off = ck->len = size; + } else + ck->off = ck->len = strnlen(ck->buf, ck->size); + break; + case 'r': + ck->len = size; + break; + case 'w': + ck->len = 0; + break; + } - /* actuall wrapper */ - FILE *f = funopen ((void *)ck, fmemopen_read, fmemopen_write, + /* Actuall wrapper. */ + f = funopen ((void *)ck, + flags & O_WRONLY ? NULL : fmemopen_read, + flags & O_RDONLY ? NULL : fmemopen_write, fmemopen_seek, fmemopen_close); if (f == NULL) { @@ -83,8 +149,10 @@ fmemopen (void * __restrict buf, size_t size, const char * __restrict mode) return (NULL); } - /* turn off buffering, so a write past the end of the buffer - * correctly returns a short object count */ + /* + * Turn off buffering, so a write past the end of the buffer + * correctly returns a short object count. + */ setvbuf (f, (char *) NULL, _IONBF, 0); return (f); @@ -93,7 +161,7 @@ fmemopen (void * __restrict buf, size_t size, const char * __restrict mode) static int fmemopen_read (void *cookie, char *buf, int nbytes) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; if (nbytes > ck->len - ck->off) nbytes = ck->len - ck->off; @@ -111,10 +179,10 @@ fmemopen_read (void *cookie, char *buf, int nbytes) static int fmemopen_write (void *cookie, const char *buf, int nbytes) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; - if (nbytes > ck->len - ck->off) - nbytes = ck->len - ck->off; + if (nbytes > ck->size - ck->off) + nbytes = ck->size - ck->off; if (nbytes == 0) return (0); @@ -123,7 +191,16 @@ fmemopen_write (void *cookie, const char *buf, int nbytes) ck->off += nbytes; - if (ck->off < ck->len && ck->buf[ck->off - 1] != '\0') + if (ck->off > ck->len) + ck->len = ck->off; + + /* + * We append a NULL byte if all these conditions are met: + * - the buffer is not binary + * - the buffer is not full + * - the data just written doesn't already end with a NULL byte + */ + if (!ck->bin && ck->off < ck->size && ck->buf[ck->off - 1] != '\0') ck->buf[ck->off] = '\0'; return (nbytes); @@ -132,12 +209,12 @@ fmemopen_write (void *cookie, const char *buf, int nbytes) static fpos_t fmemopen_seek (void *cookie, fpos_t offset, int whence) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; switch (whence) { case SEEK_SET: - if (offset > ck->len) { + if (offset > ck->size) { errno = EINVAL; return (-1); } @@ -145,7 +222,7 @@ fmemopen_seek (void *cookie, fpos_t offset, int whence) break; case SEEK_CUR: - if (ck->off + offset > ck->len) { + if (ck->off + offset > ck->size) { errno = EINVAL; return (-1); } @@ -171,7 +248,7 @@ fmemopen_seek (void *cookie, fpos_t offset, int whence) static int fmemopen_close (void *cookie) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; if (ck->own) free (ck->buf); diff --git a/lib/libc/stdio/fopen.3 b/lib/libc/stdio/fopen.3 index 41d66b7..a07be38 100644 --- a/lib/libc/stdio/fopen.3 +++ b/lib/libc/stdio/fopen.3 @@ -118,7 +118,9 @@ after either the or the first letter. This is strictly for compatibility with .St -isoC -and has no effect; the ``b'' is ignored. +and has effect only for +.Fn fmemopen +; otherwise the ``b'' is ignored. .Pp Any created files will have mode .Do Dv S_IRUSR @@ -216,7 +218,7 @@ and arguments with a stream. The .Fa buf -argument shall be either a null pointer or point to a buffer that +argument is either a null pointer or point to a buffer that is at least .Fa size bytes long. @@ -224,10 +226,15 @@ If a null pointer is specified as the .Fa buf argument, .Fn fmemopen -shall allocate +allocates .Fa size -bytes of memory. This buffer shall be automatically freed when the -stream is closed. +bytes of memory. This buffer is automatically freed when the +stream is closed. Buffers can be opened in text-mode (default) or binary-mode +(if ``b'' is present in the second or third position of the +.Fa mode +argument). Buffers opened in text-mode make sure that writes are terminated with +a NULL byte, if the last write hasn't filled up the whole buffer. Buffers +opened in binary-mode never append a NULL byte. .Sh RETURN VALUES Upon successful completion .Fn fopen , @@ -327,3 +334,5 @@ The function conforms to .St -p1003.1-2008 . +The ``b'' mode does not conform to any standard +but is also supported by glibc. -- cgit v1.1