1 /* 2 * Copyright (C) 2009 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 /* 18 * Strip Android-specific records out of hprof data, back-converting from 19 * 1.0.3 to 1.0.2. This removes some useful information, but allows 20 * Android hprof data to be handled by widely-available tools (like "jhat"). 21 */ 22 #include <stdio.h> 23 #include <string.h> 24 #include <stdlib.h> 25 #include <stdint.h> 26 #include <errno.h> 27 #include <assert.h> 28 #include <unistd.h> 29 30 //#define VERBOSE_DEBUG 31 #ifdef VERBOSE_DEBUG 32 # define DBUG(...) fprintf(stderr, __VA_ARGS__) 33 #else 34 # define DBUG(...) 35 #endif 36 37 #ifndef FALSE 38 # define FALSE 0 39 # define TRUE (!FALSE) 40 #endif 41 42 typedef enum HprofBasicType { 43 HPROF_BASIC_OBJECT = 2, 44 HPROF_BASIC_BOOLEAN = 4, 45 HPROF_BASIC_CHAR = 5, 46 HPROF_BASIC_FLOAT = 6, 47 HPROF_BASIC_DOUBLE = 7, 48 HPROF_BASIC_BYTE = 8, 49 HPROF_BASIC_SHORT = 9, 50 HPROF_BASIC_INT = 10, 51 HPROF_BASIC_LONG = 11, 52 } HprofBasicType; 53 54 typedef enum HprofTag { 55 /* tags we must handle specially */ 56 HPROF_TAG_HEAP_DUMP = 0x0c, 57 HPROF_TAG_HEAP_DUMP_SEGMENT = 0x1c, 58 } HprofTag; 59 60 typedef enum HprofHeapTag { 61 /* 1.0.2 tags */ 62 HPROF_ROOT_UNKNOWN = 0xff, 63 HPROF_ROOT_JNI_GLOBAL = 0x01, 64 HPROF_ROOT_JNI_LOCAL = 0x02, 65 HPROF_ROOT_JAVA_FRAME = 0x03, 66 HPROF_ROOT_NATIVE_STACK = 0x04, 67 HPROF_ROOT_STICKY_CLASS = 0x05, 68 HPROF_ROOT_THREAD_BLOCK = 0x06, 69 HPROF_ROOT_MONITOR_USED = 0x07, 70 HPROF_ROOT_THREAD_OBJECT = 0x08, 71 HPROF_CLASS_DUMP = 0x20, 72 HPROF_INSTANCE_DUMP = 0x21, 73 HPROF_OBJECT_ARRAY_DUMP = 0x22, 74 HPROF_PRIMITIVE_ARRAY_DUMP = 0x23, 75 76 /* Android 1.0.3 tags */ 77 HPROF_HEAP_DUMP_INFO = 0xfe, 78 HPROF_ROOT_INTERNED_STRING = 0x89, 79 HPROF_ROOT_FINALIZING = 0x8a, 80 HPROF_ROOT_DEBUGGER = 0x8b, 81 HPROF_ROOT_REFERENCE_CLEANUP = 0x8c, 82 HPROF_ROOT_VM_INTERNAL = 0x8d, 83 HPROF_ROOT_JNI_MONITOR = 0x8e, 84 HPROF_UNREACHABLE = 0x90, /* deprecated */ 85 HPROF_PRIMITIVE_ARRAY_NODATA_DUMP = 0xc3, 86 } HprofHeapTag; 87 88 typedef enum HprofHeapId { 89 HPROF_HEAP_DEFAULT = 0, 90 HPROF_HEAP_ZYGOTE = 'Z', 91 HPROF_HEAP_APP = 'A', 92 HPROF_HEAP_IMAGE = 'I', 93 } HprofHeapId; 94 95 #define kIdentSize 4 96 #define kRecHdrLen 9 97 98 #define kFlagAppOnly 1 99 100 /* 101 * =========================================================================== 102 * Expanding buffer 103 * =========================================================================== 104 */ 105 106 /* simple struct */ 107 typedef struct { 108 unsigned char* storage; 109 size_t curLen; 110 size_t maxLen; 111 } ExpandBuf; 112 113 /* 114 * Create an ExpandBuf. 115 */ 116 static ExpandBuf* ebAlloc(void) 117 { 118 static const int kInitialSize = 64; 119 120 ExpandBuf* newBuf = (ExpandBuf*) malloc(sizeof(ExpandBuf)); 121 if (newBuf == NULL) 122 return NULL; 123 newBuf->storage = (unsigned char*) malloc(kInitialSize); 124 newBuf->curLen = 0; 125 newBuf->maxLen = kInitialSize; 126 127 return newBuf; 128 } 129 130 /* 131 * Release the storage associated with an ExpandBuf. 132 */ 133 static void ebFree(ExpandBuf* pBuf) 134 { 135 if (pBuf != NULL) { 136 free(pBuf->storage); 137 free(pBuf); 138 } 139 } 140 141 /* 142 * Return a pointer to the data buffer. 143 * 144 * The pointer may change as data is added to the buffer, so this value 145 * should not be cached. 146 */ 147 static inline unsigned char* ebGetBuffer(ExpandBuf* pBuf) 148 { 149 return pBuf->storage; 150 } 151 152 /* 153 * Get the amount of data currently in the buffer. 154 */ 155 static inline size_t ebGetLength(ExpandBuf* pBuf) 156 { 157 return pBuf->curLen; 158 } 159 160 /* 161 * Empty the buffer. 162 */ 163 static void ebClear(ExpandBuf* pBuf) 164 { 165 pBuf->curLen = 0; 166 } 167 168 /* 169 * Ensure that the buffer can hold at least "size" additional bytes. 170 */ 171 static int ebEnsureCapacity(ExpandBuf* pBuf, int size) 172 { 173 assert(size > 0); 174 175 if (pBuf->curLen + size > pBuf->maxLen) { 176 int newSize = pBuf->curLen + size + 128; /* oversize slightly */ 177 unsigned char* newStorage = realloc(pBuf->storage, newSize); 178 if (newStorage == NULL) { 179 fprintf(stderr, "ERROR: realloc failed on size=%d\n", newSize); 180 return -1; 181 } 182 183 pBuf->storage = newStorage; 184 pBuf->maxLen = newSize; 185 } 186 187 assert(pBuf->curLen + size <= pBuf->maxLen); 188 return 0; 189 } 190 191 /* 192 * Add data to the buffer after ensuring it can hold it. 193 */ 194 static int ebAddData(ExpandBuf* pBuf, const void* data, size_t count) 195 { 196 ebEnsureCapacity(pBuf, count); 197 memcpy(pBuf->storage + pBuf->curLen, data, count); 198 pBuf->curLen += count; 199 return 0; 200 } 201 202 /* 203 * Read a NULL-terminated string from the input. 204 */ 205 static int ebReadString(ExpandBuf* pBuf, FILE* in) 206 { 207 int ic; 208 209 do { 210 ebEnsureCapacity(pBuf, 1); 211 212 ic = getc(in); 213 if (feof(in) || ferror(in)) { 214 fprintf(stderr, "ERROR: failed reading input\n"); 215 return -1; 216 } 217 218 pBuf->storage[pBuf->curLen++] = (unsigned char) ic; 219 } while (ic != 0); 220 221 return 0; 222 } 223 224 /* 225 * Read some data, adding it to the expanding buffer. 226 * 227 * This will ensure that the buffer has enough space to hold the new data 228 * (plus the previous contents). 229 */ 230 static int ebReadData(ExpandBuf* pBuf, FILE* in, size_t count, int eofExpected) 231 { 232 size_t actual; 233 234 assert(count > 0); 235 236 ebEnsureCapacity(pBuf, count); 237 actual = fread(pBuf->storage + pBuf->curLen, 1, count, in); 238 if (actual != count) { 239 if (eofExpected && feof(in) && !ferror(in)) { 240 /* return without reporting an error */ 241 } else { 242 fprintf(stderr, "ERROR: read %d of %d bytes\n", actual, count); 243 return -1; 244 } 245 } 246 247 pBuf->curLen += count; 248 assert(pBuf->curLen <= pBuf->maxLen); 249 250 return 0; 251 } 252 253 /* 254 * Write the data from the buffer. Resets the data count to zero. 255 */ 256 static int ebWriteData(ExpandBuf* pBuf, FILE* out) 257 { 258 size_t actual; 259 260 assert(pBuf->curLen > 0); 261 assert(pBuf->curLen <= pBuf->maxLen); 262 263 actual = fwrite(pBuf->storage, 1, pBuf->curLen, out); 264 if (actual != pBuf->curLen) { 265 fprintf(stderr, "ERROR: write %d of %d bytes\n", actual, pBuf->curLen); 266 return -1; 267 } 268 269 pBuf->curLen = 0; 270 271 return 0; 272 } 273 274 275 /* 276 * =========================================================================== 277 * Hprof stuff 278 * =========================================================================== 279 */ 280 281 /* 282 * Get a 2-byte value, in big-endian order, from memory. 283 */ 284 static uint16_t get2BE(const unsigned char* buf) 285 { 286 uint16_t val; 287 288 val = (buf[0] << 8) | buf[1]; 289 return val; 290 } 291 292 /* 293 * Get a 4-byte value, in big-endian order, from memory. 294 */ 295 static uint32_t get4BE(const unsigned char* buf) 296 { 297 uint32_t val; 298 299 val = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; 300 return val; 301 } 302 303 /* 304 * Set a 4-byte value, in big-endian order. 305 */ 306 static void set4BE(unsigned char* buf, uint32_t val) 307 { 308 buf[0] = val >> 24; 309 buf[1] = val >> 16; 310 buf[2] = val >> 8; 311 buf[3] = val; 312 } 313 314 /* 315 * Get the size, in bytes, of one of the "basic types". 316 */ 317 static int computeBasicLen(HprofBasicType basicType) 318 { 319 static const int sizes[] = { -1, -1, 4, -1, 1, 2, 4, 8, 1, 2, 4, 8 }; 320 static const size_t maxSize = sizeof(sizes) / sizeof(sizes[0]); 321 322 assert(basicType >= 0); 323 if (basicType >= maxSize) 324 return -1; 325 return sizes[basicType]; 326 } 327 328 /* 329 * Compute the length of a HPROF_CLASS_DUMP block. 330 */ 331 static int computeClassDumpLen(const unsigned char* origBuf, int len) 332 { 333 const unsigned char* buf = origBuf; 334 int blockLen = 0; 335 int i, count; 336 337 blockLen += kIdentSize * 7 + 8; 338 buf += blockLen; 339 len -= blockLen; 340 341 if (len < 0) 342 return -1; 343 344 count = get2BE(buf); 345 buf += 2; 346 len -= 2; 347 DBUG("CDL: 1st count is %d\n", count); 348 for (i = 0; i < count; i++) { 349 HprofBasicType basicType; 350 int basicLen; 351 352 basicType = buf[2]; 353 basicLen = computeBasicLen(basicType); 354 if (basicLen < 0) { 355 DBUG("ERROR: invalid basicType %d\n", basicType); 356 return -1; 357 } 358 359 buf += 2 + 1 + basicLen; 360 len -= 2 + 1 + basicLen; 361 if (len < 0) 362 return -1; 363 } 364 365 count = get2BE(buf); 366 buf += 2; 367 len -= 2; 368 DBUG("CDL: 2nd count is %d\n", count); 369 for (i = 0; i < count; i++) { 370 HprofBasicType basicType; 371 int basicLen; 372 373 basicType = buf[kIdentSize]; 374 basicLen = computeBasicLen(basicType); 375 if (basicLen < 0) { 376 fprintf(stderr, "ERROR: invalid basicType %d\n", basicType); 377 return -1; 378 } 379 380 buf += kIdentSize + 1 + basicLen; 381 len -= kIdentSize + 1 + basicLen; 382 if (len < 0) 383 return -1; 384 } 385 386 count = get2BE(buf); 387 buf += 2; 388 len -= 2; 389 DBUG("CDL: 3rd count is %d\n", count); 390 for (i = 0; i < count; i++) { 391 buf += kIdentSize + 1; 392 len -= kIdentSize + 1; 393 if (len < 0) 394 return -1; 395 } 396 397 DBUG("Total class dump len: %d\n", buf - origBuf); 398 return buf - origBuf; 399 } 400 401 /* 402 * Compute the length of a HPROF_INSTANCE_DUMP block. 403 */ 404 static int computeInstanceDumpLen(const unsigned char* origBuf, int len) 405 { 406 int extraCount = get4BE(origBuf + kIdentSize * 2 + 4); 407 return kIdentSize * 2 + 8 + extraCount; 408 } 409 410 /* 411 * Compute the length of a HPROF_OBJECT_ARRAY_DUMP block. 412 */ 413 static int computeObjectArrayDumpLen(const unsigned char* origBuf, int len) 414 { 415 int arrayCount = get4BE(origBuf + kIdentSize + 4); 416 return kIdentSize * 2 + 8 + arrayCount * kIdentSize; 417 } 418 419 /* 420 * Compute the length of a HPROF_PRIMITIVE_ARRAY_DUMP block. 421 */ 422 static int computePrimitiveArrayDumpLen(const unsigned char* origBuf, int len) 423 { 424 int arrayCount = get4BE(origBuf + kIdentSize + 4); 425 HprofBasicType basicType = origBuf[kIdentSize + 8]; 426 int basicLen = computeBasicLen(basicType); 427 428 return kIdentSize + 9 + arrayCount * basicLen; 429 } 430 431 /* 432 * Crunch through a heap dump record, writing the original or converted 433 * data to "out". 434 */ 435 static int processHeapDump(ExpandBuf* pBuf, FILE* out, int flags) 436 { 437 ExpandBuf* pOutBuf = ebAlloc(); 438 unsigned char* origBuf = ebGetBuffer(pBuf); 439 unsigned char* buf = origBuf; 440 int len = ebGetLength(pBuf); 441 int result = -1; 442 int heapType = HPROF_HEAP_DEFAULT; 443 int heapIgnore = FALSE; 444 445 pBuf = NULL; /* we just use the raw pointer from here forward */ 446 447 /* copy the original header to the output buffer */ 448 if (ebAddData(pOutBuf, buf, kRecHdrLen) != 0) 449 goto bail; 450 451 buf += kRecHdrLen; /* skip past record header */ 452 len -= kRecHdrLen; 453 454 while (len > 0) { 455 unsigned char subType = buf[0]; 456 int justCopy = TRUE; 457 int subLen; 458 459 DBUG("--- 0x%02x ", subType); 460 switch (subType) { 461 /* 1.0.2 types */ 462 case HPROF_ROOT_UNKNOWN: 463 subLen = kIdentSize; 464 break; 465 case HPROF_ROOT_JNI_GLOBAL: 466 subLen = kIdentSize * 2; 467 break; 468 case HPROF_ROOT_JNI_LOCAL: 469 subLen = kIdentSize + 8; 470 break; 471 case HPROF_ROOT_JAVA_FRAME: 472 subLen = kIdentSize + 8; 473 break; 474 case HPROF_ROOT_NATIVE_STACK: 475 subLen = kIdentSize + 4; 476 break; 477 case HPROF_ROOT_STICKY_CLASS: 478 subLen = kIdentSize; 479 break; 480 case HPROF_ROOT_THREAD_BLOCK: 481 subLen = kIdentSize + 4; 482 break; 483 case HPROF_ROOT_MONITOR_USED: 484 subLen = kIdentSize; 485 break; 486 case HPROF_ROOT_THREAD_OBJECT: 487 subLen = kIdentSize + 8; 488 break; 489 case HPROF_CLASS_DUMP: 490 subLen = computeClassDumpLen(buf+1, len-1); 491 break; 492 case HPROF_INSTANCE_DUMP: 493 subLen = computeInstanceDumpLen(buf+1, len-1); 494 if (heapIgnore) { 495 justCopy = FALSE; 496 } 497 break; 498 case HPROF_OBJECT_ARRAY_DUMP: 499 subLen = computeObjectArrayDumpLen(buf+1, len-1); 500 if (heapIgnore) { 501 justCopy = FALSE; 502 } 503 break; 504 case HPROF_PRIMITIVE_ARRAY_DUMP: 505 subLen = computePrimitiveArrayDumpLen(buf+1, len-1); 506 if (heapIgnore) { 507 justCopy = FALSE; 508 } 509 break; 510 /* these were added for Android in 1.0.3 */ 511 case HPROF_HEAP_DUMP_INFO: 512 heapType = get4BE(buf+1); 513 if ((flags & kFlagAppOnly) != 0 514 && (heapType == HPROF_HEAP_ZYGOTE || heapType == HPROF_HEAP_IMAGE)) { 515 heapIgnore = TRUE; 516 } else { 517 heapIgnore = FALSE; 518 } 519 justCopy = FALSE; 520 subLen = kIdentSize + 4; 521 // no 1.0.2 equivalent for this 522 break; 523 case HPROF_ROOT_INTERNED_STRING: 524 buf[0] = HPROF_ROOT_UNKNOWN; 525 subLen = kIdentSize; 526 break; 527 case HPROF_ROOT_FINALIZING: 528 buf[0] = HPROF_ROOT_UNKNOWN; 529 subLen = kIdentSize; 530 break; 531 case HPROF_ROOT_DEBUGGER: 532 buf[0] = HPROF_ROOT_UNKNOWN; 533 subLen = kIdentSize; 534 break; 535 case HPROF_ROOT_REFERENCE_CLEANUP: 536 buf[0] = HPROF_ROOT_UNKNOWN; 537 subLen = kIdentSize; 538 break; 539 case HPROF_ROOT_VM_INTERNAL: 540 buf[0] = HPROF_ROOT_UNKNOWN; 541 subLen = kIdentSize; 542 break; 543 case HPROF_ROOT_JNI_MONITOR: 544 /* keep the ident, drop the next 8 bytes */ 545 buf[0] = HPROF_ROOT_UNKNOWN; 546 justCopy = FALSE; 547 ebAddData(pOutBuf, buf, 1 + kIdentSize); 548 subLen = kIdentSize + 8; 549 break; 550 case HPROF_UNREACHABLE: 551 buf[0] = HPROF_ROOT_UNKNOWN; 552 subLen = kIdentSize; 553 break; 554 case HPROF_PRIMITIVE_ARRAY_NODATA_DUMP: 555 buf[0] = HPROF_PRIMITIVE_ARRAY_DUMP; 556 buf[5] = buf[6] = buf[7] = buf[8] = 0; /* set array len to 0 */ 557 subLen = kIdentSize + 9; 558 break; 559 560 /* shouldn't get here */ 561 default: 562 fprintf(stderr, "ERROR: unexpected subtype 0x%02x at offset %d\n", 563 subType, buf - origBuf); 564 goto bail; 565 } 566 567 if (justCopy) { 568 /* copy source data */ 569 DBUG("(%d)\n", 1 + subLen); 570 ebAddData(pOutBuf, buf, 1 + subLen); 571 } else { 572 /* other data has been written, or the sub-record omitted */ 573 DBUG("(adv %d)\n", 1 + subLen); 574 } 575 576 /* advance to next entry */ 577 buf += 1 + subLen; 578 len -= 1 + subLen; 579 } 580 581 /* 582 * Update the record length. 583 */ 584 set4BE(ebGetBuffer(pOutBuf) + 5, ebGetLength(pOutBuf) - kRecHdrLen); 585 586 if (ebWriteData(pOutBuf, out) != 0) 587 goto bail; 588 589 result = 0; 590 591 bail: 592 ebFree(pOutBuf); 593 return result; 594 } 595 596 /* 597 * Filter an hprof data file. 598 */ 599 static int filterData(FILE* in, FILE* out, int flags) 600 { 601 const char *magicString; 602 ExpandBuf* pBuf; 603 int result = -1; 604 605 pBuf = ebAlloc(); 606 if (pBuf == NULL) 607 goto bail; 608 609 /* 610 * Start with the header. 611 */ 612 if (ebReadString(pBuf, in) != 0) 613 goto bail; 614 615 magicString = (const char*)ebGetBuffer(pBuf); 616 if (strcmp(magicString, "JAVA PROFILE 1.0.3") != 0) { 617 if (strcmp(magicString, "JAVA PROFILE 1.0.2") == 0) { 618 fprintf(stderr, "ERROR: HPROF file already in 1.0.2 format.\n"); 619 } else { 620 fprintf(stderr, "ERROR: expecting HPROF file format 1.0.3\n"); 621 } 622 goto bail; 623 } 624 625 /* downgrade to 1.0.2 */ 626 (ebGetBuffer(pBuf))[17] = '2'; 627 if (ebWriteData(pBuf, out) != 0) 628 goto bail; 629 630 /* 631 * Copy: 632 * (4b) identifier size, always 4 633 * (8b) file creation date 634 */ 635 if (ebReadData(pBuf, in, 12, FALSE) != 0) 636 goto bail; 637 if (ebWriteData(pBuf, out) != 0) 638 goto bail; 639 640 /* 641 * Read records until we hit EOF. Each record begins with: 642 * (1b) type 643 * (4b) timestamp 644 * (4b) length of data that follows 645 */ 646 while (1) { 647 assert(ebGetLength(pBuf) == 0); 648 649 /* read type char */ 650 if (ebReadData(pBuf, in, 1, TRUE) != 0) 651 goto bail; 652 if (feof(in)) 653 break; 654 655 /* read the rest of the header */ 656 if (ebReadData(pBuf, in, kRecHdrLen-1, FALSE) != 0) 657 goto bail; 658 659 unsigned char* buf = ebGetBuffer(pBuf); 660 unsigned char type; 661 unsigned int timestamp, length; 662 663 type = buf[0]; 664 timestamp = get4BE(buf + 1); 665 length = get4BE(buf + 5); 666 buf = NULL; /* ptr invalid after next read op */ 667 668 /* read the record data */ 669 if (length != 0) { 670 if (ebReadData(pBuf, in, length, FALSE) != 0) 671 goto bail; 672 } 673 674 if (type == HPROF_TAG_HEAP_DUMP 675 || type == HPROF_TAG_HEAP_DUMP_SEGMENT) { 676 DBUG("Processing heap dump 0x%02x (%d bytes)\n", 677 type, length); 678 if (processHeapDump(pBuf, out, flags) != 0) 679 goto bail; 680 ebClear(pBuf); 681 } else { 682 /* keep */ 683 DBUG("Keeping 0x%02x (%d bytes)\n", type, length); 684 if (ebWriteData(pBuf, out) != 0) 685 goto bail; 686 } 687 } 688 689 result = 0; 690 691 bail: 692 ebFree(pBuf); 693 return result; 694 } 695 696 static FILE* fopen_or_default(const char* path, const char* mode, FILE* def) { 697 if (!strcmp(path, "-")) { 698 return def; 699 } else { 700 return fopen(path, mode); 701 } 702 } 703 704 int main(int argc, char** argv) 705 { 706 FILE* in = NULL; 707 FILE* out = NULL; 708 int flags = 0; 709 int res = 1; 710 711 int opt; 712 while ((opt = getopt(argc, argv, "z")) != -1) { 713 switch (opt) { 714 case 'z': 715 flags |= kFlagAppOnly; 716 break; 717 case '?': 718 default: 719 goto usage; 720 } 721 } 722 723 int i; 724 for (i = optind; i < argc; i++) { 725 char* arg = argv[i]; 726 if (!in) { 727 in = fopen_or_default(arg, "rb", stdin); 728 } else if (!out) { 729 out = fopen_or_default(arg, "wb", stdout); 730 } else { 731 goto usage; 732 } 733 } 734 735 if (in == NULL || out == NULL) { 736 goto usage; 737 } 738 739 res = filterData(in, out, flags); 740 goto finish; 741 742 usage: 743 fprintf(stderr, "Usage: hprof-conf [-z] infile outfile\n"); 744 fprintf(stderr, "\n"); 745 fprintf(stderr, " -z: exclude non-app heaps, such as Zygote\n"); 746 fprintf(stderr, "\n"); 747 fprintf(stderr, "Specify '-' for either or both files to use stdin/stdout.\n"); 748 fprintf(stderr, "\n"); 749 750 fprintf(stderr, 751 "Copyright (C) 2009 The Android Open Source Project\n\n" 752 "This software is built from source code licensed under the " 753 "Apache License,\n" 754 "Version 2.0 (the \"License\"). You may obtain a copy of the " 755 "License at\n\n" 756 " http://www.apache.org/licenses/LICENSE-2.0\n\n" 757 "See the associated NOTICE file for this software for further " 758 "details.\n"); 759 res = 2; 760 761 finish: 762 if (in != stdin) 763 fclose(in); 764 if (out != stdout) 765 fclose(out); 766 return res; 767 } 768