- 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
This commit is contained in:
parent
7a06a9247d
commit
646b68f04d
@ -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));
|
||||
if (ck == NULL) {
|
||||
errno = ENOMEM;
|
||||
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);
|
||||
}
|
||||
|
||||
ck->off = 0;
|
||||
ck->len = size;
|
||||
/*
|
||||
* 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) {
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* do we have to allocate the buffer ourselves? */
|
||||
ck->off = 0;
|
||||
ck->size = size;
|
||||
|
||||
/* 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 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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user