timepng.c 15 KB

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