Index: lib/libc/stdio/fmemopen.c =================================================================== --- lib/libc/stdio/fmemopen.c (revision 246120) +++ lib/libc/stdio/fmemopen.c (working copy) @@ -26,17 +26,21 @@ #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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ break; case SEEK_CUR: - if (ck->off + offset > ck->len) { + if (ck->off + offset > ck->size) { errno = EINVAL; return (-1); } @@ -171,7 +248,7 @@ static int fmemopen_close (void *cookie) { - struct __fmemopen_cookie *ck = cookie; + struct fmemopen_cookie *ck = cookie; if (ck->own) free (ck->buf); Index: lib/libc/stdio/fopen.3 =================================================================== --- lib/libc/stdio/fopen.3 (revision 246120) +++ lib/libc/stdio/fopen.3 (working copy) @@ -118,7 +118,9 @@ 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 @@ 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 @@ .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 @@ function conforms to .St -p1003.1-2008 . +The ``b'' mode does not conform to any standard +but is also supported by glibc. Index: tools/regression/lib/libc/stdio/test-fmemopen.c =================================================================== --- tools/regression/lib/libc/stdio/test-fmemopen.c (revision 246120) +++ tools/regression/lib/libc/stdio/test-fmemopen.c (working copy) @@ -41,7 +41,7 @@ test_preexisting () { /* - * use a pre-existing buffer + * Use a pre-existing buffer. */ char buf[512]; @@ -53,48 +53,52 @@ 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 @@ test_autoalloc () { /* - * let fmemopen allocate the buffer + * Let fmemopen allocate the buffer. */ char str[] = "A quick test"; @@ -111,8 +115,8 @@ 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,23 +125,128 @@ 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); +} + int main (void) { test_autoalloc (); test_preexisting (); + test_data_length (); + test_binary (); return (0); }