/*
 * Copyright (c) 1988-1997 Sam Leffler
 * Copyright (c) 1991-1997 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 "libport.h"
#include "tif_config.h"

#include <limits.h>
#include <math.h> /* for isfinite() */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include "tiffiop.h"

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_IO_H
#include <io.h>
#endif

#include "tiffio.h"

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif

static union
{
    TIFFHeaderClassic classic;
    TIFFHeaderBig big;
    TIFFHeaderCommon common;
} hdr;
static char *appname;
static char *curfile;
static int swabflag;
static int bigendian;
static int bigtiff;
static uint32_t maxitems = 24; /* maximum indirect data items to print */

static const char bytefmt[] = "%s%#02" PRIx8; /* BYTE */
static const char sbytefmt[] = "%s%" PRId8;   /* SBYTE */
static const char shortfmtd[] = "%s%" PRIu16; /* SHORT */
static const char shortfmth[] = "%s%#" PRIx16;
static const char sshortfmtd[] = "%s%" PRId16; /* SSHORT */
static const char longfmtd[] = "%s%" PRIu32;   /* LONG */
static const char longfmth[] = "%s%#" PRIx32;
static const char slongfmtd[] = "%s%" PRId32; /* SLONG */
static const char slongfmth[] = "%s%#" PRIx32;
static const char ifdfmt[] = "%s%#04" PRIx32;  /* IFD offset */
static const char long8fmt[] = "%s%" PRIu64;   /* LONG8 */
static const char slong8fmt[] = "%s%" PRId64;  /* SLONG8 */
static const char ifd8fmt[] = "%s%#08" PRIx64; /* IFD offset8 */
static const char rationalfmt[] = "%s%g";      /* RATIONAL */
static const char srationalfmt[] = "%s%g";     /* SRATIONAL */
static const char floatfmt[] = "%s%g";         /* FLOAT */
static const char doublefmt[] = "%s%g";        /* DOUBLE */

unsigned int hex_mode;

static void dump(int, uint64_t);

#if !HAVE_DECL_OPTARG
extern int optind;
extern char *optarg;
#endif

static void usage(void)
{
    fprintf(stderr, "\nDisplay directory information from TIFF files\n\n");
    fprintf(stderr, "usage: %s [-h] [-o offset] [-m maxitems] file.tif ...\n",
            appname);
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    int one = 1, fd;
    int multiplefiles = (argc > 1);
    int c;
    uint64_t diroff = 0;
    long v;
    hex_mode = 0;
    bigendian = (*(char *)&one == 0);

    appname = argv[0];
    while ((c = getopt(argc, argv, "m:o:h")) != -1)
    {
        switch (c)
        {
            case 'h': /* print values in hex */
                hex_mode = 1;
                break;
            case 'o':
                v = strtol(optarg, NULL, 0);
                if (v < 0)
                    usage();
                diroff = (uint64_t)v;
                break;
            case 'm':
                v = strtol(optarg, NULL, 0);
                if (v < 0)
                    usage();
                maxitems = (uint32_t)v;
                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 (EXIT_FAILURE);
        }
        if (multiplefiles)
            printf("%s:\n", argv[optind]);
        curfile = argv[optind];
        swabflag = 0;
        bigtiff = 0;
        dump(fd, diroff);
        close(fd);
    }
    return (EXIT_SUCCESS);
}

static uint64_t ReadDirectory(int, unsigned, uint64_t);
static void ReadError(const char *);
static void Error(const char *, ...);
static void Fatal(const char *, ...);

static void dump(int fd, uint64_t diroff)
{
    unsigned i, j;
    uint64_t *visited_diroff = NULL;
    unsigned int count_visited_dir = 0;

    _TIFF_lseek_f(fd, (_TIFF_off_t)0, 0);
    if (read(fd, (char *)&hdr, sizeof(TIFFHeaderCommon)) !=
        sizeof(TIFFHeaderCommon))
        ReadError("TIFF header");
    if (hdr.common.tiff_magic != TIFF_BIGENDIAN &&
        hdr.common.tiff_magic != TIFF_LITTLEENDIAN &&
#if HOST_BIGENDIAN
        /* MDI is sensitive to the host byte order, unlike TIFF */
        MDI_BIGENDIAN != hdr.common.tiff_magic
#else
        MDI_LITTLEENDIAN != hdr.common.tiff_magic
#endif
    )
    {
        Fatal("Not a TIFF or MDI file, bad magic number %u (%#x)",
              hdr.common.tiff_magic, hdr.common.tiff_magic);
    }
    if (hdr.common.tiff_magic == TIFF_BIGENDIAN ||
        hdr.common.tiff_magic == MDI_BIGENDIAN)
        swabflag = !bigendian;
    else
        swabflag = bigendian;
    if (swabflag)
        TIFFSwabShort(&hdr.common.tiff_version);
    if (hdr.common.tiff_version == 42)
    {
        if (read(fd,
                 ((char *)&hdr.classic) +
                     offsetof(TIFFHeaderClassic, tiff_diroff),
                 4) != 4)
            ReadError("TIFF header");
        if (swabflag)
            TIFFSwabLong(&hdr.classic.tiff_diroff);
        printf("Magic: %#x <%s-endian> Version: %#x <%s>\n",
               (unsigned)hdr.classic.tiff_magic,
               hdr.classic.tiff_magic == TIFF_BIGENDIAN ? "big" : "little",
               (unsigned)42, "ClassicTIFF");
        if (diroff == 0)
            diroff = hdr.classic.tiff_diroff;
    }
    else if (hdr.common.tiff_version == 43)
    {
        if (read(fd,
                 ((char *)&hdr.big) + offsetof(TIFFHeaderBig, tiff_offsetsize),
                 12) != 12)
            ReadError("TIFF header");
        if (swabflag)
        {
            TIFFSwabShort(&hdr.big.tiff_offsetsize);
            TIFFSwabShort(&hdr.big.tiff_unused);
            TIFFSwabLong8(&hdr.big.tiff_diroff);
        }
        printf("Magic: %#x <%s-endian> Version: %#x <%s>\n",
               (unsigned)hdr.big.tiff_magic,
               hdr.big.tiff_magic == TIFF_BIGENDIAN ? "big" : "little",
               (unsigned)43, "BigTIFF");
        printf("OffsetSize: %#x Unused: %#x\n",
               (unsigned)hdr.big.tiff_offsetsize,
               (unsigned)hdr.big.tiff_unused);
        if (diroff == 0)
            diroff = hdr.big.tiff_diroff;
        bigtiff = 1;
    }
    else
        Fatal("Not a TIFF file, bad version number %u (%#x)",
              hdr.common.tiff_version, hdr.common.tiff_version);
    for (i = 0; diroff != 0; i++)
    {
        for (j = 0; j < count_visited_dir; j++)
        {
            if (visited_diroff[j] == diroff)
            {
                free(visited_diroff);
                Fatal("Cycle detected in chaining of TIFF directories!");
            }
        }
        {
            size_t alloc_size;
            alloc_size = TIFFSafeMultiply(size_t, (count_visited_dir + 1),
                                          sizeof(uint64_t));
            if (alloc_size == 0)
            {
                if (visited_diroff)
                    free(visited_diroff);
                visited_diroff = 0;
            }
            else
            {
                visited_diroff =
                    (uint64_t *)realloc(visited_diroff, alloc_size);
            }
        }
        if (!visited_diroff)
            Fatal("Out of memory");
        visited_diroff[count_visited_dir] = diroff;
        count_visited_dir++;

        if (i > 0)
            putchar('\n');
        diroff = ReadDirectory(fd, i, diroff);
    }
    if (visited_diroff)
        free(visited_diroff);
}

static const int datawidth[] = {
    0, /* 00 = undefined */
    1, /* 01 = TIFF_BYTE */
    1, /* 02 = TIFF_ASCII */
    2, /* 03 = TIFF_SHORT */
    4, /* 04 = TIFF_LONG */
    8, /* 05 = TIFF_RATIONAL */
    1, /* 06 = TIFF_SBYTE */
    1, /* 07 = TIFF_UNDEFINED */
    2, /* 08 = TIFF_SSHORT */
    4, /* 09 = TIFF_SLONG */
    8, /* 10 = TIFF_SRATIONAL */
    4, /* 11 = TIFF_FLOAT */
    8, /* 12 = TIFF_DOUBLE */
    4, /* 13 = TIFF_IFD */
    0, /* 14 = undefined */
    0, /* 15 = undefined */
    8, /* 16 = TIFF_LONG8 */
    8, /* 17 = TIFF_SLONG8 */
    8, /* 18 = TIFF_IFD8 */
};
#define NWIDTHS (sizeof(datawidth) / sizeof(datawidth[0]))
static void PrintTag(FILE *, uint16_t);
static void PrintType(FILE *, uint16_t);
static void PrintData(FILE *, uint16_t, uint32_t, unsigned char *);

/*
 * Read the next TIFF directory from a file
 * and convert it to the internal format.
 * We read directories sequentially.
 */
static uint64_t ReadDirectory(int fd, unsigned int ix, uint64_t off)
{
    uint16_t dircount;
    uint32_t direntrysize;
    void *dirmem = NULL;
    uint64_t nextdiroff = 0;
    uint32_t n;
    tmsize_t nread;
    tmsize_t dirsize;
    uint8_t *dp;

    if (off == 0) /* no more directories */
        goto done;
    if (_TIFF_lseek_f(fd, (_TIFF_off_t)off, SEEK_SET) != (_TIFF_off_t)off)
    {
        Fatal("Seek error accessing TIFF directory");
        goto done;
    }
    if (!bigtiff)
    {
        if (read(fd, (char *)&dircount, sizeof(uint16_t)) != sizeof(uint16_t))
        {
            ReadError("directory count");
            goto done;
        }
        if (swabflag)
            TIFFSwabShort(&dircount);
        direntrysize = 12;
    }
    else
    {
        uint64_t dircount64 = 0;
        if (read(fd, (char *)&dircount64, sizeof(uint64_t)) != sizeof(uint64_t))
        {
            ReadError("directory count");
            goto done;
        }
        if (swabflag)
            TIFFSwabLong8(&dircount64);
        if (dircount64 > 0xFFFF)
        {
            Error("Sanity check on directory count failed");
            goto done;
        }
        dircount = (uint16_t)dircount64;
        direntrysize = 20;
    }
    dirsize =
        _TIFFMultiplySSize(NULL, dircount, direntrysize, "directory size");
    if (dirsize == 0 && dircount != 0)
    {
        Fatal("Integer overflow reading TIFF directory");
        goto done;
    }

    if (dirsize > INT_MAX)
    {
        Fatal("TIFF directory too large");
        goto done;
    }

    dirmem = _TIFFmalloc(dirsize);
    if (dirmem == NULL)
    {
        Fatal("No space for TIFF directory");
        goto done;
    }

    nread = (tmsize_t)read(fd, (char *)dirmem, (uint32_t)dirsize);
    if (nread < 0 || nread != dirsize)
    {
        if (nread <= 0)
            n = 0;
        else
            n = (uint32_t)((tmsize_t)nread / direntrysize);

        Error("Could only read %" PRIu32 " of %" PRIu16
              " entries in directory at offset %" PRIu64,
              n, dircount, off);
        dircount = (uint16_t)n;
        nextdiroff = 0;
    }
    else
    {
        if (!bigtiff)
        {
            uint32_t nextdiroff32;
            if (read(fd, (char *)&nextdiroff32, sizeof(uint32_t)) !=
                sizeof(uint32_t))
                nextdiroff32 = 0;
            if (swabflag)
                TIFFSwabLong(&nextdiroff32);
            nextdiroff = nextdiroff32;
        }
        else
        {
            if (read(fd, (char *)&nextdiroff, sizeof(uint64_t)) !=
                sizeof(uint64_t))
                nextdiroff = 0;
            if (swabflag)
                TIFFSwabLong8(&nextdiroff);
        }
    }
    printf("Directory %u: offset %" PRIu64 " (%#" PRIx64 ") next %" PRIu64
           " (%#" PRIx64 ")\n",
           ix, off, off, nextdiroff, nextdiroff);
    for (dp = (uint8_t *)dirmem, n = dircount; n > 0; n--)
    {
        uint16_t tag;
        uint16_t type;
        uint16_t typewidth;
        uint64_t count;
        uint64_t datasize;
        int datafits;
        void *datamem = NULL;
        void *datavalptr = NULL;
        uint64_t dataoffset;
        int datatruncated;
        int datasizeoverflow;

        tag = *(uint16_t *)dp;
        if (swabflag)
            TIFFSwabShort(&tag);
        dp += sizeof(uint16_t);
        type = *(uint16_t *)dp;
        dp += sizeof(uint16_t);
        if (swabflag)
            TIFFSwabShort(&type);
        PrintTag(stdout, tag);
        putchar(' ');
        PrintType(stdout, type);
        putchar(' ');
        if (!bigtiff)
        {
            uint32_t count32;
            count32 = *(uint32_t *)dp;
            if (swabflag)
                TIFFSwabLong(&count32);
            dp += sizeof(uint32_t);
            count = count32;
        }
        else
        {
            memcpy(&count, dp, sizeof(uint64_t));
            if (swabflag)
                TIFFSwabLong8(&count);
            dp += sizeof(uint64_t);
        }
        printf("%" PRIu64 "<", count);
        if (type >= NWIDTHS)
            typewidth = 0;
        else
            typewidth = (uint16_t)datawidth[type];
        datasize = TIFFSafeMultiply(uint64_t, count, typewidth);
        datasizeoverflow = (typewidth > 0 && datasize / typewidth != count);
        datafits = 1;
        datavalptr = (void *)dp;
        dataoffset = 0;
        datatruncated = 0;
        if (!bigtiff)
        {
            if (datasizeoverflow || datasize > 4)
            {
                uint32_t dataoffset32;
                datafits = 0;
                dataoffset32 = *(uint32_t *)dp;
                if (swabflag)
                    TIFFSwabLong(&dataoffset32);
                dataoffset = dataoffset32;
            }
            dp += sizeof(uint32_t);
        }
        else
        {
            if (datasizeoverflow || datasize > 8)
            {
                datafits = 0;
                memcpy(&dataoffset, dp, sizeof(uint64_t));
                if (swabflag)
                    TIFFSwabLong8(&dataoffset);
            }
            dp += sizeof(uint64_t);
        }
        if (datasizeoverflow || datasize > 0x10000)
        {
            datatruncated = 1;
            count = (uint64_t)(0x10000 / typewidth);
            datasize = TIFFSafeMultiply(uint64_t, count, typewidth);
        }
        if (count > maxitems)
        {
            datatruncated = 1;
            count = maxitems;
            datasize = TIFFSafeMultiply(uint64_t, count, typewidth);
        }

        /* Values within IFD-buffer 'dirmem' are either 2 byte-aligned for
         * ClassicTIFF, or 4-byte aligned for BigTIFF.
         * Thus, to be on the safe side for "unaligned memory access", the data
         * values need to be freshly aligned in an extra allocated buffer for
         * the values.
         * BTW: malloc() gets memory aligned for every data type.
         */
        datamem = _TIFFmalloc((tmsize_t)datasize);
        if (datamem)
        {
            if (!datafits)
            {
                if (_TIFF_lseek_f(fd, (_TIFF_off_t)dataoffset, 0) !=
                    (_TIFF_off_t)dataoffset)
                {
                    Error("Seek error accessing tag %u value", tag);
                    _TIFFfree(datamem);
                    datamem = NULL;
                }
                else if (read(fd, datamem, (TIFFIOSize_t)datasize) !=
                         (tmsize_t)datasize)
                {
                    Error("Read error accessing tag %u value", tag);
                    _TIFFfree(datamem);
                    datamem = NULL;
                }
            }
            else
            {
                /* Align values from IFD-buffer */
                memcpy(datamem, datavalptr, (size_t)datasize);
            }
        }
        else
            Error("No space for data for tag %u", tag);

        if (datamem)
        {
            if (swabflag)
            {
                switch (type)
                {
                    case TIFF_BYTE:
                    case TIFF_ASCII:
                    case TIFF_SBYTE:
                    case TIFF_UNDEFINED:
                        break;
                    case TIFF_SHORT:
                    case TIFF_SSHORT:
                        TIFFSwabArrayOfShort((uint16_t *)datamem,
                                             (tmsize_t)count);
                        break;
                    case TIFF_LONG:
                    case TIFF_SLONG:
                    case TIFF_FLOAT:
                    case TIFF_IFD:
                        TIFFSwabArrayOfLong((uint32_t *)datamem,
                                            (tmsize_t)count);
                        break;
                    case TIFF_RATIONAL:
                    case TIFF_SRATIONAL:
                    {
                        tmsize_t count_s = _TIFFCastUInt64ToSSize(
                            NULL, count, "rational word count");
                        tmsize_t word_count = _TIFFMultiplySSize(
                            NULL, count_s, 2, "rational word count");
                        if ((count_s == 0 && count != 0) ||
                            (word_count == 0 && count_s != 0))
                        {
                            Error("Integer overflow swabbing tag %u", tag);
                            break;
                        }
                        TIFFSwabArrayOfLong((uint32_t *)datamem, word_count);
                        break;
                    }
                    case TIFF_DOUBLE:
                    case TIFF_LONG8:
                    case TIFF_SLONG8:
                    case TIFF_IFD8:
                        TIFFSwabArrayOfLong8((uint64_t *)datamem,
                                             (tmsize_t)count);
                        break;
                    default:
                        break;
                }
            }
            /* Silence Coverity Scan warning about tainted_data: Passing tainted
             * expression *datamem to PrintData, which uses it as a divisor or
             * modulus. False positive because division by zero is checked in
             * PrintData(). */
            /* coverity[tainted_data:SUPPRESS] */
            PrintData(stdout, type, (uint32_t)count, (unsigned char *)datamem);
            if (datatruncated)
                printf(" ...");

            _TIFFfree(datamem);
            datamem = NULL;
        }
        printf(">\n");
    }
done:
    if (dirmem)
        _TIFFfree((char *)dirmem);
    return (nextdiroff);
}

static const struct tagname
{
    uint16_t tag;
    const 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_NUMBEROFINKS, "NumberOfInks"},
    {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_JPEGTABLES, "JPEGTables"},
    {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_ICCPROFILE, "ICC Profile"},
    {TIFFTAG_JBIGOPTIONS, "JBIG Options"},
    {TIFFTAG_STONITS, "StoNits"},
    {TIFFTAG_GDAL_METADATA, "GDALMetadata"},
    {TIFFTAG_GDAL_NODATA, "GDALNoDataValue"},
};
#define NTAGS (sizeof(tagnames) / sizeof(tagnames[0]))

static void PrintTag(FILE *fd, uint16_t tag)
{
    const 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_t type)
{
    static const char *typenames[] = {
        "0",         "BYTE",  "ASCII",     "SHORT",  "LONG",
        "RATIONAL",  "SBYTE", "UNDEFINED", "SSHORT", "SLONG",
        "SRATIONAL", "FLOAT", "DOUBLE",    "IFD",    "14",
        "15",        "LONG8", "SLONG8",    "IFD8"};
#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

#include <ctype.h>

static void PrintASCII(FILE *fd, uint32_t 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 if (*cp)
            fprintf(fd, "\\%03o", *cp);
        else
            fprintf(fd, "\\0");
    }
}

static void PrintData(FILE *fd, uint16_t type, uint32_t count,
                      unsigned char *data)
{
    const 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:
        {
            uint16_t *wp = (uint16_t *)data;
            while (count-- > 0)
                fprintf(fd, hex_mode ? shortfmth : shortfmtd, sep, *wp++),
                    sep = " ";
            break;
        }
        case TIFF_SSHORT:
        {
            int16_t *wp = (int16_t *)data;
            while (count-- > 0)
            {
                int16_t val = *wp++;
                if (hex_mode)
                    fprintf(fd, "%s%#x", sep, (unsigned int)val);
                else
                    fprintf(fd, sshortfmtd, sep, val);
                sep = " ";
            }
            break;
        }
        case TIFF_LONG:
        {
            uint32_t *lp = (uint32_t *)data;
            while (count-- > 0)
            {
                fprintf(fd, hex_mode ? longfmth : longfmtd, sep, *lp++);
                sep = " ";
            }
            break;
        }
        case TIFF_SLONG:
        {
            int32_t *lp = (int32_t *)data;
            while (count-- > 0)
            {
                int32_t val = *lp++;
                if (hex_mode)
                    fprintf(fd, slongfmth, sep, (uint32_t)val);
                else
                    fprintf(fd, slongfmtd, sep, val);
                sep = " ";
            }
            break;
        }
        case TIFF_LONG8:
        {
            uint64_t *llp = (uint64_t *)data;
            while (count-- > 0)
            {
                uint64_t val;
                memcpy(&val, llp, sizeof(uint64_t));
                llp++;
                fprintf(fd, long8fmt, sep, val);
                sep = " ";
            }
            break;
        }
        case TIFF_SLONG8:
        {
            int64_t *llp = (int64_t *)data;
            while (count-- > 0)
            {
                int64_t val;
                memcpy(&val, llp, sizeof(int64_t));
                llp++;
                fprintf(fd, slong8fmt, sep, val);
                sep = " ";
            }
            break;
        }
        case TIFF_RATIONAL:
        {
            uint32_t *lp = (uint32_t *)data;
            while (count-- > 0)
            {
                if (lp[1] == 0)
                {
                    if (lp[0] == 0)
                        fprintf(fd, "%sNan (%" PRIu32 "/%" PRIu32 ")", sep,
                                lp[0], lp[1]);
                    else
                        fprintf(fd, "%sInf (%" PRIu32 "/%" PRIu32 ")", sep,
                                lp[0], lp[1]);
                }
                else
                    fprintf(fd, rationalfmt, sep,
                            (double)lp[0] / (double)lp[1]);
                sep = " ";
                lp += 2;
            }
            break;
        }
        case TIFF_SRATIONAL:
        {
            int32_t *lp = (int32_t *)data;
            while (count-- > 0)
            {
                if (lp[1] == 0)
                    fprintf(fd, "%sNan (%" PRId32 "/%" PRId32 ")", sep, lp[0],
                            lp[1]);
                else
                    fprintf(fd, srationalfmt, sep,
                            (double)lp[0] / (double)lp[1]);
                sep = " ";
                lp += 2;
            }
            break;
        }
        case TIFF_FLOAT:
        {
            float *fp = (float *)data;
            while (count-- > 0)
                fprintf(fd, floatfmt, sep, (double)*fp++), sep = " ";
            break;
        }
        case TIFF_DOUBLE:
        {
            double *dp = (double *)data;
            while (count-- > 0)
                fprintf(fd, doublefmt, sep, *dp++), sep = " ";
            break;
        }
        case TIFF_IFD:
        {
            uint32_t *lp = (uint32_t *)data;
            while (count-- > 0)
            {
                fprintf(fd, ifdfmt, sep, *lp++);
                sep = " ";
            }
            break;
        }
        case TIFF_IFD8:
        {
            uint64_t *llp = (uint64_t *)data;
            while (count-- > 0)
            {
                fprintf(fd, ifd8fmt, sep, *llp++);
                sep = " ";
                sep = " ";
            }
            break;
        }
        default:
            break;
    }
}

static void ReadError(const char *what)
{
    Fatal("Error while reading %s", what);
}

#include <stdarg.h>

static void vError(FILE *fd, const char *fmt, va_list ap)
{
    fprintf(fd, "%s: ", curfile);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#elif defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
#endif
    vfprintf(fd, fmt, ap);
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#elif defined(__clang__)
#pragma clang diagnostic pop
#endif
    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(EXIT_FAILURE);
}
