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