open_memstream.c revision cf63d5d00f5a631a2905da7812b5c029b5211cf6
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 <stdio.h> 50#include <string.h> 51#include <errno.h> 52#include <assert.h> 53 54#if 0 55# define DBUG(x) printf x 56#else 57# define DBUG(x) ((void)0) 58#endif 59 60#ifdef HAVE_FUNOPEN 61 62/* 63 * Definition of a seekable, write-only memory stream. 64 */ 65typedef struct { 66 char** bufp; /* pointer to buffer pointer */ 67 size_t* sizep; /* pointer to eof */ 68 69 size_t allocSize; /* size of buffer */ 70 size_t eof; /* furthest point we've written to */ 71 size_t offset; /* current write offset */ 72 char saved; /* required by NUL handling */ 73} MemStream; 74 75#define kInitialSize 1024 76 77/* 78 * Ensure that we have enough storage to write "size" bytes at the 79 * current offset. We also have to take into account the extra '\0' 80 * that we maintain just past EOF. 81 * 82 * Returns 0 on success. 83 */ 84static int ensureCapacity(MemStream* stream, int writeSize) 85{ 86 DBUG(("+++ ensureCap off=%d size=%d\n", stream->offset, writeSize)); 87 88 size_t neededSize = stream->offset + writeSize + 1; 89 if (neededSize <= stream->allocSize) 90 return 0; 91 92 size_t newSize; 93 94 if (stream->allocSize == 0) { 95 newSize = kInitialSize; 96 } else { 97 newSize = stream->allocSize; 98 newSize += newSize / 2; /* expand by 3/2 */ 99 } 100 101 if (newSize < neededSize) 102 newSize = neededSize; 103 DBUG(("+++ realloc %p->%p to size=%d\n", 104 stream->bufp, *stream->bufp, newSize)); 105 char* newBuf = (char*) realloc(*stream->bufp, newSize); 106 if (newBuf == NULL) 107 return -1; 108 109 *stream->bufp = newBuf; 110 stream->allocSize = newSize; 111 return 0; 112} 113 114/* 115 * Write data to a memstream, expanding the buffer if necessary. 116 * 117 * If we previously seeked beyond EOF, zero-fill the gap. 118 * 119 * Returns the number of bytes written. 120 */ 121static int write_memstream(void* cookie, const char* buf, int size) 122{ 123 MemStream* stream = (MemStream*) cookie; 124 125 if (ensureCapacity(stream, size) < 0) 126 return -1; 127 128 /* seeked past EOF earlier? */ 129 if (stream->eof < stream->offset) { 130 DBUG(("+++ zero-fill gap from %d to %d\n", 131 stream->eof, stream->offset-1)); 132 memset(*stream->bufp + stream->eof, '\0', 133 stream->offset - stream->eof); 134 } 135 136 /* copy data, advance write pointer */ 137 memcpy(*stream->bufp + stream->offset, buf, size); 138 stream->offset += size; 139 140 if (stream->offset > stream->eof) { 141 /* EOF has advanced, update it and append null byte */ 142 DBUG(("+++ EOF advanced to %d, appending nul\n", stream->offset)); 143 assert(stream->offset < stream->allocSize); 144 stream->eof = stream->offset; 145 } else { 146 /* within previously-written area; save char we're about to stomp */ 147 DBUG(("+++ within written area, saving '%c' at %d\n", 148 *(*stream->bufp + stream->offset), stream->offset)); 149 stream->saved = *(*stream->bufp + stream->offset); 150 } 151 *(*stream->bufp + stream->offset) = '\0'; 152 *stream->sizep = stream->offset; 153 154 return size; 155} 156 157/* 158 * Seek within a memstream. 159 * 160 * Returns the new offset, or -1 on failure. 161 */ 162static fpos_t seek_memstream(void* cookie, fpos_t offset, int whence) 163{ 164 MemStream* stream = (MemStream*) cookie; 165 off_t newPosn = (off_t) offset; 166 167 if (whence == SEEK_CUR) { 168 newPosn += stream->offset; 169 } else if (whence == SEEK_END) { 170 newPosn += stream->eof; 171 } 172 173 if (newPosn < 0 || ((fpos_t)((size_t) newPosn)) != newPosn) { 174 /* bad offset - negative or huge */ 175 DBUG(("+++ bogus seek offset %ld\n", (long) newPosn)); 176 errno = EINVAL; 177 return (fpos_t) -1; 178 } 179 180 if (stream->offset < stream->eof) { 181 /* 182 * We were pointing to an area we'd already written to, which means 183 * we stomped on a character and must now restore it. 184 */ 185 DBUG(("+++ restoring char '%c' at %d\n", 186 stream->saved, stream->offset)); 187 *(*stream->bufp + stream->offset) = stream->saved; 188 } 189 190 stream->offset = (size_t) newPosn; 191 192 if (stream->offset < stream->eof) { 193 /* 194 * We're seeked backward into the stream. Preserve the character 195 * at EOF and stomp it with a NUL. 196 */ 197 stream->saved = *(*stream->bufp + stream->offset); 198 *(*stream->bufp + stream->offset) = '\0'; 199 *stream->sizep = stream->offset; 200 } else { 201 /* 202 * We're positioned at, or possibly beyond, the EOF. We want to 203 * publish the current EOF, not the current position. 204 */ 205 *stream->sizep = stream->eof; 206 } 207 208 return newPosn; 209} 210 211/* 212 * Close the memstream. We free everything but the data buffer. 213 */ 214static int close_memstream(void* cookie) 215{ 216 free(cookie); 217 return 0; 218} 219 220/* 221 * Prepare a memstream. 222 */ 223FILE* open_memstream(char** bufp, size_t* sizep) 224{ 225 FILE* fp; 226 MemStream* stream; 227 228 if (bufp == NULL || sizep == NULL) { 229 errno = EINVAL; 230 return NULL; 231 } 232 233 stream = (MemStream*) calloc(1, sizeof(MemStream)); 234 if (stream == NULL) 235 return NULL; 236 237 fp = funopen(stream, 238 NULL, write_memstream, seek_memstream, close_memstream); 239 if (fp == NULL) { 240 free(stream); 241 return NULL; 242 } 243 244 *sizep = 0; 245 *bufp = NULL; 246 stream->bufp = bufp; 247 stream->sizep = sizep; 248 249 return fp; 250} 251 252#else /*not HAVE_FUNOPEN*/ 253FILE* open_memstream(char** bufp, size_t* sizep) 254{ 255 abort(); 256} 257#endif /*HAVE_FUNOPEN*/ 258 259 260 261#if 0 262#define _GNU_SOURCE 263#include <stdio.h> 264#include <stdlib.h> 265#include <string.h> 266 267/* 268 * Simple regression test. 269 * 270 * To test on desktop Linux with valgrind, it's possible to make a simple 271 * change to open_memstream() to use fopencookie instead: 272 * 273 * cookie_io_functions_t iofuncs = 274 * { NULL, write_memstream, seek_memstream, close_memstream }; 275 * fp = fopencookie(stream, "w", iofuncs); 276 * 277 * (Some tweaks to seek_memstream are also required, as that takes a 278 * pointer to an offset rather than an offset, and returns 0 or -1.) 279 */ 280int testMemStream(void) 281{ 282 FILE *stream; 283 char *buf; 284 size_t len; 285 off_t eob; 286 287 printf("Test1\n"); 288 289 /* std example */ 290 stream = open_memstream(&buf, &len); 291 fprintf(stream, "hello my world"); 292 fflush(stream); 293 printf("buf=%s, len=%zu\n", buf, len); 294 eob = ftello(stream); 295 fseeko(stream, 0, SEEK_SET); 296 fprintf(stream, "good-bye"); 297 fseeko(stream, eob, SEEK_SET); 298 fclose(stream); 299 printf("buf=%s, len=%zu\n", buf, len); 300 free(buf); 301 302 printf("Test2\n"); 303 304 /* std example without final seek-to-end */ 305 stream = open_memstream(&buf, &len); 306 fprintf(stream, "hello my world"); 307 fflush(stream); 308 printf("buf=%s, len=%zu\n", buf, len); 309 eob = ftello(stream); 310 fseeko(stream, 0, SEEK_SET); 311 fprintf(stream, "good-bye"); 312 //fseeko(stream, eob, SEEK_SET); 313 fclose(stream); 314 printf("buf=%s, len=%zu\n", buf, len); 315 free(buf); 316 317 printf("Test3\n"); 318 319 /* fancy example; should expand buffer with writes */ 320 static const int kCmpLen = 1024 + 128; 321 char* cmp = malloc(kCmpLen); 322 memset(cmp, 0, 1024); 323 memset(cmp+1024, 0xff, kCmpLen-1024); 324 sprintf(cmp, "This-is-a-tes1234"); 325 sprintf(cmp + 1022, "abcdef"); 326 327 stream = open_memstream (&buf, &len); 328 setvbuf(stream, NULL, _IONBF, 0); /* note: crashes in glibc with this */ 329 fprintf(stream, "This-is-a-test"); 330 fseek(stream, -1, SEEK_CUR); /* broken in glibc; can use {13,SEEK_SET} */ 331 fprintf(stream, "1234"); 332 fseek(stream, 1022, SEEK_SET); 333 fputc('a', stream); 334 fputc('b', stream); 335 fputc('c', stream); 336 fputc('d', stream); 337 fputc('e', stream); 338 fputc('f', stream); 339 fflush(stream); 340 341 if (memcmp(buf, cmp, len+1) != 0) { 342 printf("mismatch\n"); 343 } else { 344 printf("match\n"); 345 } 346 347 printf("Test4\n"); 348 stream = open_memstream (&buf, &len); 349 fseek(stream, 5000, SEEK_SET); 350 fseek(stream, 4096, SEEK_SET); 351 fseek(stream, -1, SEEK_SET); /* should have no effect */ 352 fputc('x', stream); 353 if (ftell(stream) == 4097) 354 printf("good\n"); 355 else 356 printf("BAD: offset is %ld\n", ftell(stream)); 357 358 printf("DONE\n"); 359 360 return 0; 361} 362 363/* expected output: 364Test1 365buf=hello my world, len=14 366buf=good-bye world, len=14 367Test2 368buf=hello my world, len=14 369buf=good-bye, len=8 370Test3 371match 372Test4 373good 374DONE 375*/ 376 377#endif 378 379#endif /*!HAVE_OPEN_MEMSTREAM*/ 380