Home | History | Annotate | Download | only in libcutils
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #ifndef HAVE_OPEN_MEMSTREAM
     18 
     19 /*
     20  * Implementation of the POSIX open_memstream() function, which Linux has
     21  * but BSD lacks.
     22  *
     23  * Summary:
     24  * - Works like a file-backed FILE* opened with fopen(name, "w"), but the
     25  *   backing is a chunk of memory rather than a file.
     26  * - The buffer expands as you write more data.  Seeking past the end
     27  *   of the file and then writing to it zero-fills the gap.
     28  * - The values at "*bufp" and "*sizep" should be considered read-only,
     29  *   and are only valid immediately after an fflush() or fclose().
     30  * - A '\0' is maintained just past the end of the file. This is not included
     31  *   in "*sizep".  (The behavior w.r.t. fseek() is not clearly defined.
     32  *   The spec says the null byte is written when a write() advances EOF,
     33  *   but it looks like glibc ensures the null byte is always found at EOF,
     34  *   even if you just seeked backwards.  The example on the opengroup.org
     35  *   page suggests that this is the expected behavior.  The null must be
     36  *   present after a no-op fflush(), which we can't see, so we have to save
     37  *   and restore it.  Annoying, but allows file truncation.)
     38  * - After fclose(), the caller must eventually free(*bufp).
     39  *
     40  * This is built out of funopen(), which BSD has but Linux lacks.  There is
     41  * no flush() operator, so we need to keep the user pointers up to date
     42  * after each operation.
     43  *
     44  * I don't think Windows has any of the above, but we don't need to use
     45  * them there, so we just supply a stub.
     46  */
     47 #include <cutils/open_memstream.h>
     48 #include <stdlib.h>
     49 #include <sys/types.h>
     50 #include <unistd.h>
     51 #include <stdio.h>
     52 #include <string.h>
     53 #include <errno.h>
     54 #include <assert.h>
     55 
     56 #if 0
     57 # define DBUG(x) printf x
     58 #else
     59 # define DBUG(x) ((void)0)
     60 #endif
     61 
     62 #ifdef HAVE_FUNOPEN
     63 
     64 /*
     65  * Definition of a seekable, write-only memory stream.
     66  */
     67 typedef struct {
     68     char**      bufp;       /* pointer to buffer pointer */
     69     size_t*     sizep;      /* pointer to eof */
     70 
     71     size_t      allocSize;  /* size of buffer */
     72     size_t      eof;        /* furthest point we've written to */
     73     size_t      offset;     /* current write offset */
     74     char        saved;      /* required by NUL handling */
     75 } MemStream;
     76 
     77 #define kInitialSize    1024
     78 
     79 /*
     80  * Ensure that we have enough storage to write "size" bytes at the
     81  * current offset.  We also have to take into account the extra '\0'
     82  * that we maintain just past EOF.
     83  *
     84  * Returns 0 on success.
     85  */
     86 static int ensureCapacity(MemStream* stream, int writeSize)
     87 {
     88     DBUG(("+++ ensureCap off=%d size=%d\n", stream->offset, writeSize));
     89 
     90     size_t neededSize = stream->offset + writeSize + 1;
     91     if (neededSize <= stream->allocSize)
     92         return 0;
     93 
     94     size_t newSize;
     95 
     96     if (stream->allocSize == 0) {
     97         newSize = kInitialSize;
     98     } else {
     99         newSize = stream->allocSize;
    100         newSize += newSize / 2;             /* expand by 3/2 */
    101     }
    102 
    103     if (newSize < neededSize)
    104         newSize = neededSize;
    105     DBUG(("+++ realloc %p->%p to size=%d\n",
    106         stream->bufp, *stream->bufp, newSize));
    107     char* newBuf = (char*) realloc(*stream->bufp, newSize);
    108     if (newBuf == NULL)
    109         return -1;
    110 
    111     *stream->bufp = newBuf;
    112     stream->allocSize = newSize;
    113     return 0;
    114 }
    115 
    116 /*
    117  * Write data to a memstream, expanding the buffer if necessary.
    118  *
    119  * If we previously seeked beyond EOF, zero-fill the gap.
    120  *
    121  * Returns the number of bytes written.
    122  */
    123 static int write_memstream(void* cookie, const char* buf, int size)
    124 {
    125     MemStream* stream = (MemStream*) cookie;
    126 
    127     if (ensureCapacity(stream, size) < 0)
    128         return -1;
    129 
    130     /* seeked past EOF earlier? */
    131     if (stream->eof < stream->offset) {
    132         DBUG(("+++ zero-fill gap from %d to %d\n",
    133             stream->eof, stream->offset-1));
    134         memset(*stream->bufp + stream->eof, '\0',
    135             stream->offset - stream->eof);
    136     }
    137 
    138     /* copy data, advance write pointer */
    139     memcpy(*stream->bufp + stream->offset, buf, size);
    140     stream->offset += size;
    141 
    142     if (stream->offset > stream->eof) {
    143         /* EOF has advanced, update it and append null byte */
    144         DBUG(("+++ EOF advanced to %d, appending nul\n", stream->offset));
    145         assert(stream->offset < stream->allocSize);
    146         stream->eof = stream->offset;
    147     } else {
    148         /* within previously-written area; save char we're about to stomp */
    149         DBUG(("+++ within written area, saving '%c' at %d\n",
    150             *(*stream->bufp + stream->offset), stream->offset));
    151         stream->saved = *(*stream->bufp + stream->offset);
    152     }
    153     *(*stream->bufp + stream->offset) = '\0';
    154     *stream->sizep = stream->offset;
    155 
    156     return size;
    157 }
    158 
    159 /*
    160  * Seek within a memstream.
    161  *
    162  * Returns the new offset, or -1 on failure.
    163  */
    164 static fpos_t seek_memstream(void* cookie, fpos_t offset, int whence)
    165 {
    166     MemStream* stream = (MemStream*) cookie;
    167     off_t newPosn = (off_t) offset;
    168 
    169     if (whence == SEEK_CUR) {
    170         newPosn += stream->offset;
    171     } else if (whence == SEEK_END) {
    172         newPosn += stream->eof;
    173     }
    174 
    175     if (newPosn < 0 || ((fpos_t)((size_t) newPosn)) != newPosn) {
    176         /* bad offset - negative or huge */
    177         DBUG(("+++ bogus seek offset %ld\n", (long) newPosn));
    178         errno = EINVAL;
    179         return (fpos_t) -1;
    180     }
    181 
    182     if (stream->offset < stream->eof) {
    183         /*
    184          * We were pointing to an area we'd already written to, which means
    185          * we stomped on a character and must now restore it.
    186          */
    187         DBUG(("+++ restoring char '%c' at %d\n",
    188             stream->saved, stream->offset));
    189         *(*stream->bufp + stream->offset) = stream->saved;
    190     }
    191 
    192     stream->offset = (size_t) newPosn;
    193 
    194     if (stream->offset < stream->eof) {
    195         /*
    196          * We're seeked backward into the stream.  Preserve the character
    197          * at EOF and stomp it with a NUL.
    198          */
    199         stream->saved = *(*stream->bufp + stream->offset);
    200         *(*stream->bufp + stream->offset) = '\0';
    201         *stream->sizep = stream->offset;
    202     } else {
    203         /*
    204          * We're positioned at, or possibly beyond, the EOF.  We want to
    205          * publish the current EOF, not the current position.
    206          */
    207         *stream->sizep = stream->eof;
    208     }
    209 
    210     return newPosn;
    211 }
    212 
    213 /*
    214  * Close the memstream.  We free everything but the data buffer.
    215  */
    216 static int close_memstream(void* cookie)
    217 {
    218     free(cookie);
    219     return 0;
    220 }
    221 
    222 /*
    223  * Prepare a memstream.
    224  */
    225 FILE* open_memstream(char** bufp, size_t* sizep)
    226 {
    227     FILE* fp;
    228     MemStream* stream;
    229 
    230     if (bufp == NULL || sizep == NULL) {
    231         errno = EINVAL;
    232         return NULL;
    233     }
    234 
    235     stream = (MemStream*) calloc(1, sizeof(MemStream));
    236     if (stream == NULL)
    237         return NULL;
    238 
    239     fp = funopen(stream,
    240         NULL, write_memstream, seek_memstream, close_memstream);
    241     if (fp == NULL) {
    242         free(stream);
    243         return NULL;
    244     }
    245 
    246     *sizep = 0;
    247     *bufp = NULL;
    248     stream->bufp = bufp;
    249     stream->sizep = sizep;
    250 
    251     return fp;
    252 }
    253 
    254 #else /*not HAVE_FUNOPEN*/
    255 FILE* open_memstream(char** bufp, size_t* sizep)
    256 {
    257     abort();
    258 }
    259 #endif /*HAVE_FUNOPEN*/
    260 
    261 
    262 
    263 #if 0
    264 #define _GNU_SOURCE
    265 #include <stdio.h>
    266 #include <stdlib.h>
    267 #include <string.h>
    268 
    269 /*
    270  * Simple regression test.
    271  *
    272  * To test on desktop Linux with valgrind, it's possible to make a simple
    273  * change to open_memstream() to use fopencookie instead:
    274  *
    275  *  cookie_io_functions_t iofuncs =
    276  *      { NULL, write_memstream, seek_memstream, close_memstream };
    277  *  fp = fopencookie(stream, "w", iofuncs);
    278  *
    279  * (Some tweaks to seek_memstream are also required, as that takes a
    280  * pointer to an offset rather than an offset, and returns 0 or -1.)
    281  */
    282 int testMemStream(void)
    283 {
    284     FILE *stream;
    285     char *buf;
    286     size_t len;
    287     off_t eob;
    288 
    289     printf("Test1\n");
    290 
    291     /* std example */
    292     stream = open_memstream(&buf, &len);
    293     fprintf(stream, "hello my world");
    294     fflush(stream);
    295     printf("buf=%s, len=%zu\n", buf, len);
    296     eob = ftello(stream);
    297     fseeko(stream, 0, SEEK_SET);
    298     fprintf(stream, "good-bye");
    299     fseeko(stream, eob, SEEK_SET);
    300     fclose(stream);
    301     printf("buf=%s, len=%zu\n", buf, len);
    302     free(buf);
    303 
    304     printf("Test2\n");
    305 
    306     /* std example without final seek-to-end */
    307     stream = open_memstream(&buf, &len);
    308     fprintf(stream, "hello my world");
    309     fflush(stream);
    310     printf("buf=%s, len=%zu\n", buf, len);
    311     eob = ftello(stream);
    312     fseeko(stream, 0, SEEK_SET);
    313     fprintf(stream, "good-bye");
    314     //fseeko(stream, eob, SEEK_SET);
    315     fclose(stream);
    316     printf("buf=%s, len=%zu\n", buf, len);
    317     free(buf);
    318 
    319     printf("Test3\n");
    320 
    321     /* fancy example; should expand buffer with writes */
    322     static const int kCmpLen = 1024 + 128;
    323     char* cmp = malloc(kCmpLen);
    324     memset(cmp, 0, 1024);
    325     memset(cmp+1024, 0xff, kCmpLen-1024);
    326     sprintf(cmp, "This-is-a-tes1234");
    327     sprintf(cmp + 1022, "abcdef");
    328 
    329     stream = open_memstream (&buf, &len);
    330     setvbuf(stream, NULL, _IONBF, 0);   /* note: crashes in glibc with this */
    331     fprintf(stream, "This-is-a-test");
    332     fseek(stream, -1, SEEK_CUR);    /* broken in glibc; can use {13,SEEK_SET} */
    333     fprintf(stream, "1234");
    334     fseek(stream, 1022, SEEK_SET);
    335     fputc('a', stream);
    336     fputc('b', stream);
    337     fputc('c', stream);
    338     fputc('d', stream);
    339     fputc('e', stream);
    340     fputc('f', stream);
    341     fflush(stream);
    342 
    343     if (memcmp(buf, cmp, len+1) != 0) {
    344         printf("mismatch\n");
    345     } else {
    346         printf("match\n");
    347     }
    348 
    349     printf("Test4\n");
    350     stream = open_memstream (&buf, &len);
    351     fseek(stream, 5000, SEEK_SET);
    352     fseek(stream, 4096, SEEK_SET);
    353     fseek(stream, -1, SEEK_SET);        /* should have no effect */
    354     fputc('x', stream);
    355     if (ftell(stream) == 4097)
    356         printf("good\n");
    357     else
    358         printf("BAD: offset is %ld\n", ftell(stream));
    359 
    360     printf("DONE\n");
    361 
    362     return 0;
    363 }
    364 
    365 /* expected output:
    366 Test1
    367 buf=hello my world, len=14
    368 buf=good-bye world, len=14
    369 Test2
    370 buf=hello my world, len=14
    371 buf=good-bye, len=8
    372 Test3
    373 match
    374 Test4
    375 good
    376 DONE
    377 */
    378 
    379 #endif
    380 
    381 #endif /*!HAVE_OPEN_MEMSTREAM*/
    382