source: trunk/src/kDepIDB/kDepIDB.c@ 393

Last change on this file since 393 was 393, checked in by bird, 20 years ago
  • src/kDepIDB/kDepIDB.c:

o Initial coding. (This is a VC++ dependency extractor.)

  • src/kDepPre/kDepPre.c, src/lib/kDep.h, src/lib/kDep.c, Config.kmk:

o Created a library for the dep*() functions.
o Removed the IDB hacks from kDepPre.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1/* $Id: kDepIDB.c 393 2006-01-13 00:42:10Z bird $ */
2/** @file
3 *
4 * kDepIDB - Extract dependency information from a MS Visual C++ .idb file.
5 *
6 * Copyright (c) 2006 knut st. osmundsen <bird@innotek.de>
7 *
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
25 */
26
27
28/*******************************************************************************
29* Header Files *
30*******************************************************************************/
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <errno.h>
35#include <ctype.h>
36#ifndef __WIN32__
37# include <stdint.h>
38#else
39 typedef unsigned char uint8_t;
40 typedef unsigned short uint16_t;
41 typedef unsigned int uint32_t;
42#endif
43#include "kDep.h"
44
45#define OFFSETOF(type, member) ( (int)(void *)&( ((type *)(void *)0)->member) )
46
47
48
49/*******************************************************************************
50* Global Variables *
51*******************************************************************************/
52/** the executable name. */
53static const char *argv0 = "";
54
55
56/**
57 * Scans a stream (chunk of data really) for dependencies.
58 *
59 * @returns 0 on success.
60 * @returns !0 on failure.
61 * @param pbStream The stream bits.
62 * @param cbStream The size of the stream.
63 * @param pszPrefix The dependency prefix.
64 * @param cchPrefix The size of the prefix.
65 */
66static int ScanStream(uint8_t *pbStream, size_t cbStream, const char *pszPrefix, size_t cchPrefix)
67{
68 register char chFirst = *pszPrefix;
69 while (cbStream > cchPrefix + 2)
70 {
71 if ( *pbStream != chFirst
72 || memcmp(pbStream, pszPrefix, cchPrefix))
73 {
74 pbStream++;
75 cbStream--;
76 }
77 else
78 {
79 size_t cchDep;
80 pbStream += cchPrefix;
81 cchDep = strlen(pbStream);
82 depAdd(pbStream, cchDep);
83
84 pbStream += cchDep;
85 cbStream -= cchDep + cchPrefix;
86 }
87 }
88
89 return 0;
90}
91
92
93
94/**
95 * Reads the file specified by the pInput file stream into memory.
96 * The size of the file is returned in *pcbFile if specified.
97 * The returned pointer should be freed by free().
98 */
99void *ReadFileIntoMemory(FILE *pInput, size_t *pcbFile)
100{
101 void *pvFile;
102 long cbFile;
103 int rc = 0;
104
105 /*
106 * Figure out file size.
107 */
108 if ( fseek(pInput, 0, SEEK_END) < 0
109 || (cbFile = ftell(pInput)) < 0
110 || fseek(pInput, 0, SEEK_SET))
111 {
112 fprintf(stderr, "%s: error: Failed to determin file size.\n", argv0);
113 return NULL;
114 }
115 if (pcbFile)
116 *pcbFile = cbFile;
117
118 /*
119 * Allocate memory and read the file.
120 */
121 pvFile = malloc(cbFile + 1);
122 if (pvFile)
123 {
124 if (fread(pvFile, cbFile, 1, pInput))
125 {
126 ((uint8_t *)pvFile)[cbFile] = '\0';
127 return pvFile;
128 }
129 fprintf(stderr, "%s: error: Failed to read %ld bytes.\n", argv0, cbFile);
130 free(pvFile);
131 }
132 else
133 fprintf(stderr, "%s: error: Failed to allocate %ld bytes (file mapping).\n", argv0, cbFile);
134 return NULL;
135}
136
137
138
139///////////////////////////////////////////////////////////////////////////////
140//
141//
142// P D B 7 . 0
143//
144//
145///////////////////////////////////////////////////////////////////////////////
146
147/** A PDB 7.0 Page number. */
148typedef uint32_t PDB70PAGE;
149/** Pointer to a PDB 7.0 Page number. */
150typedef PDB70PAGE *PPDB70PAGE;
151
152/**
153 * A PDB 7.0 stream.
154 */
155typedef struct PDB70STREAM
156{
157 /** The size of the stream. */
158 uint32_t cbStream;
159} PDB70STREAM, *PPDB70STREAM;
160
161
162/** The PDB 7.00 signature. */
163#define PDB_SIGNATURE_700 "Microsoft C/C++ MSF 7.00\r\n\x1A" "DS\0\0"
164/**
165 * The PDB 7.0 header.
166 */
167typedef struct PDB70HDR
168{
169 /** The signature string. */
170 uint8_t szSignature[sizeof(PDB_SIGNATURE_700)];
171 /** The page size. */
172 uint32_t cbPage;
173 /** The start page. */
174 PDB70PAGE iStartPage;
175 /** The number of pages in the file. */
176 PDB70PAGE cPages;
177 /** The root stream directory. */
178 uint32_t cbRoot;
179 /** Unknown function, always 0. */
180 uint32_t u32Reserved;
181 /** The page index of the root page table. */
182 PDB70PAGE iRootPages;
183} PDB70HDR, *PPDB70HDR;
184
185/**
186 * The PDB 7.0 root directory.
187 */
188typedef struct PDB70ROOT
189{
190 /** The number of streams */
191 uint32_t cStreams;
192 /** Array of streams. */
193 PDB70STREAM aStreams[1];
194 /* uint32_t aiPages[] */
195} PDB70ROOT, *PPDB70ROOT;
196
197
198
199static int Pdb70ValidateHeader(PPDB70HDR pHdr, size_t cbFile)
200{
201 if (pHdr->cbPage * pHdr->cPages != cbFile)
202 {
203 fprintf(stderr, "%s: error: Bad PDB 2.0 header - cbPage * cPages != cbFile.\n", argv0);
204 return 1;
205 }
206 if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
207 {
208 fprintf(stderr, "%s: error: Bad PDB 2.0 header - cbPage * cPages != cbFile.\n", argv0);
209 return 1;
210 }
211 return 0;
212}
213
214static size_t Pdb70Pages(PPDB70HDR pHdr, size_t cb)
215{
216 if (cb == ~(uint32_t)0 || !cb)
217 return 0;
218 return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
219}
220
221static void *Pdb70AllocAndRead(PPDB70HDR pHdr, size_t cb, PPDB70PAGE paiPageMap)
222{
223 size_t cPages = Pdb70Pages(pHdr, cb);
224 uint8_t *pbBuf = malloc(cPages * pHdr->cbPage + 1);
225 if (pbBuf)
226 {
227 size_t iPage = 0;
228 while (iPage < cPages)
229 {
230 size_t off = paiPageMap[iPage];
231 off *= pHdr->cbPage;
232 memcpy(pbBuf + iPage * pHdr->cbPage, (uint8_t *)pHdr + off, pHdr->cbPage);
233 iPage++;
234 }
235 pbBuf[cPages * pHdr->cbPage] = '\0';
236 }
237 else
238 fprintf(stderr, "%s: error: failed to allocate %d bytes\n", argv0, cPages * pHdr->cbPage + 1);
239 return pbBuf;
240}
241
242static PPDB70ROOT Pdb70AllocAndReadRoot(PPDB70HDR pHdr)
243{
244 /*
245 * The tricky bit here is to find the right length.
246 * (Todo: Check if we can just use the stream size..)
247 */
248 PPDB70PAGE piPageMap = (uint32_t *)((uint8_t *)pHdr + pHdr->iRootPages * pHdr->cbPage);
249 PPDB70ROOT pRoot = Pdb70AllocAndRead(pHdr, pHdr->cbRoot, piPageMap);
250 if (pRoot)
251 {
252 /* This stuff is probably unnecessary: */
253 /* size = stream header + array of stream. */
254 size_t cb = OFFSETOF(PDB70ROOT, aStreams[pRoot->cStreams]);
255 free(pRoot);
256 pRoot = Pdb70AllocAndRead(pHdr, cb, piPageMap);
257 if (pRoot)
258 {
259 /* size += page tables. */
260 unsigned iStream = pRoot->cStreams;
261 while (iStream-- > 0)
262 if (pRoot->aStreams[iStream].cbStream != ~(uint32_t)0)
263 cb += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB70PAGE);
264 free(pRoot);
265 pRoot = Pdb70AllocAndRead(pHdr, cb, piPageMap);
266 if (pRoot)
267 {
268 /* validate? */
269 return pRoot;
270 }
271 }
272 }
273 return NULL;
274}
275
276static void *Pdb70AllocAndReadStream(PPDB70HDR pHdr, PPDB70ROOT pRoot, unsigned iStream, size_t *pcbStream)
277{
278 size_t cbStream = pRoot->aStreams[iStream].cbStream;
279 PPDB70PAGE paiPageMap;
280 if ( iStream >= pRoot->cStreams
281 || cbStream == ~(uint32_t)0)
282 {
283 fprintf(stderr, "%s: error: Invalid stream %d\n", iStream);
284 return NULL;
285 }
286
287 paiPageMap = (PPDB70PAGE)&pRoot->aStreams[pRoot->cStreams];
288 while (iStream-- > 0)
289 if (pRoot->aStreams[iStream].cbStream != ~(uint32_t)0)
290 paiPageMap += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream) / pHdr->cbPage;
291
292 if (pcbStream)
293 *pcbStream = cbStream;
294 return Pdb70AllocAndRead(pHdr, cbStream, paiPageMap);
295}
296
297static int Pdb70Process(uint8_t *pbFile, size_t cbFile)
298{
299 PPDB70HDR pHdr = (PPDB70HDR)pbFile;
300 PPDB70ROOT pRoot;
301 unsigned iStream;
302 int rc = 0;
303
304 /*
305 * Validate the header and read the root stream.
306 */
307 if (Pdb70ValidateHeader(pHdr, cbFile))
308 return 1;
309 pRoot = Pdb70AllocAndReadRoot(pHdr);
310 if (!pRoot)
311 return 1;
312
313 /*
314 * Iterate the streams in the root and scan their content for
315 * dependencies.
316 */
317 rc = 0;
318 for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
319 {
320 uint8_t *pbStream;
321 if ( pRoot->aStreams[iStream].cbStream == ~(uint32_t)0
322 || !pRoot->aStreams[iStream].cbStream)
323 continue;
324 pbStream = (uint8_t *)Pdb70AllocAndReadStream(pHdr, pRoot, iStream, NULL);
325 if (pbStream)
326 {
327 rc = ScanStream(pbStream, pRoot->aStreams[iStream].cbStream, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1);
328 free(pbStream);
329 }
330 else
331 rc = 1;
332 }
333
334 free(pRoot);
335 return rc;
336}
337
338
339
340///////////////////////////////////////////////////////////////////////////////
341//
342//
343// P D B 2 . 0
344//
345//
346///////////////////////////////////////////////////////////////////////////////
347
348
349/** A PDB 2.0 Page number. */
350typedef uint16_t PDB20PAGE;
351/** Pointer to a PDB 2.0 Page number. */
352typedef PDB20PAGE *PPDB20PAGE;
353
354/**
355 * A PDB 2.0 stream.
356 */
357typedef struct PDB20STREAM
358{
359 /** The size of the stream. */
360 uint32_t cbStream;
361 /** Some unknown value. */
362 uint32_t u32Unknown;
363} PDB20STREAM, *PPDB20STREAM;
364
365/** The PDB 2.00 signature. */
366#define PDB_SIGNATURE_200 "Microsoft C/C++ program database 2.00\r\n\x1A" "JG\0"
367/**
368 * The PDB 2.0 header.
369 */
370typedef struct PDB20HDR
371{
372 /** The signature string. */
373 uint8_t szSignature[sizeof(PDB_SIGNATURE_200)];
374 /** The page size. */
375 uint32_t cbPage;
376 /** The start page - whatever that is... */
377 PDB20PAGE iStartPage;
378 /** The number of pages in the file. */
379 PDB20PAGE cPages;
380 /** The root stream directory. */
381 PDB20STREAM RootStream;
382 /** The root page table. */
383 PDB20PAGE aiRootPageMap[1];
384} PDB20HDR, *PPDB20HDR;
385
386/**
387 * The PDB 2.0 root directory.
388 */
389typedef struct PDB20ROOT
390{
391 /** The number of streams */
392 uint16_t cStreams;
393 /** Reserved or high part of cStreams. */
394 uint16_t u16Reserved;
395 /** Array of streams. */
396 PDB20STREAM aStreams[1];
397} PDB20ROOT, *PPDB20ROOT;
398
399
400static int Pdb20ValidateHeader(PPDB20HDR pHdr, size_t cbFile)
401{
402 if (pHdr->cbPage * pHdr->cPages != cbFile)
403 {
404 fprintf(stderr, "%s: error: Bad PDB 2.0 header - cbPage * cPages != cbFile.\n", argv0);
405 return 1;
406 }
407 if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
408 {
409 fprintf(stderr, "%s: error: Bad PDB 2.0 header - cbPage * cPages != cbFile.\n", argv0);
410 return 1;
411 }
412 return 0;
413}
414
415static size_t Pdb20Pages(PPDB20HDR pHdr, size_t cb)
416{
417 if (cb == ~(uint32_t)0 || !cb)
418 return 0;
419 return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
420}
421
422static void *Pdb20AllocAndRead(PPDB20HDR pHdr, size_t cb, PPDB20PAGE paiPageMap)
423{
424 size_t cPages = Pdb20Pages(pHdr, cb);
425 uint8_t *pbBuf = malloc(cPages * pHdr->cbPage + 1);
426 if (pbBuf)
427 {
428 size_t iPage = 0;
429 while (iPage < cPages)
430 {
431 size_t off = paiPageMap[iPage];
432 off *= pHdr->cbPage;
433 memcpy(pbBuf + iPage * pHdr->cbPage, (uint8_t *)pHdr + off, pHdr->cbPage);
434 iPage++;
435 }
436 pbBuf[cPages * pHdr->cbPage] = '\0';
437 }
438 else
439 fprintf(stderr, "%s: error: failed to allocate %d bytes\n", argv0, cPages * pHdr->cbPage + 1);
440 return pbBuf;
441}
442
443static PPDB20ROOT Pdb20AllocAndReadRoot(PPDB20HDR pHdr)
444{
445 /*
446 * The tricky bit here is to find the right length.
447 * (Todo: Check if we can just use the stream size..)
448 */
449 PPDB20ROOT pRoot = Pdb20AllocAndRead(pHdr, sizeof(*pRoot), &pHdr->aiRootPageMap[0]);
450 if (pRoot)
451 {
452 /* size = stream header + array of stream. */
453 size_t cb = OFFSETOF(PDB20ROOT, aStreams[pRoot->cStreams]);
454 free(pRoot);
455 pRoot = Pdb20AllocAndRead(pHdr, cb, &pHdr->aiRootPageMap[0]);
456 if (pRoot)
457 {
458 /* size += page tables. */
459 unsigned iStream = pRoot->cStreams;
460 while (iStream-- > 0)
461 if (pRoot->aStreams[iStream].cbStream != ~(uint32_t)0)
462 cb += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB20PAGE);
463 free(pRoot);
464 pRoot = Pdb20AllocAndRead(pHdr, cb, &pHdr->aiRootPageMap[0]);
465 if (pRoot)
466 {
467 /* validate? */
468 return pRoot;
469 }
470 }
471 }
472 return NULL;
473
474}
475
476static void *Pdb20AllocAndReadStream(PPDB20HDR pHdr, PPDB20ROOT pRoot, unsigned iStream, size_t *pcbStream)
477{
478 size_t cbStream = pRoot->aStreams[iStream].cbStream;
479 PPDB20PAGE paiPageMap;
480 if ( iStream >= pRoot->cStreams
481 || cbStream == ~(uint32_t)0)
482 {
483 fprintf(stderr, "%s: error: Invalid stream %d\n", iStream);
484 return NULL;
485 }
486
487 paiPageMap = (PPDB20PAGE)&pRoot->aStreams[pRoot->cStreams];
488 while (iStream-- > 0)
489 if (pRoot->aStreams[iStream].cbStream != ~(uint32_t)0)
490 paiPageMap += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream);
491
492 if (pcbStream)
493 *pcbStream = cbStream;
494 return Pdb20AllocAndRead(pHdr, cbStream, paiPageMap);
495}
496
497static int Pdb20Process(uint8_t *pbFile, size_t cbFile)
498{
499 PPDB20HDR pHdr = (PPDB20HDR)pbFile;
500 PPDB20ROOT pRoot;
501 unsigned iStream;
502 int rc = 0;
503
504 /*
505 * Validate the header and read the root stream.
506 */
507 if (Pdb20ValidateHeader(pHdr, cbFile))
508 return 1;
509 pRoot = Pdb20AllocAndReadRoot(pHdr);
510 if (!pRoot)
511 return 1;
512
513 /*
514 * Iterate the streams in the root and scan their content for
515 * dependencies.
516 */
517 rc = 0;
518 for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
519 {
520 uint8_t *pbStream;
521 if (pRoot->aStreams[iStream].cbStream == ~(uint32_t)0)
522 continue;
523 pbStream = (uint8_t *)Pdb20AllocAndReadStream(pHdr, pRoot, iStream, NULL);
524 if (pbStream)
525 {
526 rc = ScanStream(pbStream, pRoot->aStreams[iStream].cbStream, "/ipm/header/", sizeof("/ipm/header/") - 1);
527 free(pbStream);
528 }
529 else
530 rc = 1;
531 }
532
533 free(pRoot);
534 return rc;
535}
536
537
538/**
539 * Make an attempt at parsing a Visual C++ IDB file.
540 */
541static int ProcessIDB(FILE *pInput)
542{
543 long cbFile;
544 char *pbFile;
545 char *pbHdr = PDB_SIGNATURE_200;
546 int rc = 0;
547
548 /*
549 * Read the file into memory.
550 */
551 pbFile = (char *)ReadFileIntoMemory(pInput, &cbFile);
552 if (!pbFile)
553 return 1;
554
555 /*
556 * Figure out which parser to use.
557 */
558 if (!memcmp(pbFile, PDB_SIGNATURE_700, sizeof(PDB_SIGNATURE_700)))
559 rc = Pdb70Process(pbFile, cbFile);
560 else if (!memcmp(pbFile, PDB_SIGNATURE_200, sizeof(PDB_SIGNATURE_200)))
561 rc = Pdb20Process(pbFile, cbFile);
562 else
563 {
564 fprintf(stderr, "%s: error: Doesn't recognize the header of the Visual C++ IDB file.\n", argv0);
565 rc = 1;
566 }
567
568 free(pbFile);
569 return rc;
570}
571
572
573static void usage(const char *argv0)
574{
575 printf("syntax: %s -o <output> -t <target> [-f] [-s] <vc idb-file>\n", argv0);
576}
577
578
579int main(int argc, char *argv[])
580{
581 int i;
582
583 /* Arguments. */
584 FILE *pOutput = NULL;
585 const char *pszOutput = NULL;
586 FILE *pInput = NULL;
587 const char *pszTarget = NULL;
588 int fStubs = 0;
589 int fFixCase = 0;
590 /* Argument parsing. */
591 int fInput = 0; /* set when we've found input argument. */
592
593 argv0 = argv[0];
594
595 /*
596 * Parse arguments.
597 */
598 if (argc <= 1)
599 {
600 usage(argv[0]);
601 return 1;
602 }
603 for (i = 1; i < argc; i++)
604 {
605 if (argv[i][0] == '-')
606 {
607 switch (argv[i][1])
608 {
609 /*
610 * Output file.
611 */
612 case 'o':
613 {
614 pszOutput = &argv[i][2];
615 if (pOutput)
616 {
617 fprintf(stderr, "%s: syntax error: only one output file!\n", argv[0]);
618 return 1;
619 }
620 if (!*pszOutput)
621 {
622 if (++i >= argc)
623 {
624 fprintf(stderr, "%s: syntax error: The '-o' argument is missing the filename.\n", argv[0]);
625 return 1;
626 }
627 pszOutput = argv[i];
628 }
629 if (pszOutput[0] == '-' && !pszOutput[1])
630 pOutput = stdout;
631 else
632 pOutput = fopen(pszOutput, "w");
633 if (!pOutput)
634 {
635 fprintf(stderr, "%s: error: Failed to create output file '%s'.\n", argv[0], pszOutput);
636 return 1;
637 }
638 break;
639 }
640
641 /*
642 * Target name.
643 */
644 case 't':
645 {
646 if (pszTarget)
647 {
648 fprintf(stderr, "%s: syntax error: only one target!\n", argv[0]);
649 return 1;
650 }
651 pszTarget = &argv[i][2];
652 if (!*pszTarget)
653 {
654 if (++i >= argc)
655 {
656 fprintf(stderr, "%s: syntax error: The '-t' argument is missing the target name.\n", argv[0]);
657 return 1;
658 }
659 pszTarget = argv[i];
660 }
661 break;
662 }
663
664 /*
665 * Fix case.
666 */
667 case 'f':
668 {
669 fFixCase = 1;
670 break;
671 }
672
673 /*
674 * Generate stubs.
675 */
676 case 's':
677 {
678 fStubs = 1;
679 break;
680 }
681
682 /*
683 * Invalid argument.
684 */
685 default:
686 fprintf(stderr, "%s: syntax error: Invalid argument '%s'.\n", argv[0], argv[i]);
687 usage(argv[0]);
688 return 1;
689 }
690 }
691 else
692 {
693 pInput = fopen(argv[i], "rb");
694 if (!pInput)
695 {
696 fprintf(stderr, "%s: error: Failed to open input file '%s'.\n", argv[0], argv[i]);
697 return 1;
698 }
699 fInput = 1;
700 }
701
702 /*
703 * End of the line?
704 */
705 if (fInput)
706 {
707 if (++i < argc)
708 {
709 fprintf(stderr, "%s: syntax error: No arguments shall follow the input spec.\n", argv[0]);
710 return 1;
711 }
712 break;
713 }
714 }
715
716 /*
717 * Got all we require?
718 */
719 if (!pInput)
720 {
721 fprintf(stderr, "%s: syntax error: No input!\n", argv[0]);
722 return 1;
723 }
724 if (!pOutput)
725 {
726 fprintf(stderr, "%s: syntax error: No output!\n", argv[0]);
727 return 1;
728 }
729 if (!pszTarget)
730 {
731 fprintf(stderr, "%s: syntax error: No target!\n", argv[0]);
732 return 1;
733 }
734
735 /*
736 * Do the parsing.
737 */
738 i = ProcessIDB(pInput);
739
740 /*
741 * Write the dependecy file.
742 */
743 if (!i)
744 {
745 depOptimize(fFixCase);
746 fprintf(pOutput, "%s:", pszTarget);
747 depPrint(pOutput);
748 if (fStubs)
749 depPrintStubs(pOutput);
750 }
751
752 /*
753 * Close the output, delete output on failure.
754 */
755 if (!i && ferror(pOutput))
756 {
757 i = 1;
758 fprintf(stderr, "%s: error: Error writing to '%s'.\n", argv[0], pszOutput);
759 }
760 fclose(pOutput);
761 if (i)
762 {
763 if (unlink(pszOutput))
764 fprintf(stderr, "%s: warning: failed to remove output file '%s' on failure.\n", argv[0], pszOutput);
765 }
766
767 return i;
768}
769
Note: See TracBrowser for help on using the repository browser.