Home | History | Annotate | Download | only in libtests
      1 /* timepng.c
      2  *
      3  * Copyright (c) 2013,2016 John Cunningham Bowler
      4  *
      5  * Last changed in libpng 1.6.22 [May 26, 2016]
      6  *
      7  * This code is released under the libpng license.
      8  * For conditions of distribution and use, see the disclaimer
      9  * and license in png.h
     10  *
     11  * Load an arbitrary number of PNG files (from the command line, or, if there
     12  * are no arguments on the command line, from stdin) then run a time test by
     13  * reading each file by row or by image (possibly with transforms in the latter
     14  * case).  The only output is a time as a floating point number of seconds with
     15  * 9 decimal digits.
     16  */
     17 #define _POSIX_C_SOURCE 199309L /* for clock_gettime */
     18 
     19 #include <stdlib.h>
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <errno.h>
     23 #include <limits.h>
     24 
     25 #include <time.h>
     26 
     27 #if defined(HAVE_CONFIG_H) && !defined(PNG_NO_CONFIG_H)
     28 #  include <config.h>
     29 #endif
     30 
     31 /* Define the following to use this test against your installed libpng, rather
     32  * than the one being built here:
     33  */
     34 #ifdef PNG_FREESTANDING_TESTS
     35 #  include <png.h>
     36 #else
     37 #  include "../../png.h"
     38 #endif
     39 
     40 /* The following is to support direct compilation of this file as C++ */
     41 #ifdef __cplusplus
     42 #  define voidcast(type, value) static_cast<type>(value)
     43 #else
     44 #  define voidcast(type, value) (value)
     45 #endif /* __cplusplus */
     46 
     47 /* 'CLOCK_PROCESS_CPUTIME_ID' is one of the clock timers for clock_gettime.  It
     48  * need not be supported even when clock_gettime is available.  It returns the
     49  * 'CPU' time the process has consumed.  'CPU' time is assumed to include time
     50  * when the CPU is actually blocked by a pending cache fill but not time
     51  * waiting for page faults.  The attempt is to get a measure of the actual time
     52  * the implementation takes to read a PNG ignoring the potentially very large IO
     53  * overhead.
     54  */
     55 #if defined (CLOCK_PROCESS_CPUTIME_ID) && defined(PNG_STDIO_SUPPORTED) &&\
     56     defined(PNG_EASY_ACCESS_SUPPORTED) &&\
     57     (PNG_LIBPNG_VER >= 10700 ? defined(PNG_READ_PNG_SUPPORTED) :\
     58      defined (PNG_SEQUENTIAL_READ_SUPPORTED) &&\
     59      defined(PNG_INFO_IMAGE_SUPPORTED))
     60 
     61 typedef struct
     62 {
     63    FILE *input;
     64    FILE *output;
     65 }  io_data;
     66 
     67 static PNG_CALLBACK(void, read_and_copy,
     68       (png_structp png_ptr, png_bytep buffer, png_size_t cb))
     69 {
     70    io_data *io = (io_data*)png_get_io_ptr(png_ptr);
     71 
     72    if (fread(buffer, cb, 1, io->input) != 1)
     73       png_error(png_ptr, strerror(errno));
     74 
     75    if (fwrite(buffer, cb, 1, io->output) != 1)
     76    {
     77       perror("temporary file");
     78       fprintf(stderr, "temporary file PNG write failed\n");
     79       exit(1);
     80    }
     81 }
     82 
     83 static void read_by_row(png_structp png_ptr, png_infop info_ptr,
     84       FILE *write_ptr, FILE *read_ptr)
     85 {
     86    /* These don't get freed on error, this is fine; the program immediately
     87     * exits.
     88     */
     89    png_bytep row = NULL, display = NULL;
     90    io_data io_copy;
     91 
     92    if (write_ptr != NULL)
     93    {
     94       /* Set up for a copy to the temporary file: */
     95       io_copy.input = read_ptr;
     96       io_copy.output = write_ptr;
     97       png_set_read_fn(png_ptr, &io_copy, read_and_copy);
     98    }
     99 
    100    png_read_info(png_ptr, info_ptr);
    101 
    102    {
    103       png_size_t rowbytes = png_get_rowbytes(png_ptr, info_ptr);
    104 
    105       row = voidcast(png_bytep,malloc(rowbytes));
    106       display = voidcast(png_bytep,malloc(rowbytes));
    107 
    108       if (row == NULL || display == NULL)
    109          png_error(png_ptr, "OOM allocating row buffers");
    110 
    111       {
    112          png_uint_32 height = png_get_image_height(png_ptr, info_ptr);
    113          int passes = png_set_interlace_handling(png_ptr);
    114          int pass;
    115 
    116          png_start_read_image(png_ptr);
    117 
    118          for (pass = 0; pass < passes; ++pass)
    119          {
    120             png_uint_32 y = height;
    121 
    122             /* NOTE: this trashes the row each time; interlace handling won't
    123              * work, but this avoids memory thrashing for speed testing and is
    124              * somewhat representative of an application that works row-by-row.
    125              */
    126             while (y-- > 0)
    127                png_read_row(png_ptr, row, display);
    128          }
    129       }
    130    }
    131 
    132    /* Make sure to read to the end of the file: */
    133    png_read_end(png_ptr, info_ptr);
    134 
    135    /* Free this up: */
    136    free(row);
    137    free(display);
    138 }
    139 
    140 static PNG_CALLBACK(void, no_warnings, (png_structp png_ptr,
    141          png_const_charp warning))
    142 {
    143    (void)png_ptr;
    144    (void)warning;
    145 }
    146 
    147 static int read_png(FILE *fp, png_int_32 transforms, FILE *write_file)
    148 {
    149    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,
    150          no_warnings);
    151    png_infop info_ptr = NULL;
    152 
    153    if (png_ptr == NULL)
    154       return 0;
    155 
    156    if (setjmp(png_jmpbuf(png_ptr)))
    157    {
    158       png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    159       return 0;
    160    }
    161 
    162 #  ifdef PNG_BENIGN_ERRORS_SUPPORTED
    163       png_set_benign_errors(png_ptr, 1/*allowed*/);
    164 #  endif
    165    png_init_io(png_ptr, fp);
    166 
    167    info_ptr = png_create_info_struct(png_ptr);
    168 
    169    if (info_ptr == NULL)
    170       png_error(png_ptr, "OOM allocating info structure");
    171 
    172    if (transforms < 0)
    173       read_by_row(png_ptr, info_ptr, write_file, fp);
    174 
    175    else
    176       png_read_png(png_ptr, info_ptr, transforms, NULL/*params*/);
    177 
    178    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    179    return 1;
    180 }
    181 
    182 static int mytime(struct timespec *t)
    183 {
    184    /* Do the timing using clock_gettime and the per-process timer. */
    185    if (!clock_gettime(CLOCK_PROCESS_CPUTIME_ID, t))
    186       return 1;
    187 
    188    perror("CLOCK_PROCESS_CPUTIME_ID");
    189    fprintf(stderr, "timepng: could not get the time\n");
    190    return 0;
    191 }
    192 
    193 static int perform_one_test(FILE *fp, int nfiles, png_int_32 transforms)
    194 {
    195    int i;
    196    struct timespec before, after;
    197 
    198    /* Clear out all errors: */
    199    rewind(fp);
    200 
    201    if (mytime(&before))
    202    {
    203       for (i=0; i<nfiles; ++i)
    204       {
    205          if (read_png(fp, transforms, NULL/*write*/))
    206          {
    207             if (ferror(fp))
    208             {
    209                perror("temporary file");
    210                fprintf(stderr, "file %d: error reading PNG data\n", i);
    211                return 0;
    212             }
    213          }
    214 
    215          else
    216          {
    217             perror("temporary file");
    218             fprintf(stderr, "file %d: error from libpng\n", i);
    219             return 0;
    220          }
    221       }
    222    }
    223 
    224    else
    225       return 0;
    226 
    227    if (mytime(&after))
    228    {
    229       /* Work out the time difference and print it - this is the only output,
    230        * so flush it immediately.
    231        */
    232       unsigned long s = after.tv_sec - before.tv_sec;
    233       long ns = after.tv_nsec - before.tv_nsec;
    234 
    235       if (ns < 0)
    236       {
    237          --s;
    238          ns += 1000000000;
    239 
    240          if (ns < 0)
    241          {
    242             fprintf(stderr, "timepng: bad clock from kernel\n");
    243             return 0;
    244          }
    245       }
    246 
    247       printf("%lu.%.9ld\n", s, ns);
    248       fflush(stdout);
    249       if (ferror(stdout))
    250       {
    251          fprintf(stderr, "timepng: error writing output\n");
    252          return 0;
    253       }
    254 
    255       /* Successful return */
    256       return 1;
    257    }
    258 
    259    else
    260       return 0;
    261 }
    262 
    263 static int add_one_file(FILE *fp, char *name)
    264 {
    265    FILE *ip = fopen(name, "rb");
    266 
    267    if (ip != NULL)
    268    {
    269       /* Read the file using libpng; this detects errors and also deals with
    270        * files which contain data beyond the end of the file.
    271        */
    272       int ok = 0;
    273       fpos_t pos;
    274 
    275       if (fgetpos(fp, &pos))
    276       {
    277          /* Fatal error reading the start: */
    278          perror("temporary file");
    279          fprintf(stderr, "temporary file fgetpos error\n");
    280          exit(1);
    281       }
    282 
    283       if (read_png(ip, -1/*by row*/, fp/*output*/))
    284       {
    285          if (ferror(ip))
    286          {
    287             perror(name);
    288             fprintf(stderr, "%s: read error\n", name);
    289          }
    290 
    291          else
    292             ok = 1; /* read ok */
    293       }
    294 
    295       else
    296          fprintf(stderr, "%s: file not added\n", name);
    297 
    298       (void)fclose(ip);
    299 
    300       /* An error in the output is fatal; exit immediately: */
    301       if (ferror(fp))
    302       {
    303          perror("temporary file");
    304          fprintf(stderr, "temporary file write error\n");
    305          exit(1);
    306       }
    307 
    308       if (ok)
    309          return 1;
    310 
    311       /* Did not read the file successfully, simply rewind the temporary
    312        * file.  This must happen after the ferror check above to avoid clearing
    313        * the error.
    314        */
    315       if (fsetpos(fp, &pos))
    316       {
    317          perror("temporary file");
    318          fprintf(stderr, "temporary file fsetpos error\n");
    319          exit(1);
    320       }
    321    }
    322 
    323    else
    324    {
    325       /* file open error: */
    326       perror(name);
    327       fprintf(stderr, "%s: open failed\n", name);
    328    }
    329 
    330    return 0; /* file not added */
    331 }
    332 
    333 static void
    334 usage(FILE *fp)
    335 {
    336    if (fp != NULL) fclose(fp);
    337 
    338    fprintf(stderr,
    339 "Usage:\n"
    340 " timepng --assemble <assembly> {files}\n"
    341 "  Read the files into <assembly>, output the count.  Options are ignored.\n"
    342 " timepng --dissemble <assembly> <count> [options]\n"
    343 "  Time <count> files from <assembly>, additional files may not be given.\n"
    344 " Otherwise:\n"
    345 "  Read the files into a temporary file and time the decode\n"
    346 "Transforms:\n"
    347 "  --by-image: read by image with png_read_png\n"
    348 "  --<transform>: implies by-image, use PNG_TRANSFORM_<transform>\n"
    349 "  Otherwise: read by row using png_read_row (to a single row buffer)\n"
    350    /* ISO C90 string length max 509 */);fprintf(stderr,
    351 "{files}:\n"
    352 "  PNG files to copy into the assembly and time.  Invalid files are skipped\n"
    353 "  with appropriate error messages.  If no files are given the list of files\n"
    354 "  is read from stdin with each file name terminated by a newline\n"
    355 "Output:\n"
    356 "  For --assemble the output is the name of the assembly file followed by the\n"
    357 "  count of the files it contains; the arguments for --dissemble.  Otherwise\n"
    358 "  the output is the total decode time in seconds.\n");
    359 
    360    exit(99);
    361 }
    362 
    363 int main(int argc, char **argv)
    364 {
    365    int ok = 0;
    366    int err = 0;
    367    int nfiles = 0;
    368    int transforms = -1; /* by row */
    369    const char *assembly = NULL;
    370    FILE *fp;
    371 
    372    if (argc > 2 && strcmp(argv[1], "--assemble") == 0)
    373    {
    374       /* Just build the test file, argv[2] is the file name. */
    375       assembly = argv[2];
    376       fp = fopen(assembly, "wb");
    377       if (fp == NULL)
    378       {
    379          perror(assembly);
    380          fprintf(stderr, "timepng --assemble %s: could not open for write\n",
    381                assembly);
    382          usage(NULL);
    383       }
    384 
    385       argv += 2;
    386       argc -= 2;
    387    }
    388 
    389    else if (argc > 3 && strcmp(argv[1], "--dissemble") == 0)
    390    {
    391       fp = fopen(argv[2], "rb");
    392 
    393       if (fp == NULL)
    394       {
    395          perror(argv[2]);
    396          fprintf(stderr, "timepng --dissemble %s: could not open for read\n",
    397                argv[2]);
    398          usage(NULL);
    399       }
    400 
    401       nfiles = atoi(argv[3]);
    402       if (nfiles <= 0)
    403       {
    404          fprintf(stderr,
    405                "timepng --dissemble <file> <count>: %s is not a count\n",
    406                argv[3]);
    407          exit(99);
    408       }
    409 #ifdef __COVERITY__
    410       else
    411       {
    412          nfiles &= PNG_UINT_31_MAX;
    413       }
    414 #endif
    415 
    416       argv += 3;
    417       argc -= 3;
    418    }
    419 
    420    else /* Else use a temporary file */
    421    {
    422 #ifndef __COVERITY__
    423       fp = tmpfile();
    424 #else
    425       /* Experimental. Coverity says tmpfile() is insecure because it
    426        * generates predictable names.
    427        *
    428        * It is possible to satisfy Coverity by using mkstemp(); however,
    429        * any platform supporting mkstemp() undoubtedly has a secure tmpfile()
    430        * implementation as well, and doesn't need the fix.  Note that
    431        * the fix won't work on platforms that don't support mkstemp().
    432        *
    433        * https://www.securecoding.cert.org/confluence/display/c/
    434        * FIO21-C.+Do+not+create+temporary+files+in+shared+directories
    435        * says that most historic implementations of tmpfile() provide
    436        * only a limited number of possible temporary file names
    437        * (usually 26) before file names are recycled. That article also
    438        * provides a secure solution that unfortunately depends upon mkstemp().
    439        */
    440       char tmpfile[] = "timepng-XXXXXX";
    441       int filedes;
    442       umask(0177);
    443       filedes = mkstemp(tmpfile);
    444       if (filedes < 0)
    445         fp = NULL;
    446       else
    447       {
    448         fp = fdopen(filedes,"w+");
    449         /* Hide the filename immediately and ensure that the file does
    450          * not exist after the program ends
    451          */
    452         (void) unlink(tmpfile);
    453       }
    454 #endif
    455 
    456       if (fp == NULL)
    457       {
    458          perror("tmpfile");
    459          fprintf(stderr, "timepng: could not open the temporary file\n");
    460          exit(1); /* not a user error */
    461       }
    462    }
    463 
    464    /* Handle the transforms: */
    465    while (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-')
    466    {
    467       const char *opt = *++argv + 2;
    468 
    469       --argc;
    470 
    471       /* Transforms turn on the by-image processing and maybe set some
    472        * transforms:
    473        */
    474       if (transforms == -1)
    475          transforms = PNG_TRANSFORM_IDENTITY;
    476 
    477       if (strcmp(opt, "by-image") == 0)
    478       {
    479          /* handled above */
    480       }
    481 
    482 #        define OPT(name) else if (strcmp(opt, #name) == 0)\
    483          transforms |= PNG_TRANSFORM_ ## name
    484 
    485       OPT(STRIP_16);
    486       OPT(STRIP_ALPHA);
    487       OPT(PACKING);
    488       OPT(PACKSWAP);
    489       OPT(EXPAND);
    490       OPT(INVERT_MONO);
    491       OPT(SHIFT);
    492       OPT(BGR);
    493       OPT(SWAP_ALPHA);
    494       OPT(SWAP_ENDIAN);
    495       OPT(INVERT_ALPHA);
    496       OPT(STRIP_FILLER);
    497       OPT(STRIP_FILLER_BEFORE);
    498       OPT(STRIP_FILLER_AFTER);
    499       OPT(GRAY_TO_RGB);
    500       OPT(EXPAND_16);
    501       OPT(SCALE_16);
    502 
    503       else
    504       {
    505          fprintf(stderr, "timepng %s: unrecognized transform\n", opt);
    506          usage(fp);
    507       }
    508    }
    509 
    510    /* Handle the files: */
    511    if (argc > 1 && nfiles > 0)
    512       usage(fp); /* Additional files not valid with --dissemble */
    513 
    514    else if (argc > 1)
    515    {
    516       int i;
    517 
    518       for (i=1; i<argc; ++i)
    519       {
    520          if (nfiles == INT_MAX)
    521          {
    522             fprintf(stderr, "%s: skipped, too many files\n", argv[i]);
    523             break;
    524          }
    525 
    526          else if (add_one_file(fp, argv[i]))
    527             ++nfiles;
    528       }
    529    }
    530 
    531    else if (nfiles == 0) /* Read from stdin withoout --dissemble */
    532    {
    533       char filename[FILENAME_MAX+1];
    534 
    535       while (fgets(filename, FILENAME_MAX+1, stdin))
    536       {
    537          size_t len = strlen(filename);
    538 
    539          if (filename[len-1] == '\n')
    540          {
    541             filename[len-1] = 0;
    542             if (nfiles == INT_MAX)
    543             {
    544                fprintf(stderr, "%s: skipped, too many files\n", filename);
    545                break;
    546             }
    547 
    548             else if (add_one_file(fp, filename))
    549                ++nfiles;
    550          }
    551 
    552          else
    553          {
    554             fprintf(stderr, "timepng: file name too long: ...%s\n",
    555                filename+len-32);
    556             err = 1;
    557             break;
    558          }
    559       }
    560 
    561       if (ferror(stdin))
    562       {
    563          fprintf(stderr, "timepng: stdin: read error\n");
    564          err = 1;
    565       }
    566    }
    567 
    568    /* Perform the test, or produce the --assemble output: */
    569    if (!err)
    570    {
    571       if (nfiles > 0)
    572       {
    573          if (assembly != NULL)
    574          {
    575             if (fflush(fp) && !ferror(fp) && fclose(fp))
    576             {
    577                perror(assembly);
    578                fprintf(stderr, "%s: close failed\n", assembly);
    579             }
    580 
    581             else
    582             {
    583                printf("%s %d\n", assembly, nfiles);
    584                fflush(stdout);
    585                ok = !ferror(stdout);
    586             }
    587          }
    588 
    589          else
    590          {
    591             ok = perform_one_test(fp, nfiles, transforms);
    592             (void)fclose(fp);
    593          }
    594       }
    595 
    596       else
    597          usage(fp);
    598    }
    599 
    600    else
    601       (void)fclose(fp);
    602 
    603    /* Exit code 0 on success. */
    604    return ok == 0;
    605 }
    606 #else /* !sufficient support */
    607 int main(void) { return 77; }
    608 #endif /* !sufficient support */
    609