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 <sys/cdefs.h>
 __FBSDID("$FreeBSD$");
 
+#include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#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);
 }
_______________________________________________
svn-src-all@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to