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 61typedef struct 62{ 63 FILE *input; 64 FILE *output; 65} io_data; 66 67static 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 83static 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 140static PNG_CALLBACK(void, no_warnings, (png_structp png_ptr, 141 png_const_charp warning)) 142{ 143 (void)png_ptr; 144 (void)warning; 145} 146 147static 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 182static 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 193static 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 263static 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 333static void 334usage(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 363int 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 */ 607int main(void) { return 77; } 608#endif /* !sufficient support */ 609