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