/* $Header: /usr/people/sam/tiff/tools/RCS/tiffdump.c,v 1.49 1996/03/29 16:38:33 sam Exp $ */ /* * Copyright (c) 1988-1996 Sam Leffler * Copyright (c) 1991-1996 Silicon Graphics, Inc. * * Permission to use, copy, modify, distribute, and sell this software and * its documentation for any purpose is hereby granted without fee, provided * that (i) the above copyright notices and this permission notice appear in * all copies of the software and related documentation, and (ii) the names of * Sam Leffler and Silicon Graphics may not be used in any advertising or * publicity relating to the software without the specific, prior written * permission of Sam Leffler and Silicon Graphics. * * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. * * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE * OF THIS SOFTWARE. */ #include #include #include #if defined(VMS) #include #include #elif defined(_WINDOWS) #include #define off_t toff_t #include "tiffio.h" #include #elif defined(applec) #define open _open_ /* to avoid conflicts */ #include #undef open int open(const char*, int, int); typedef unsigned int off_t; #else /* !VMS && !_WINDOWS && !applec */ #ifdef unix #include #endif #include #include #endif #if defined(MSDOS) #include #endif #ifndef O_BINARY #define O_BINARY 0 #endif #include "tiffio.h" char* appname; char* curfile; int swabflag; int bigendian; int typeshift[13]; /* data type shift counts */ long typemask[13]; /* data type masks */ char* bytefmt = "%s%#02x"; /* BYTE */ char* sbytefmt = "%s%d"; /* SBYTE */ char* shortfmt = "%s%u"; /* SHORT */ char* sshortfmt = "%s%d"; /* SSHORT */ char* longfmt = "%s%lu"; /* LONG */ char* slongfmt = "%s%ld"; /* SLONG */ char* rationalfmt = "%s%g"; /* RATIONAL */ char* srationalfmt = "%s%g"; /* SRATIONAL */ char* floatfmt = "%s%g"; /* FLOAT */ char* doublefmt = "%s%g"; /* DOUBLE */ static void dump(int, uint32); extern int optind; extern char* optarg; void usage() { fprintf(stderr, "usage: %s [-h] [-o offset] file.tif ...\n", appname); exit(-1); } int main(int argc, char* argv[]) { int one = 1, fd; int multiplefiles = (argc > 1); int c; uint32 diroff = (uint32) 0; bigendian = (*(char *)&one == 0); appname = argv[0]; while ((c = getopt(argc, argv, "o:h")) != -1) { switch (c) { case 'h': /* print values in hex */ shortfmt = "%s%#x"; sshortfmt = "%s%#x"; longfmt = "%s%#lx"; slongfmt = "%s%#lx"; break; case 'o': diroff = (uint32) strtoul(optarg, NULL, 0); break; default: usage(); } } if (optind >= argc) usage(); for (; optind < argc; optind++) { fd = open(argv[optind], O_RDONLY|O_BINARY, 0); if (fd < 0) { perror(argv[0]); return (-1); } if (multiplefiles) printf("%s:\n", argv[optind]); curfile = argv[optind]; swabflag = 0; dump(fd, diroff); close(fd); } return (0); } static TIFFHeader hdr; #define ord(e) ((int)e) /* * Initialize shift & mask tables and byte * swapping state according to the file * byte order. */ static void InitByteOrder(int magic) { typemask[0] = 0; typemask[ord(TIFF_BYTE)] = 0xff; typemask[ord(TIFF_SBYTE)] = 0xff; typemask[ord(TIFF_UNDEFINED)] = 0xff; typemask[ord(TIFF_SHORT)] = 0xffff; typemask[ord(TIFF_SSHORT)] = 0xffff; typemask[ord(TIFF_LONG)] = 0xffffffff; typemask[ord(TIFF_SLONG)] = 0xffffffff; typemask[ord(TIFF_RATIONAL)] = 0xffffffff; typemask[ord(TIFF_SRATIONAL)] = 0xffffffff; typemask[ord(TIFF_FLOAT)] = 0xffffffff; typemask[ord(TIFF_DOUBLE)] = 0xffffffff; typeshift[0] = 0; typeshift[ord(TIFF_LONG)] = 0; typeshift[ord(TIFF_SLONG)] = 0; typeshift[ord(TIFF_RATIONAL)] = 0; typeshift[ord(TIFF_SRATIONAL)] = 0; typeshift[ord(TIFF_FLOAT)] = 0; typeshift[ord(TIFF_DOUBLE)] = 0; if (magic == TIFF_BIGENDIAN) { typeshift[ord(TIFF_BYTE)] = 24; typeshift[ord(TIFF_SBYTE)] = 24; typeshift[ord(TIFF_SHORT)] = 16; typeshift[ord(TIFF_SSHORT)] = 16; swabflag = !bigendian; } else { typeshift[ord(TIFF_BYTE)] = 0; typeshift[ord(TIFF_SBYTE)] = 0; typeshift[ord(TIFF_SHORT)] = 0; typeshift[ord(TIFF_SSHORT)] = 0; swabflag = bigendian; } } static uint32 ReadDirectory(int, unsigned, uint32); static void ReadError(char*); static void Error(const char*, ...); static void Fatal(const char*, ...); static void dump(int fd, uint32 diroff) { unsigned i; lseek(fd, (off_t) 0, 0); if (read(fd, (char*) &hdr, sizeof (hdr)) != sizeof (hdr)) ReadError("TIFF header"); /* * Setup the byte order handling. */ if (hdr.tiff_magic != TIFF_BIGENDIAN && hdr.tiff_magic != TIFF_LITTLEENDIAN) Fatal("Not a TIFF file, bad magic number %u (%#x)", hdr.tiff_magic, hdr.tiff_magic); InitByteOrder(hdr.tiff_magic); /* * Swap header if required. */ if (swabflag) { TIFFSwabShort(&hdr.tiff_version); TIFFSwabLong(&hdr.tiff_diroff); } /* * Now check version (if needed, it's been byte-swapped). * Note that this isn't actually a version number, it's a * magic number that doesn't change (stupid). */ if (hdr.tiff_version != TIFF_VERSION) Fatal("Not a TIFF file, bad version number %u (%#x)", hdr.tiff_version, hdr.tiff_version); printf("Magic: %#x <%s-endian> Version: %#x\n", hdr.tiff_magic, hdr.tiff_magic == TIFF_BIGENDIAN ? "big" : "little", hdr.tiff_version); if (diroff == 0) diroff = hdr.tiff_diroff; for (i = 0; diroff != 0; i++) { if (i > 0) putchar('\n'); diroff = ReadDirectory(fd, i, diroff); } } static int datawidth[] = { 0, /* nothing */ 1, /* TIFF_BYTE */ 1, /* TIFF_ASCII */ 2, /* TIFF_SHORT */ 4, /* TIFF_LONG */ 8, /* TIFF_RATIONAL */ 1, /* TIFF_SBYTE */ 1, /* TIFF_UNDEFINED */ 2, /* TIFF_SSHORT */ 4, /* TIFF_SLONG */ 8, /* TIFF_SRATIONAL */ 4, /* TIFF_FLOAT */ 8, /* TIFF_DOUBLE */ }; #define NWIDTHS (sizeof (datawidth) / sizeof (datawidth[0])) static int TIFFFetchData(int, TIFFDirEntry*, void*); static void PrintTag(FILE*, uint16); static void PrintType(FILE*, uint16); static void PrintData(FILE*, uint16, uint32, unsigned char*); static void PrintByte(FILE*, const char*, TIFFDirEntry*); static void PrintShort(FILE*, const char*, TIFFDirEntry*); static void PrintLong(FILE*, const char*, TIFFDirEntry*); /* * Read the next TIFF directory from a file * and convert it to the internal format. * We read directories sequentially. */ static uint32 ReadDirectory(int fd, unsigned ix, uint32 off) { register TIFFDirEntry *dp; register int n; TIFFDirEntry *dir = 0; uint16 dircount; int space; uint32 nextdiroff = 0; if (off == 0) /* no more directories */ goto done; if (lseek(fd, (off_t) off, 0) != off) { Fatal("Seek error accessing TIFF directory"); goto done; } if (read(fd, (char*) &dircount, sizeof (uint16)) != sizeof (uint16)) { ReadError("directory count"); goto done; } if (swabflag) TIFFSwabShort(&dircount); dir = (TIFFDirEntry *)_TIFFmalloc(dircount * sizeof (TIFFDirEntry)); if (dir == NULL) { Fatal("No space for TIFF directory"); goto done; } n = read(fd, (char*) dir, dircount*sizeof (*dp)); if (n != dircount*sizeof (*dp)) { n /= sizeof (*dp); Error( "Could only read %u of %u entries in directory at offset %#lx", n, dircount, (unsigned long) off); dircount = n; } if (read(fd, (char*) &nextdiroff, sizeof (uint32)) != sizeof (uint32)) nextdiroff = 0; if (swabflag) TIFFSwabLong(&nextdiroff); printf("Directory %u: offset %lu (%#lx) next %lu (%#lx)\n", ix, (unsigned long) off, (unsigned long) off, (unsigned long) nextdiroff, (unsigned long) nextdiroff); for (dp = dir, n = dircount; n > 0; n--, dp++) { if (swabflag) { TIFFSwabArrayOfShort(&dp->tdir_tag, 2); TIFFSwabArrayOfLong(&dp->tdir_count, 2); } PrintTag(stdout, dp->tdir_tag); putchar(' '); PrintType(stdout, dp->tdir_type); putchar(' '); printf("%lu<", (unsigned long) dp->tdir_count); if (dp->tdir_type >= NWIDTHS) { printf(">\n"); continue; } space = dp->tdir_count * datawidth[dp->tdir_type]; if (space <= 4) { switch (dp->tdir_type) { case TIFF_UNDEFINED: case TIFF_ASCII: { unsigned char data[4]; _TIFFmemcpy(data, &dp->tdir_offset, 4); if (swabflag) TIFFSwabLong((uint32*) data); PrintData(stdout, dp->tdir_type, dp->tdir_count, data); break; } case TIFF_BYTE: PrintByte(stdout, bytefmt, dp); break; case TIFF_SBYTE: PrintByte(stdout, sbytefmt, dp); break; case TIFF_SHORT: PrintShort(stdout, shortfmt, dp); break; case TIFF_SSHORT: PrintShort(stdout, sshortfmt, dp); break; case TIFF_LONG: PrintLong(stdout, longfmt, dp); break; case TIFF_SLONG: PrintLong(stdout, slongfmt, dp); break; } } else { unsigned char *data = (unsigned char *)_TIFFmalloc(space); if (data) { if (TIFFFetchData(fd, dp, data)) PrintData(stdout, dp->tdir_type, dp->tdir_count, data); _TIFFfree(data); } else Error("No space for data for tag %u", dp->tdir_tag); } printf(">\n"); } done: if (dir) _TIFFfree((char *)dir); return (nextdiroff); } static struct tagname { uint16 tag; char* name; } tagnames[] = { { TIFFTAG_SUBFILETYPE, "SubFileType" }, { TIFFTAG_OSUBFILETYPE, "OldSubFileType" }, { TIFFTAG_IMAGEWIDTH, "ImageWidth" }, { TIFFTAG_IMAGELENGTH, "ImageLength" }, { TIFFTAG_BITSPERSAMPLE, "BitsPerSample" }, { TIFFTAG_COMPRESSION, "Compression" }, { TIFFTAG_PHOTOMETRIC, "Photometric" }, { TIFFTAG_THRESHHOLDING, "Threshholding" }, { TIFFTAG_CELLWIDTH, "CellWidth" }, { TIFFTAG_CELLLENGTH, "CellLength" }, { TIFFTAG_FILLORDER, "FillOrder" }, { TIFFTAG_DOCUMENTNAME, "DocumentName" }, { TIFFTAG_IMAGEDESCRIPTION, "ImageDescription" }, { TIFFTAG_MAKE, "Make" }, { TIFFTAG_MODEL, "Model" }, { TIFFTAG_STRIPOFFSETS, "StripOffsets" }, { TIFFTAG_ORIENTATION, "Orientation" }, { TIFFTAG_SAMPLESPERPIXEL, "SamplesPerPixel" }, { TIFFTAG_ROWSPERSTRIP, "RowsPerStrip" }, { TIFFTAG_STRIPBYTECOUNTS, "StripByteCounts" }, { TIFFTAG_MINSAMPLEVALUE, "MinSampleValue" }, { TIFFTAG_MAXSAMPLEVALUE, "MaxSampleValue" }, { TIFFTAG_XRESOLUTION, "XResolution" }, { TIFFTAG_YRESOLUTION, "YResolution" }, { TIFFTAG_PLANARCONFIG, "PlanarConfig" }, { TIFFTAG_PAGENAME, "PageName" }, { TIFFTAG_XPOSITION, "XPosition" }, { TIFFTAG_YPOSITION, "YPosition" }, { TIFFTAG_FREEOFFSETS, "FreeOffsets" }, { TIFFTAG_FREEBYTECOUNTS, "FreeByteCounts" }, { TIFFTAG_GRAYRESPONSEUNIT, "GrayResponseUnit" }, { TIFFTAG_GRAYRESPONSECURVE,"GrayResponseCurve" }, { TIFFTAG_GROUP3OPTIONS, "Group3Options" }, { TIFFTAG_GROUP4OPTIONS, "Group4Options" }, { TIFFTAG_RESOLUTIONUNIT, "ResolutionUnit" }, { TIFFTAG_PAGENUMBER, "PageNumber" }, { TIFFTAG_COLORRESPONSEUNIT,"ColorResponseUnit" }, { TIFFTAG_TRANSFERFUNCTION, "TransferFunction" }, { TIFFTAG_SOFTWARE, "Software" }, { TIFFTAG_DATETIME, "DateTime" }, { TIFFTAG_ARTIST, "Artist" }, { TIFFTAG_HOSTCOMPUTER, "HostComputer" }, { TIFFTAG_PREDICTOR, "Predictor" }, { TIFFTAG_WHITEPOINT, "Whitepoint" }, { TIFFTAG_PRIMARYCHROMATICITIES,"PrimaryChromaticities" }, { TIFFTAG_COLORMAP, "Colormap" }, { TIFFTAG_HALFTONEHINTS, "HalftoneHints" }, { TIFFTAG_TILEWIDTH, "TileWidth" }, { TIFFTAG_TILELENGTH, "TileLength" }, { TIFFTAG_TILEOFFSETS, "TileOffsets" }, { TIFFTAG_TILEBYTECOUNTS, "TileByteCounts" }, { TIFFTAG_BADFAXLINES, "BadFaxLines" }, { TIFFTAG_CLEANFAXDATA, "CleanFaxData" }, { TIFFTAG_CONSECUTIVEBADFAXLINES, "ConsecutiveBadFaxLines" }, { TIFFTAG_SUBIFD, "SubIFD" }, { TIFFTAG_INKSET, "InkSet" }, { TIFFTAG_INKNAMES, "InkNames" }, { TIFFTAG_DOTRANGE, "DotRange" }, { TIFFTAG_TARGETPRINTER, "TargetPrinter" }, { TIFFTAG_EXTRASAMPLES, "ExtraSamples" }, { TIFFTAG_SAMPLEFORMAT, "SampleFormat" }, { TIFFTAG_SMINSAMPLEVALUE, "SMinSampleValue" }, { TIFFTAG_SMAXSAMPLEVALUE, "SMaxSampleValue" }, { TIFFTAG_JPEGPROC, "JPEGProcessingMode" }, { TIFFTAG_JPEGIFOFFSET, "JPEGInterchangeFormat" }, { TIFFTAG_JPEGIFBYTECOUNT, "JPEGInterchangeFormatLength" }, { TIFFTAG_JPEGRESTARTINTERVAL,"JPEGRestartInterval" }, { TIFFTAG_JPEGLOSSLESSPREDICTORS,"JPEGLosslessPredictors" }, { TIFFTAG_JPEGPOINTTRANSFORM,"JPEGPointTransform" }, { TIFFTAG_JPEGQTABLES, "JPEGQTables" }, { TIFFTAG_JPEGDCTABLES, "JPEGDCTables" }, { TIFFTAG_JPEGACTABLES, "JPEGACTables" }, { TIFFTAG_YCBCRCOEFFICIENTS,"YCbCrCoefficients" }, { TIFFTAG_YCBCRSUBSAMPLING, "YCbCrSubsampling" }, { TIFFTAG_YCBCRPOSITIONING, "YCbCrPositioning" }, { TIFFTAG_REFERENCEBLACKWHITE, "ReferenceBlackWhite" }, { TIFFTAG_REFPTS, "IgReferencePoints (Island Graphics)" }, { TIFFTAG_REGIONTACKPOINT, "IgRegionTackPoint (Island Graphics)" }, { TIFFTAG_REGIONWARPCORNERS,"IgRegionWarpCorners (Island Graphics)" }, { TIFFTAG_REGIONAFFINE, "IgRegionAffine (Island Graphics)" }, { TIFFTAG_MATTEING, "OBSOLETE Matteing (Silicon Graphics)" }, { TIFFTAG_DATATYPE, "OBSOLETE DataType (Silicon Graphics)" }, { TIFFTAG_IMAGEDEPTH, "ImageDepth (Silicon Graphics)" }, { TIFFTAG_TILEDEPTH, "TileDepth (Silicon Graphics)" }, { 32768, "OLD BOGUS Matteing tag" }, { TIFFTAG_COPYRIGHT, "Copyright" }, { TIFFTAG_JBIGOPTIONS, "JBIG Options" }, }; #define NTAGS (sizeof (tagnames) / sizeof (tagnames[0])) static void PrintTag(FILE* fd, uint16 tag) { register struct tagname *tp; for (tp = tagnames; tp < &tagnames[NTAGS]; tp++) if (tp->tag == tag) { fprintf(fd, "%s (%u)", tp->name, tag); return; } fprintf(fd, "%u (%#x)", tag, tag); } static void PrintType(FILE* fd, uint16 type) { static char *typenames[] = { "0", "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE" }; #define NTYPES (sizeof (typenames) / sizeof (typenames[0])) if (type < NTYPES) fprintf(fd, "%s (%u)", typenames[type], type); else fprintf(fd, "%u (%#x)", type, type); } #undef NTYPES static void PrintByte(FILE* fd, const char* fmt, TIFFDirEntry* dp) { char* sep = ""; if (hdr.tiff_magic != TIFF_LITTLEENDIAN) { switch ((int)dp->tdir_count) { case 4: fprintf(fd, fmt, sep, dp->tdir_offset&0xff); sep = " "; case 3: fprintf(fd, fmt, sep, (dp->tdir_offset>>8)&0xff); sep = " "; case 2: fprintf(fd, fmt, sep, (dp->tdir_offset>>16)&0xff); sep = " "; case 1: fprintf(fd, fmt, sep, dp->tdir_offset>>24); } } else { switch ((int)dp->tdir_count) { case 4: fprintf(fd, fmt, sep, dp->tdir_offset>>24); sep = " "; case 3: fprintf(fd, fmt, sep, (dp->tdir_offset>>16)&0xff); sep = " "; case 2: fprintf(fd, fmt, sep, (dp->tdir_offset>>8)&0xff); sep = " "; case 1: fprintf(fd, fmt, sep, dp->tdir_offset&0xff); } } } static void PrintShort(FILE* fd, const char* fmt, TIFFDirEntry* dp) { char *sep = ""; if (hdr.tiff_magic != TIFF_LITTLEENDIAN) { switch (dp->tdir_count) { case 2: fprintf(fd, fmt, sep, dp->tdir_offset&0xffff); sep = " "; case 1: fprintf(fd, fmt, sep, dp->tdir_offset>>16); } } else { switch (dp->tdir_count) { case 2: fprintf(fd, fmt, sep, dp->tdir_offset>>16); sep = " "; case 1: fprintf(fd, fmt, sep, dp->tdir_offset&0xffff); } } } static void PrintLong(FILE* fd, const char* fmt, TIFFDirEntry* dp) { fprintf(fd, fmt, "", (long) dp->tdir_offset); } #include static void PrintASCII(FILE* fd, uint32 cc, const unsigned char* cp) { for (; cc > 0; cc--, cp++) { const char* tp; if (isprint(*cp)) { fputc(*cp, fd); continue; } for (tp = "\tt\bb\rr\nn\vv"; *tp; tp++) if (*tp++ == *cp) break; if (*tp) fprintf(fd, "\\%c", *tp); else fprintf(fd, "\\%03o", *cp); } } static void PrintData(FILE* fd, uint16 type, uint32 count, unsigned char* data) { char* sep = ""; switch (type) { case TIFF_BYTE: while (count-- > 0) fprintf(fd, bytefmt, sep, *data++), sep = " "; break; case TIFF_SBYTE: while (count-- > 0) fprintf(fd, sbytefmt, sep, *(char *)data++), sep = " "; break; case TIFF_UNDEFINED: while (count-- > 0) fprintf(fd, bytefmt, sep, *data++), sep = " "; break; case TIFF_ASCII: PrintASCII(fd, count, data); break; case TIFF_SHORT: { register uint16 *wp = (uint16*)data; while (count-- > 0) fprintf(fd, shortfmt, sep, *wp++), sep = " "; break; } case TIFF_SSHORT: { register int16 *wp = (int16*)data; while (count-- > 0) fprintf(fd, sshortfmt, sep, *wp++), sep = " "; break; } case TIFF_LONG: { register uint32 *lp = (uint32*)data; while (count-- > 0) { fprintf(fd, longfmt, sep, (unsigned long) *lp++); sep = " "; } break; } case TIFF_SLONG: { register int32 *lp = (int32*)data; while (count-- > 0) fprintf(fd, slongfmt, sep, (long) *lp++), sep = " "; break; } case TIFF_RATIONAL: { register uint32 *lp = (uint32*)data; while (count-- > 0) { if (lp[1] == 0) fprintf(fd, "%sNan (%lu/%lu)", sep, (unsigned long) lp[0], (unsigned long) lp[1]); else fprintf(fd, rationalfmt, sep, (double)lp[0] / (double)lp[1]); sep = " "; lp += 2; } break; } case TIFF_SRATIONAL: { register int32 *lp = (int32*)data; while (count-- > 0) { if (lp[1] == 0) fprintf(fd, "%sNan (%ld/%ld)", sep, (long) lp[0], (long) lp[1]); else fprintf(fd, srationalfmt, sep, (double)lp[0] / (double)lp[1]); sep = " "; lp += 2; } break; } case TIFF_FLOAT: { register float *fp = (float *)data; while (count-- > 0) fprintf(fd, floatfmt, sep, *fp++), sep = " "; break; } case TIFF_DOUBLE: { register double *dp = (double *)data; while (count-- > 0) fprintf(fd, doublefmt, sep, *dp++), sep = " "; break; } } } /* * Fetch a contiguous directory item. */ static int TIFFFetchData(int fd, TIFFDirEntry* dir, void* cp) { int cc, w; w = (dir->tdir_type < NWIDTHS ? datawidth[dir->tdir_type] : 0); cc = dir->tdir_count * w; if (lseek(fd, (off_t) dir->tdir_offset, 0) == dir->tdir_offset && read(fd, cp, cc) == cc) { if (swabflag) { switch (dir->tdir_type) { case TIFF_SHORT: case TIFF_SSHORT: TIFFSwabArrayOfShort((uint16*) cp, dir->tdir_count); break; case TIFF_LONG: case TIFF_SLONG: case TIFF_FLOAT: TIFFSwabArrayOfLong((uint32*) cp, dir->tdir_count); break; case TIFF_RATIONAL: TIFFSwabArrayOfLong((uint32*) cp, 2*dir->tdir_count); break; case TIFF_DOUBLE: TIFFSwabArrayOfDouble((double*) cp, dir->tdir_count); break; } } return (cc); } Error("Error while reading data for tag %u", dir->tdir_tag); return (0); } static void ReadError(char* what) { Fatal("Error while reading %s", what); } #include static void vError(FILE* fd, const char* fmt, va_list ap) { fprintf(fd, "%s: ", curfile); vfprintf(fd, fmt, ap); fprintf(fd, ".\n"); } static void Error(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vError(stderr, fmt, ap); va_end(ap); } static void Fatal(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vError(stderr, fmt, ap); va_end(ap); exit(-1); }