From owner-svn-src-head@FreeBSD.ORG Thu Jan 31 16:39:51 2013 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:1900:2254:206a::19:1]) by hub.freebsd.org (Postfix) with ESMTP id 4E9687D2; Thu, 31 Jan 2013 16:39:51 +0000 (UTC) (envelope-from gahr@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) by mx1.freebsd.org (Postfix) with ESMTP id 26F3524C; Thu, 31 Jan 2013 16:39:51 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.5/8.14.5) with ESMTP id r0VGdpCC044247; Thu, 31 Jan 2013 16:39:51 GMT (envelope-from gahr@svn.freebsd.org) Received: (from gahr@localhost) by svn.freebsd.org (8.14.5/8.14.5/Submit) id r0VGdo0Q044244; Thu, 31 Jan 2013 16:39:50 GMT (envelope-from gahr@svn.freebsd.org) Message-Id: <201301311639.r0VGdo0Q044244@svn.freebsd.org> From: Pietro Cerutti Date: Thu, 31 Jan 2013 16:39:50 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r246148 - in head: lib/libc/stdio tools/regression/lib/libc/stdio X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 31 Jan 2013 16:39:51 -0000 Author: gahr (ports committer) Date: Thu Jan 31 16:39:50 2013 New Revision: 246148 URL: http://svnweb.freebsd.org/changeset/base/246148 Log: - 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 Modified: head/lib/libc/stdio/fmemopen.c head/lib/libc/stdio/fopen.3 head/tools/regression/lib/libc/stdio/test-fmemopen.c Modified: head/lib/libc/stdio/fmemopen.c ============================================================================== --- head/lib/libc/stdio/fmemopen.c Thu Jan 31 16:04:40 2013 (r246147) +++ head/lib/libc/stdio/fmemopen.c Thu Jan 31 16:39:50 2013 (r246148) @@ -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; - /* actuall wrapper */ - FILE *f = funopen ((void *)ck, fmemopen_read, fmemopen_write, + /* + * 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. */ + 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 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 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, 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 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 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 offs 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 offs static int fmemopen_close (void *cookie) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; if (ck->own) free (ck->buf); Modified: head/lib/libc/stdio/fopen.3 ============================================================================== --- head/lib/libc/stdio/fopen.3 Thu Jan 31 16:04:40 2013 (r246147) +++ head/lib/libc/stdio/fopen.3 Thu Jan 31 16:39:50 2013 (r246148) @@ -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. Modified: head/tools/regression/lib/libc/stdio/test-fmemopen.c ============================================================================== --- head/tools/regression/lib/libc/stdio/test-fmemopen.c Thu Jan 31 16:04:40 2013 (r246147) +++ head/tools/regression/lib/libc/stdio/test-fmemopen.c Thu Jan 31 16:39:50 2013 (r246148) @@ -41,7 +41,7 @@ void test_preexisting () { /* - * use a pre-existing buffer + * Use a pre-existing buffer. */ char buf[512]; @@ -53,48 +53,52 @@ test_preexisting () size_t nofw, nofr; int rc; - /* open a FILE * using fmemopen */ - fp = fmemopen (buf, sizeof buf, "w"); + /* Open a FILE * using fmemopen. */ + fp = fmemopen (buf, sizeof(buf), "w"); assert (fp != NULL); - /* write to the buffer */ - nofw = fwrite (str, 1, sizeof str, fp); - assert (nofw == sizeof str); + /* Write to the buffer. */ + nofw = fwrite (str, 1, sizeof(str), fp); + assert (nofw == sizeof(str)); - /* close the FILE * */ + /* Close the FILE *. */ rc = fclose (fp); assert (rc == 0); - /* re-open the FILE * to read back the data */ - fp = fmemopen (buf, sizeof buf, "r"); + /* Re-open the FILE * to read back the data. */ + fp = fmemopen (buf, sizeof(buf), "r"); assert (fp != NULL); - /* read from the buffer */ - bzero (buf2, sizeof buf2); - nofr = fread (buf2, 1, sizeof buf2, fp); - assert (nofr == sizeof buf2); + /* Read from the buffer. */ + bzero (buf2, sizeof(buf2)); + nofr = fread (buf2, 1, sizeof(buf2), fp); + assert (nofr == sizeof(buf2)); - /* since a write on a FILE * retrieved by fmemopen + /* + * Since a write on a FILE * retrieved by fmemopen * will add a '\0' (if there's space), we can check - * the strings for equality */ + * the strings for equality. + */ assert (strcmp(str, buf2) == 0); - /* close the FILE * */ + /* Close the FILE *. */ rc = fclose (fp); assert (rc == 0); - /* now open a FILE * on the first 4 bytes of the string */ + /* Now open a FILE * on the first 4 bytes of the string. */ fp = fmemopen (str, 4, "w"); assert (fp != NULL); - /* try to write more bytes than we shoud, we'll get a short count (4) */ - nofw = fwrite (str2, 1, sizeof str2, fp); + /* + * Try to write more bytes than we shoud, we'll get a short count (4). + */ + nofw = fwrite (str2, 1, sizeof(str2), fp); assert (nofw == 4); - /* close the FILE * */ + /* Close the FILE *. */ rc = fclose (fp); - /* check that the string was not modified after the first 4 bytes */ + /* Check that the string was not modified after the first 4 bytes. */ assert (strcmp (str, str3) == 0); } @@ -102,7 +106,7 @@ void test_autoalloc () { /* - * let fmemopen allocate the buffer + * Let fmemopen allocate the buffer. */ char str[] = "A quick test"; @@ -111,8 +115,8 @@ test_autoalloc () size_t nofw, nofr, i; int rc; - /* open a FILE * using fmemopen */ - fp = fmemopen (NULL, 512, "w"); + /* Open a FILE * using fmemopen. */ + fp = fmemopen (NULL, 512, "w+"); assert (fp != NULL); /* fill the buffer */ @@ -121,15 +125,118 @@ test_autoalloc () assert (nofw == 1); } - /* get the current position into the stream */ + /* Get the current position into the stream. */ pos = ftell (fp); assert (pos == 512); - /* try to write past the end, we should get a short object count (0) */ + /* + * Try to write past the end, we should get a short object count (0) + */ nofw = fwrite ("a", 1, 1, fp); assert (nofw == 0); - /* close the FILE * */ + /* Close the FILE *. */ + rc = fclose (fp); + assert (rc == 0); +} + +void +test_data_length () +{ + /* + * Here we test that a read operation doesn't go past the end of the + * data actually written, and that a SEEK_END seeks from the end of the + * data, not of the whole buffer. + */ + FILE *fp; + char buf[512] = {'\0'}; + char str[] = "Test data length. "; + char str2[] = "Do we have two sentences?"; + char str3[sizeof(str) + sizeof(str2) -1]; + long pos; + size_t nofw, nofr; + int rc; + + /* Open a FILE * for updating our buffer. */ + fp = fmemopen (buf, sizeof(buf), "w+"); + assert (fp != NULL); + + /* Write our string into the buffer. */ + nofw = fwrite (str, 1, sizeof(str), fp); + assert (nofw == sizeof(str)); + + /* + * Now seek to the end and check that ftell + * gives us sizeof(str). + */ + rc = fseek (fp, 0, SEEK_END); + assert (rc == 0); + pos = ftell (fp); + assert (pos == sizeof(str)); + + /* Close the FILE *. */ + rc = fclose (fp); + assert (rc == 0); + + /* Reopen the buffer for appending. */ + fp = fmemopen (buf, sizeof(buf), "a+"); + assert (fp != NULL); + + /* We should now be writing after the first string. */ + nofw = fwrite (str2, 1, sizeof(str2), fp); + assert (nofw == sizeof(str2)); + + /* Rewind the FILE *. */ + rc = fseek (fp, 0, SEEK_SET); + assert (rc == 0); + + /* Make sure we're at the beginning. */ + pos = ftell (fp); + assert (pos == 0); + + /* Read the whole buffer. */ + nofr = fread (str3, 1, sizeof(buf), fp); + assert (nofr == sizeof(str3)); + + /* Make sure the two strings are there. */ + assert (strncmp (str3, str, sizeof(str) - 1) == 0); + assert (strncmp (str3 + sizeof(str) - 1, str2, sizeof(str2)) == 0); + + /* Close the FILE *. */ + rc = fclose (fp); + assert (rc == 0); +} + +void +test_binary () +{ + /* + * Make sure that NULL bytes are never appended when opening a buffer + * in binary mode. + */ + + FILE *fp; + char buf[20]; + char str[] = "Test"; + size_t nofw; + int rc, i; + + /* Pre-fill the buffer. */ + memset (buf, 'A', sizeof(buf)); + + /* Open a FILE * in binary mode. */ + fp = fmemopen (buf, sizeof(buf), "w+b"); + assert (fp != NULL); + + /* Write some data into it. */ + nofw = fwrite (str, 1, strlen(str), fp); + assert (nofw == strlen(str)); + + /* Make sure that the buffer doesn't contain any NULL bytes. */ + for (i = 0; i < sizeof(buf); i++) + assert (buf[i] != '\0'); + + /* Close the FILE *. */ rc = fclose (fp); assert (rc == 0); } @@ -139,5 +246,7 @@ main (void) { test_autoalloc (); test_preexisting (); + test_data_length (); + test_binary (); return (0); }