Coverage Report

Created: 2026-09-28 07:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/jpegoptim/jpegoptim.c
Line
Count
Source
1
/*******************************************************************
2
 * JPEGoptim
3
 * Copyright (c) Timo Kokkonen, 1996-2026.
4
 * All Rights Reserved.
5
 *
6
 * requires libjpeg (Independent JPEG Group's JPEG software
7
 *                     release 6a or later...)
8
 *
9
 * SPDX-License-Identifier: GPL-3.0-or-later
10
 *
11
 * This file is part of JPEGoptim.
12
 *
13
 * JPEGoptim is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation, either version 3 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * JPEGoptim is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with JPEGoptim. If not, see <https://www.gnu.org/licenses/>.
25
 */
26
27
#ifdef HAVE_CONFIG_H
28
#include "config.h"
29
#endif
30
#include <stdio.h>
31
#include <stdlib.h>
32
#ifdef HAVE_UNISTD_H
33
#include <unistd.h>
34
#endif
35
#if HAVE_DIRENT_H
36
#include <dirent.h>
37
#endif
38
#if HAVE_FCNTL_H
39
#include <fcntl.h>
40
#endif
41
#if HAVE_SYS_STAT_H
42
#include <sys/stat.h>
43
#endif
44
#if HAVE_SYS_TYPES_H
45
#include <sys/types.h>
46
#endif
47
#if HAVE_SYS_WAIT_H
48
#include <sys/wait.h>
49
#endif
50
#if HAVE_GETOPT_H && HAVE_GETOPT_LONG
51
#include <getopt.h>
52
#else
53
#include "getopt.h"
54
#endif
55
#include <signal.h>
56
#include <string.h>
57
#include <limits.h>
58
#include <jpeglib.h>
59
#include <jerror.h>
60
#include <setjmp.h>
61
#include <time.h>
62
#include <math.h>
63
64
#include "jpegmarker.h"
65
#include "jpegoptim.h"
66
67
68
0
#define VERSION "1.5.7beta"
69
0
#define COPYRIGHT  "Copyright (C) 1996-2026, Timo Kokkonen"
70
71
#if HAVE_WAIT && HAVE_FORK
72
#define PARALLEL_PROCESSING 1
73
0
#define MAX_WORKERS 256
74
#include <poll.h>
75
#include <errno.h>
76
#endif
77
78
1.51k
#define IN_BUF_SIZE (256 * 1024)
79
80
81
struct my_error_mgr {
82
  struct jpeg_error_mgr pub;
83
  jmp_buf setjmp_buffer;
84
  int     jump_set;
85
};
86
typedef struct my_error_mgr * my_error_ptr;
87
88
89
#ifdef PARALLEL_PROCESSING
90
struct worker {
91
  pid_t pid;
92
  int   read_pipe;
93
};
94
struct worker *workers;
95
int worker_count = 0;
96
#endif
97
98
99
int verbose_mode = 0;
100
int quiet_mode = 0;
101
int preserve_mode = 0;
102
int preserve_perms = 0;
103
int overwrite_mode = 0;
104
int retry_mode = 0;
105
int totals_mode = 0;
106
int stdin_mode = 0;
107
int stdout_mode = 0;
108
int noaction = 0;
109
int quality = -1;
110
int dest = 0;
111
int force = 0;
112
int save_extra = 0;
113
int save_exif = 1;
114
int save_iptc = 1;
115
int save_com = 1;
116
int save_icc = 1;
117
int save_xmp = 1;
118
int save_adobe = 0;
119
int save_jfxx = 0;
120
int save_jfif = 1;
121
int strip_none = 0;
122
double threshold = -1.0;
123
int csv = 0;
124
int auto_mode = 0;
125
int all_normal = 0;
126
int all_progressive = 0;
127
int target_size = 0;
128
#ifdef HAVE_ARITH_CODE
129
int arith_mode = -1;
130
#endif
131
int max_workers = 1;
132
int nofix_mode = 0;
133
int files_stdin = 0;
134
FILE *files_from = NULL;
135
136
int compress_err_count = 0;
137
int decompress_err_count = 0;
138
int global_error_counter = 0;
139
char last_error[JMSG_LENGTH_MAX+1];
140
FILE *jpeg_log_fh;
141
long average_count = 0;
142
double average_rate = 0.0;
143
double total_save = 0.0;
144
145
const struct option long_options[] = {
146
#ifdef HAVE_ARITH_CODE
147
  { "all-arith",          0, &arith_mode,          1 },
148
  { "all-huffman",        0, &arith_mode,          0 },
149
#endif
150
  { "auto-mode",          0, &auto_mode,          1 },
151
  { "all-normal",         0, &all_normal,          1 },
152
  { "all-progressive",    0, &all_progressive,     1 },
153
  { "csv",                0, 0,                    'b' },
154
  { "dest",               1, 0,                    'd' },
155
  { "files-stdin",        0, &files_stdin,         1 },
156
  { "files-from",         1, 0,                    'F' },
157
  { "force",              0, 0,                    'f' },
158
  { "help",               0, 0,                    'h' },
159
  { "keep-adobe",         0, &save_adobe,          1 },
160
  { "keep-all",           0, &strip_none,          1 },
161
  { "keep-com",           0, &save_com,            1 },
162
  { "keep-exif",          0, &save_exif,           1 },
163
  { "keep-iptc",          0, &save_iptc,           1 },
164
  { "keep-icc",           0, &save_icc,            1 },
165
  { "keep-jfif",          0, &save_jfif,           1 },
166
  { "keep-jfxx",          0, &save_jfxx,           1 },
167
  { "keep-xmp",           0, &save_xmp,            1 },
168
  { "max",                1, 0,                    'm' },
169
  { "noaction",           0, 0,                    'n' },
170
  { "nofix",              0, &nofix_mode,          1 },
171
  { "overwrite",          0, 0,                    'o' },
172
  { "preserve",           0, 0,                    'p' },
173
  { "preserve-perms",     0, 0,                    'P' },
174
  { "quiet",              0, 0,                    'q' },
175
  { "retry",              0, 0,                    'r' },
176
  { "save-extra",         0, &save_extra,          1 },
177
  { "size",               1, 0,                    'S' },
178
  { "stdin",              0, &stdin_mode,          1 },
179
  { "stdout",             0, &stdout_mode,         1 },
180
  { "strip-all",          0, 0,                    's' },
181
  { "strip-none",         0, &strip_none,          1 },
182
  { "strip-com",          0, &save_com,            0 },
183
  { "strip-exif",         0, &save_exif,           0 },
184
  { "strip-iptc",         0, &save_iptc,           0 },
185
  { "strip-icc",          0, &save_icc,            0 },
186
  { "strip-xmp",          0, &save_xmp,            0 },
187
  { "strip-jfif",         0, &save_jfif,           0 },
188
  { "strip-jfxx",         0, &save_jfxx,           0 },
189
  { "strip-adobe",        0, &save_adobe,          0 },
190
  { "threshold",          1, 0,                    'T' },
191
  { "totals",             0, 0,                    't' },
192
  { "verbose",            0, 0,                    'v' },
193
  { "version",            0, 0,                    'V' },
194
#ifdef PARALLEL_PROCESSING
195
  { "workers",            1, 0,                    'w' },
196
#endif
197
  { 0, 0, 0, 0 }
198
};
199
200
201
/*****************************************************************/
202
203
204
void free_line_buf(JSAMPARRAY volatile *buf, unsigned int lines)
205
788
{
206
788
  if (*buf == NULL)
207
409
    return;
208
209
1.97M
  for (unsigned int i = 0; i < lines; i++) {
210
1.97M
    if ((*buf)[i])
211
1.97M
      free((*buf)[i]);
212
1.97M
  }
213
379
  free(*buf);
214
379
  *buf = NULL;
215
379
}
216
217
218
METHODDEF(void) my_error_exit (j_common_ptr cinfo)
219
294
{
220
294
  my_error_ptr myerr = (my_error_ptr)cinfo->err;
221
222
294
  (*cinfo->err->output_message)(cinfo);
223
294
  if (myerr->jump_set)
224
294
    longjmp(myerr->setjmp_buffer, 1);
225
0
  else
226
0
    fatal("fatal error");
227
294
}
228
229
230
METHODDEF(void) my_output_message (j_common_ptr cinfo)
231
1.07k
{
232
1.07k
  char buffer[JMSG_LENGTH_MAX+1];
233
234
1.07k
  (*cinfo->err->format_message)((j_common_ptr)cinfo, buffer);
235
1.07k
  buffer[sizeof(buffer) - 1] = 0;
236
237
1.07k
  if (verbose_mode)
238
0
    fprintf(jpeg_log_fh, " (%s) ", buffer);
239
240
1.07k
  global_error_counter++;
241
1.07k
  strncopy(last_error, buffer, sizeof(last_error));
242
1.07k
}
243
244
245
void print_usage(void)
246
0
{
247
0
  fprintf(stdout,PROGRAMNAME " v" VERSION "  " COPYRIGHT "\n");
248
249
0
  fprintf(stdout,
250
0
    "Usage: " PROGRAMNAME " [options] <filenames> \n\n"
251
0
    "  -d<path>, --dest=<path>\n"
252
0
    "                    specify alternative destination directory for \n"
253
0
    "                    optimized files (default is to overwrite originals)\n"
254
0
    "  -f, --force       force optimization\n"
255
0
    "  -h, --help        display this help and exit\n"
256
0
    "  -m<quality>, --max=<quality>\n"
257
0
    "                    set maximum image quality factor (disables lossless\n"
258
0
    "                    optimization mode, which is by default on)\n"
259
0
    "                    Valid quality values: 0 - 100\n"
260
0
    "  -n, --noaction    don't really optimize files, just print results\n"
261
0
    "  -S<size>, --size=<size>\n"
262
0
    "                    Try to optimize file to given size (disables lossless\n"
263
0
    "                    optimization mode). Target size is specified either in\n"
264
0
    "                    kilo bytes (1 - n) or as percentage (1%% - 99%%)\n"
265
0
    "  -T<threshold>, --threshold=<threshold>\n"
266
0
    "                    keep old file if the gain is below a threshold (%%)\n"
267
0
#ifdef PARALLEL_PROCESSING
268
0
    "  -w<max>, --workers=<max>\n"
269
0
    "                    set maximum number of parallel threads (default is 1)\n"
270
0
#endif
271
0
    "  -b, --csv         print progress info in CSV format\n"
272
0
    "  -o, --overwrite   overwrite target file even if it exists (meaningful\n"
273
0
    "                    only when used with -d, --dest option)\n"
274
0
    "  -p, --preserve    preserve file timestamps\n"
275
0
    "  -P, --preserve-perms\n"
276
0
    "                    preserve original file permissions by overwriting it\n"
277
0
    "  -q, --quiet       quiet mode\n"
278
0
    "  -r, --retry       try (recursively) optimize until file size does not change anymore\n"
279
0
    "  -t, --totals      print totals after processing all files\n"
280
0
    "  -v, --verbose     enable verbose mode (positively chatty)\n"
281
0
    "  -V, --version     print program version\n\n"
282
0
    "  -s, --strip-all   strip all markers from output file\n"
283
0
    "  --strip-none      do not strip any markers\n"
284
0
    "  --strip-adobe     strip Adobe (APP14) markers from output file\n"
285
0
    "  --strip-com       strip Comment markers from output file\n"
286
0
    "  --strip-exif      strip Exif markers from output file\n"
287
0
    "  --strip-iptc      strip IPTC/Photoshop (APP13) markers from output file\n"
288
0
    "  --strip-icc       strip ICC profile markers from output file\n"
289
0
    "  --strip-jfif      strip JFIF markers from output file\n"
290
0
    "  --strip-jfxx      strip JFXX (JFIF Extension) markers from output file\n"
291
0
    "  --strip-xmp       strip XMP markers markers from output file\n"
292
0
    "\n"
293
0
    "  --keep-all        do not strip any markers (same as --strip-none)\n"
294
0
    "  --keep-adobe      preserve any Adobe (APP14) markers\n"
295
0
    "  --keep-com        preserve any Comment markers\n"
296
0
    "  --keep-exif       preserve any Exif markers\n"
297
0
    "  --keep-iptc       preserve any IPTC/Photoshop (APP13) markers\n"
298
0
    "  --keep-icc        preserve any ICC profile markers\n"
299
0
    "  --keep-jfif       preserve any JFIF markers\n"
300
0
    "  --keep-jfxx       preserve any JFXX (JFIF Extension) markers\n"
301
0
    "  --keep-xmp        preserve any XMP markers markers\n"
302
0
    "\n"
303
0
    "  --all-normal      force all output files to be non-progressive\n"
304
0
    "  --all-progressive force all output files to be progressive\n"
305
0
    "  --auto-mode       select normal or progressive based on which produces\n"
306
0
    "                    smaller output file\n"
307
0
#ifdef HAVE_ARITH_CODE
308
0
    "  --all-arith       force all output files to use arithmetic coding\n"
309
0
    "  --all-huffman     force all output files to use Huffman coding\n"
310
0
#endif
311
0
    "  --stdout          send output to standard output (instead of a file)\n"
312
0
    "  --stdin           read input from standard input (instead of a file)\n"
313
0
    "  --files-stdin     Read names of files to process from stdin\n"
314
0
    "  --files-from=FILE Read names of files to process from a file\n"
315
0
    "  --nofix           skip processing of input files if they contain any errors\n"
316
0
    "  --save-extra      preserve extraneous data after the end of image\n"
317
0
    "\n\n");
318
0
}
319
320
321
void print_version()
322
0
{
323
0
  struct jpeg_error_mgr jerr;
324
325
0
#ifdef  __DATE__
326
0
  printf(PROGRAMNAME " v%s  %s (%s)\n",VERSION, HOST_TYPE, __DATE__);
327
#else
328
  printf(PROGRAMNAME " v%s  %s\n", VERSION, HOST_TYPE);
329
#endif
330
0
  printf(COPYRIGHT "\n\n");
331
0
  printf("This program comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
332
0
    "and you are welcome to redistribute it under certain conditions.\n"
333
0
    "See the GNU General Public License for more details.\n\n");
334
335
0
  if (!jpeg_std_error(&jerr))
336
0
    fatal("jpeg_std_error() failed");
337
338
0
  printf("\nlibjpeg version: %s\n%s\n",
339
0
    jerr.jpeg_message_table[JMSG_VERSION],
340
0
    jerr.jpeg_message_table[JMSG_COPYRIGHT]);
341
0
}
342
343
344
void parse_arguments(int argc, char **argv, char *dest_path, size_t dest_path_len)
345
0
{
346
0
  int opt_index;
347
0
  int i, c;
348
349
0
  while(1) {
350
0
    opt_index=0;
351
0
    if ((c = getopt_long(argc,argv,"d:hm:nstqvfVpProT:S:bw:",
352
0
            long_options, &opt_index)) == -1)
353
0
      break;
354
355
0
    switch (c) {
356
357
0
    case 'm':
358
0
            {
359
0
        int tmpvar;
360
361
0
        if (sscanf(optarg,"%d",&tmpvar) == 1) {
362
0
          quality=tmpvar;
363
0
          if (quality < 0) quality=0;
364
0
          if (quality > 100) quality=100;
365
0
        }
366
0
        else
367
0
          fatal("invalid argument for -m, --max");
368
0
      }
369
0
      break;
370
371
0
    case 'd':
372
0
            {
373
        /* Use the allocating form of realpath(), as a caller
374
           supplied buffer would need to be at least PATH_MAX
375
           bytes, which can be larger than MAXPATHLEN... */
376
0
        char *dpath = realpath(optarg, NULL);
377
378
0
        if (!dpath)
379
0
          fatal("invalid destination directory: %s", optarg);
380
0
        if (!is_directory(dpath))
381
0
          fatal("destination not a directory: %s", dpath);
382
0
        if (strlen(dpath) + strlen(DIR_SEPARATOR_S) >= dest_path_len)
383
0
          fatal("destination directory path too long: %s", optarg);
384
0
        strncopy(dest_path, dpath, dest_path_len);
385
0
        free(dpath);
386
0
        strncatenate(dest_path, DIR_SEPARATOR_S, dest_path_len);
387
0
        if (verbose_mode)
388
0
          fprintf(stderr,"Destination directory: %s\n",dest_path);
389
0
        dest=1;
390
0
      }
391
0
      break;
392
393
0
    case 'v':
394
0
      verbose_mode++;
395
0
      break;
396
397
0
    case 'h':
398
0
      print_usage();
399
0
      exit(0);
400
0
      break;
401
402
0
    case 'q':
403
0
      quiet_mode=1;
404
0
      break;
405
406
0
    case 'r':
407
0
      retry_mode=1;
408
0
      break;
409
410
0
    case 't':
411
0
      totals_mode=1;
412
0
      break;
413
414
0
    case 'n':
415
0
      noaction=1;
416
0
      break;
417
418
0
    case 'f':
419
0
      force=1;
420
0
      break;
421
422
0
    case 'b':
423
0
      csv=1;
424
0
      quiet_mode=1;
425
0
      break;
426
427
0
    case 'V':
428
0
      print_version();
429
0
      exit(0);
430
0
      break;
431
432
0
    case 'o':
433
0
      overwrite_mode=1;
434
0
      break;
435
436
0
    case 'p':
437
0
      preserve_mode=1;
438
0
      break;
439
440
0
    case 'P':
441
0
      preserve_perms=1;
442
0
      break;
443
444
0
    case 's':
445
0
      save_exif=0;
446
0
      save_iptc=0;
447
0
      save_com=0;
448
0
      save_icc=0;
449
0
      save_xmp=0;
450
0
      save_adobe=0;
451
0
      save_jfif=0;
452
0
      save_jfxx=0;
453
0
      break;
454
455
0
    case 'T':
456
0
            {
457
0
        double tmpvar;
458
0
        if (sscanf(optarg,"%lf", &tmpvar) == 1) {
459
0
          threshold=tmpvar;
460
0
          if (threshold < 0) threshold=0;
461
0
          if (threshold > 100) threshold=100;
462
0
        }
463
0
        else fatal("invalid argument for -T, --threshold");
464
0
      }
465
0
      break;
466
467
0
    case 'S':
468
0
            {
469
0
        char *endp = NULL;
470
0
        long tmpvar = strtol(optarg, &endp, 10);
471
472
0
        if (endp == optarg || tmpvar <= 0)
473
0
          fatal("invalid argument for -S, --size: %s", optarg);
474
0
        if (endp[0] == '%' && endp[1] == 0) {
475
          /* Target size given as percentage (1% - 99%) */
476
0
          if (tmpvar >= 100)
477
0
            fatal("invalid percentage for -S, --size: %s", optarg);
478
0
          target_size = -tmpvar;
479
0
        } else if (endp[0] == 0) {
480
          /* Target size given in kilobytes */
481
0
          if (tmpvar > INT_MAX)
482
0
            fatal("invalid argument for -S, --size: %s", optarg);
483
0
          target_size = tmpvar;
484
0
        } else {
485
0
          fatal("invalid argument for -S, --size: %s", optarg);
486
0
        }
487
0
        quality=100;
488
0
      }
489
0
      break;
490
491
0
#ifdef PARALLEL_PROCESSING
492
0
    case 'w':
493
0
            {
494
0
        int tmpvar;
495
0
        if (sscanf(optarg, "%d", &tmpvar) == 1) {
496
0
          if (tmpvar > 0 && tmpvar <= MAX_WORKERS)
497
0
            max_workers = tmpvar;
498
0
        }
499
0
        else fatal("invalid argument for -w, --workers");
500
0
      }
501
0
      break;
502
0
#endif
503
504
0
    case 'F':
505
0
            {
506
0
        if (optarg[0] == '-' && optarg[1] == 0) {
507
0
          files_stdin = 1;
508
0
          break;
509
0
        }
510
0
        if (!is_file(optarg, NULL))
511
0
          fatal("argument for --files-from must be a file");
512
0
        if ((files_from = fopen(optarg, "r")) == NULL)
513
0
          fatal("cannot open file: '%s'", optarg);
514
0
      }
515
0
      break;
516
517
0
    case '?':
518
0
      exit(1);
519
520
0
    }
521
0
  }
522
523
524
  /* check for '-' option indicating input is from stdin... */
525
0
  i = optind;
526
0
  while (argv[i]) {
527
0
    if (argv[i][0]=='-' && argv[i][1]==0)
528
0
      stdin_mode=1;
529
0
    i++;
530
0
  }
531
532
0
  if (stdin_mode)
533
0
    stdout_mode=1;
534
0
  if (files_stdin)
535
0
    files_from = stdin;
536
0
  if (stdin_mode && files_from == stdin)
537
0
    fatal("cannot specify both --stdin and --files-stdin");
538
0
#ifdef PARALLEL_PROCESSING
539
0
  if (stdout_mode && max_workers > 1) {
540
    /* Parallel workers would write to stdout concurrently,
541
       interleaving (corrupting) the output stream... */
542
0
    warn("--stdout is incompatible with parallel workers, using only one worker");
543
0
    max_workers = 1;
544
0
  }
545
0
#endif
546
0
  if (all_normal && all_progressive)
547
0
    fatal("cannot specify both --all-normal and --all-progressive");
548
0
  if (auto_mode && (all_normal || all_progressive))
549
0
    fatal("cannot specify --all-normal or --all-progressive if using --auto-mode");
550
0
}
551
552
553
void own_signal_handler(int a)
554
0
{
555
0
  if (verbose_mode > 1)
556
0
    fprintf(stderr,PROGRAMNAME ": died from signal: %d\n",a);
557
0
  exit(2);
558
0
}
559
560
561
void write_markers(struct jpeg_decompress_struct *dinfo,
562
    struct jpeg_compress_struct *cinfo)
563
1.24k
{
564
1.24k
  jpeg_saved_marker_ptr mrk;
565
1.24k
  int write_marker;
566
1.24k
  const char *s_name;
567
568
1.24k
  if (!cinfo || !dinfo)
569
0
    fatal("invalid call to write_markers()");
570
571
1.24k
  mrk=dinfo->marker_list;
572
856k
  while (mrk) {
573
855k
    write_marker = 0;
574
855k
    s_name = jpeg_special_marker_name(mrk);
575
576
    /* Check for markers to save... */
577
578
855k
    if (save_com && mrk->marker == JPEG_COM)
579
3.95k
      write_marker++;
580
581
855k
    if (save_iptc && !strncmp(s_name, "IPTC", 5))
582
636
      write_marker++;
583
584
855k
    if (save_exif && !strncmp(s_name, "Exif", 5))
585
577
      write_marker++;
586
587
855k
    if (save_icc && !strncmp(s_name, "ICC", 4))
588
2.95k
      write_marker++;
589
590
855k
    if (save_xmp && !strncmp(s_name, "XMP", 4))
591
974
      write_marker++;
592
593
855k
    if (save_jfxx && !strncmp(s_name, "JFXX", 5))
594
0
      write_marker++;
595
596
855k
    if (save_adobe && !strncmp(s_name, "Adobe", 6))
597
0
      write_marker++;
598
599
855k
    if (strip_none)
600
0
      write_marker++;
601
602
603
    /* libjpeg emits some markers automatically so skip these to avoid duplicates... */
604
605
855k
    if (!strncmp(s_name, "JFIF", 5))
606
158k
      write_marker=0;
607
608
609
855k
    if (verbose_mode > 2)
610
0
      fprintf(jpeg_log_fh, " (Marker %s [%s]: %s)", jpeg_marker_name(mrk->marker),
611
0
        s_name, (write_marker ? "Keep" : "Discard"));
612
855k
    if (write_marker)
613
9.09k
      jpeg_write_marker(cinfo, mrk->marker, mrk->data, mrk->data_length);
614
615
855k
    mrk=mrk->next;
616
855k
  }
617
1.24k
}
618
619
620
unsigned int parse_markers(const struct jpeg_decompress_struct *dinfo,
621
      char *str, unsigned int str_size, unsigned int *markers_total_size)
622
745
{
623
745
  jpeg_saved_marker_ptr m;
624
745
  unsigned int count = 0;
625
745
  char *seen;
626
745
  size_t marker_types = jpeg_special_marker_types_count();
627
745
  int com_seen = 0;
628
745
  int special;
629
630
745
  if ((seen = calloc(marker_types, 1)) == NULL)
631
0
    fatal("not enough of memory");
632
633
745
  str[0] = 0;
634
745
  *markers_total_size = 0;
635
636
745
  m = dinfo->marker_list;
637
299k
  while (m) {
638
298k
    count++;
639
298k
    *markers_total_size += m->data_length;
640
641
298k
    if ((special = jpeg_special_marker(m)) >= 0) {
642
218k
      if (!seen[special])
643
1.60k
        str_add_list(str, str_size, jpeg_special_marker_types[special].name, ",");
644
218k
      seen[special]++;
645
218k
    }
646
647
298k
    if (m->marker == JPEG_COM && !com_seen) {
648
127
      str_add_list(str, str_size, "COM", ",");
649
127
      com_seen = 1;
650
127
    }
651
652
298k
    m = m->next;
653
298k
  }
654
655
745
  free(seen);
656
657
745
  return count;
658
745
}
659
660
661
662
int optimize(FILE *log_fh, const char *filename, const char *newname,
663
  const char *tmpdir, struct stat *file_stat,
664
  double *rate, double *saved,
665
  int auto_mode, int all_normal, int all_progressive)
666
508
{
667
508
  FILE *infile = NULL;
668
508
  FILE *outfile = NULL;
669
508
  const char *outfname = NULL;
670
508
  char tmpfilename[MAXPATHLEN];
671
508
  struct jpeg_decompress_struct dinfo;
672
508
  struct jpeg_compress_struct cinfo;
673
508
  struct my_error_mgr jcerr, jderr;
674
675
  /* These buffers are (re)allocated between setjmp() and a possible
676
     longjmp() from the libjpeg error handlers, and then freed at
677
     exit_point after the jump. They must be volatile-qualified, as
678
     otherwise their values are indeterminate after longjmp()
679
     (C11 7.13.2.1), risking double-free or leak. */
680
508
  JSAMPARRAY volatile buf = NULL;
681
508
  unsigned char * volatile outbuffer = NULL;
682
508
  unsigned char * volatile inbuffer = NULL;
683
508
  unsigned char * volatile tmpbuffer = NULL;
684
508
  unsigned char * volatile extrabuffer = NULL;
685
686
508
  size_t outbuffersize = 0;
687
508
  size_t inbuffersize = 0;
688
508
  size_t inbufferused = 0;
689
508
  size_t tmpbuffersize = 0;
690
508
  size_t extrabuffersize = 0;
691
508
  int write_error = 0;
692
693
508
  jvirt_barray_ptr *coef_arrays = NULL;
694
508
  char marker_str[256];
695
508
  unsigned int marker_in_count, marker_in_size;
696
697
508
  long in_image_size = 0;
698
508
  long insize = 0, outsize = 0, lastsize = 0;
699
508
  int oldquality, searchdone;
700
508
  double ratio;
701
508
  size_t last_retry_size = 0;
702
508
  int retry_count = 0;
703
508
  int retry = 0;
704
508
  int res = -1;
705
706
508
  jpeg_log_fh = log_fh;
707
708
  /* Initialize decompression object */
709
508
  dinfo.err = jpeg_std_error(&jderr.pub);
710
508
  jpeg_create_decompress(&dinfo);
711
508
  jderr.pub.error_exit=my_error_exit;
712
508
  jderr.pub.output_message=my_output_message;
713
508
  jderr.jump_set = 0;
714
715
  /* Initialize compression object */
716
508
  cinfo.err = jpeg_std_error(&jcerr.pub);
717
508
  jpeg_create_compress(&cinfo);
718
508
  jcerr.pub.error_exit=my_error_exit;
719
508
  jcerr.pub.output_message=my_output_message;
720
508
  jcerr.jump_set = 0;
721
722
508
  if (rate)
723
508
    *rate = 0.0;
724
508
  if (saved)
725
508
    *saved = 0.0;
726
727
508
  if (filename) {
728
508
    if ((infile = fopen(filename, "rb")) == NULL) {
729
0
      warn("cannot open file: %s", filename);
730
0
      res = 1;
731
0
      goto exit_point;
732
0
    }
733
508
  } else {
734
0
    infile = stdin;
735
0
    set_filemode_binary(infile);
736
0
  }
737
738
788
retry_point:
739
740
788
  if (setjmp(jderr.setjmp_buffer)) {
741
    /* Error handler for decompress */
742
148
  abort_decompress:
743
148
    jpeg_abort_decompress(&dinfo);
744
148
    fclose(infile);
745
148
    free_line_buf(&buf, dinfo.output_height);
746
148
    if (!quiet_mode || csv)
747
148
      fprintf(log_fh,csv ? ",,,,,error\n" : " [ERROR]\n");
748
148
    jderr.jump_set=0;
749
148
    res = 1;
750
148
    goto exit_point;
751
640
  } else {
752
640
    jderr.jump_set=1;
753
640
  }
754
755
756
  /* Prepare to decompress */
757
640
  if (!retry) {
758
508
    if (!quiet_mode || csv) {
759
508
      if (csv) {
760
0
        fprint_csv_field(log_fh, (filename ? filename : "stdin"));
761
0
        fputc(',', log_fh);
762
508
      } else {
763
508
        fprintf(log_fh, "%s ", (filename ? filename : "stdin"));
764
508
      }
765
508
      fflush(log_fh);
766
508
    }
767
768
508
    if (stdin_mode || stdout_mode) {
769
0
      inbuffersize = IN_BUF_SIZE;
770
508
    } else {
771
508
      long fsize = file_size(infile);
772
508
      if (fsize < 0)
773
0
        fatal("failed to stat() input file: %s", filename);
774
508
      inbuffersize = (fsize < IN_BUF_SIZE ? IN_BUF_SIZE : (size_t)fsize);
775
508
    }
776
508
    if (inbuffer)
777
0
      free(inbuffer);
778
508
    if (!(inbuffer=calloc(inbuffersize, 1)))
779
0
      fatal("not enough memory");
780
508
  }
781
640
  global_error_counter=0;
782
640
  jpeg_save_markers(&dinfo, JPEG_COM, 0xffff);
783
13.2k
  for (int i = 0; i < 16; i++) {
784
12.6k
    jpeg_save_markers(&dinfo, JPEG_APP0 + i, 0xffff);
785
12.6k
  }
786
640
  if (!retry) {
787
508
    jpeg_custom_src(&dinfo, infile, &inbuffer, &inbuffersize, &inbufferused, IN_BUF_SIZE);
788
508
  } else {
789
    /* Only the --retry shrink loop (retry == 2) deliberately re-compresses
790
       its own previous output. The lossless fallback (retry == 1) and the
791
       auto-mode alternate pass (retry == 3) must start from the original
792
       image, as recompressing already lossy-compressed data would cause
793
       generation loss. */
794
132
    if (retry == 2)
795
0
      jpeg_custom_mem_src(&dinfo, tmpbuffer, tmpbuffersize);
796
132
    else
797
132
      jpeg_custom_mem_src(&dinfo, inbuffer, inbufferused);
798
132
  }
799
640
  jpeg_read_header(&dinfo, TRUE);
800
801
802
  /* Check for known (Exif, IPTC, ICC , XMP, ...) markers */
803
640
  marker_in_count = parse_markers(&dinfo, marker_str, sizeof(marker_str),
804
640
          &marker_in_size);
805
806
640
  if (!retry) {
807
465
    if (verbose_mode > 1) {
808
0
      fprintf(log_fh,"%d markers found in input file (total size %d bytes)\n",
809
0
        marker_in_count,marker_in_size);
810
0
      fprintf(log_fh,"coding: %s\n", (dinfo.arith_code == TRUE ? "Arithmetic" : "Huffman"));
811
0
    }
812
813
465
    if (!quiet_mode || csv) {
814
465
      fprintf(log_fh,csv ? "%dx%d,%dbit,%c," : "%dx%d %dbit %c ",(int)dinfo.image_width,
815
465
        (int)dinfo.image_height,(int)dinfo.num_components*8,
816
465
        (dinfo.progressive_mode?'P':'N'));
817
465
      if (!csv)
818
465
        fprintf(log_fh,"%s",marker_str);
819
465
      fflush(log_fh);
820
465
    }
821
465
  }
822
823
  /* Decompress the image */
824
676
  if (quality >= 0 && retry != 1) {
825
396
    jpeg_start_decompress(&dinfo);
826
827
    /* Allocate line buffer to store the decompressed image */
828
396
    if (!(buf = calloc(dinfo.output_height, sizeof(JSAMPROW))))
829
0
      fatal("not enough memory");
830
1.97M
    for (int i = 0; i < dinfo.output_height; i++) {
831
1.97M
      if (!(buf[i]=calloc((size_t)dinfo.output_width * dinfo.out_color_components,
832
1.97M
              sizeof(JSAMPLE))))
833
0
        fatal("not enough memory");
834
1.97M
    }
835
836
909k
    while (dinfo.output_scanline < dinfo.output_height) {
837
908k
      jpeg_read_scanlines(&dinfo,&buf[dinfo.output_scanline],
838
908k
          dinfo.output_height-dinfo.output_scanline);
839
908k
    }
840
396
  } else {
841
244
    coef_arrays = jpeg_read_coefficients(&dinfo);
842
244
    if (!coef_arrays) {
843
0
      if (!quiet_mode)
844
0
        fprintf(log_fh, " (failed to read coefficients) ");
845
0
      goto abort_decompress;
846
0
    }
847
244
  }
848
640
  if (!retry) {
849
378
    in_image_size = inbufferused - dinfo.src->bytes_in_buffer;
850
378
    if(verbose_mode > 2)
851
0
      fprintf(log_fh, " (input image size: %ld (%ld))", in_image_size, inbufferused);
852
378
    if (stdin_mode) {
853
0
      insize = in_image_size;
854
378
    } else {
855
378
      if ((insize = file_size(infile)) < 0)
856
0
        fatal("failed to stat() input file: %s", filename);
857
378
      if (in_image_size > 0 && in_image_size < insize) {
858
74
        if (!quiet_mode)
859
74
          fprintf(log_fh, " (%ld bytes extraneous data found after end of image) ",
860
74
            insize - in_image_size);
861
74
        if (nofix_mode)
862
0
          global_error_counter++;
863
74
        if (save_extra) {
864
0
          extrabuffersize = insize - in_image_size;
865
0
          if (extrabuffer)
866
0
            free(extrabuffer);
867
0
          if (!(extrabuffer = calloc(extrabuffersize, 1)))
868
0
            fatal("not enough memory");
869
0
          if (fseek(infile, in_image_size, SEEK_SET))
870
0
            fatal("failed to seek input file");
871
0
          if (fread(extrabuffer, extrabuffersize, 1, infile) != 1)
872
0
            fatal("failed to read inputfile");
873
0
        }
874
74
      }
875
378
    }
876
378
    if (!quiet_mode) {
877
378
      fprintf(log_fh,(global_error_counter==0 ? " [OK] " : " [WARNING] "));
878
378
      fflush(log_fh);
879
378
    }
880
881
378
    if (nofix_mode && global_error_counter != 0) {
882
      /* Skip files containing any errors (or warnings) */
883
0
      goto abort_decompress;
884
0
    }
885
886
378
    if (dest && !noaction) {
887
0
      if (file_exists(newname) && !overwrite_mode) {
888
0
        if (!quiet_mode)
889
0
          fprintf(log_fh, " (target file already exists) ");
890
0
        goto abort_decompress;
891
0
      }
892
0
    }
893
378
  }
894
895
896
  /* Prepare to compress... */
897
640
  if (setjmp(jcerr.setjmp_buffer)) {
898
    /* Error handler for compress failures */
899
146
    if (!quiet_mode)
900
146
      fprintf(log_fh," [Compress ERROR: %s]\n",last_error);
901
146
    jpeg_abort_compress(&cinfo);
902
146
    jpeg_abort_decompress(&dinfo);
903
146
    fclose(infile);
904
146
    free_line_buf(&buf, dinfo.output_height);
905
146
    jcerr.jump_set=0;
906
146
    res = 2;
907
146
    goto exit_point;
908
494
  } else {
909
494
    jcerr.jump_set=1;
910
494
  }
911
912
494
  lastsize = 0;
913
494
  searchdone = 0;
914
494
  if (!retry) {
915
378
    oldquality = 200;
916
378
    if (target_size != 0) {
917
      /* Always start with quality 100 if -S option specified... */
918
378
      quality = 100;
919
378
    }
920
378
  }
921
922
923
1.31k
binary_search_loop:
924
925
  /* Allocate memory buffer that should be large enough to store the output JPEG... */
926
1.31k
  if (outbuffer)
927
933
    free(outbuffer);
928
1.31k
  outbuffersize = insize + 32768;
929
1.31k
  if (!(outbuffer=calloc(outbuffersize, 1)))
930
0
    fatal("not enough memory");
931
932
  /* setup custom "destination manager" for libjpeg to write to our buffer */
933
1.31k
  jpeg_memory_dest(&cinfo, &outbuffer, &outbuffersize, 65536);
934
935
936
1.31k
  if (quality >= 0 && retry != 1) {
937
    /* Lossy "optimization" ... */
938
939
1.03k
    cinfo.in_color_space=dinfo.out_color_space;
940
1.03k
    cinfo.input_components=dinfo.output_components;
941
1.03k
    cinfo.image_width=dinfo.image_width;
942
1.03k
    cinfo.image_height=dinfo.image_height;
943
1.03k
    jpeg_set_defaults(&cinfo);
944
1.03k
    jpeg_set_quality(&cinfo,quality,TRUE);
945
1.03k
#ifdef HAVE_JINT_DC_SCAN_OPT_MODE
946
1.03k
    if (jpeg_c_int_param_supported(&cinfo, JINT_DC_SCAN_OPT_MODE))
947
1.03k
      jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1);
948
1.03k
#endif
949
1.03k
    if (all_normal || (!dinfo.progressive_mode && !all_progressive)) {
950
      /* Explicitly disable progressive mode. */
951
582
      cinfo.scan_info = NULL;
952
582
      cinfo.num_scans = 0;
953
582
    } else if (all_progressive || dinfo.progressive_mode) {
954
      /* Enable progressive mode. */
955
451
      jpeg_simple_progression(&cinfo);
956
451
    }
957
1.03k
    cinfo.optimize_coding = TRUE;
958
1.03k
#ifdef HAVE_ARITH_CODE
959
1.03k
    if (arith_mode >= 0)
960
0
      cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE);
961
1.03k
#endif
962
1.03k
    if (dinfo.saw_JFIF_marker && (save_jfif || strip_none)) {
963
414
      cinfo.write_JFIF_header = TRUE;
964
619
    } else {
965
619
      cinfo.write_JFIF_header = FALSE;
966
619
    }
967
1.03k
    if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) {
968
      /* If outputting Adobe marker, don't write JFIF marker... */
969
0
      cinfo.write_JFIF_header = FALSE;
970
0
    }
971
972
1.03k
    jpeg_start_compress(&cinfo,TRUE);
973
974
    /* Write markers */
975
1.03k
    write_markers(&dinfo,&cinfo);
976
977
    /* Write image */
978
2.06k
    while (cinfo.next_scanline < cinfo.image_height) {
979
1.03k
      jpeg_write_scanlines(&cinfo,&buf[cinfo.next_scanline],
980
1.03k
          cinfo.image_height - cinfo.next_scanline);
981
1.03k
    }
982
983
1.03k
  } else {
984
    /* Lossless optimization ... */
985
986
278
    jpeg_copy_critical_parameters(&dinfo, &cinfo);
987
278
#ifdef HAVE_JINT_DC_SCAN_OPT_MODE
988
278
    if (jpeg_c_int_param_supported(&cinfo, JINT_DC_SCAN_OPT_MODE))
989
217
      jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1);
990
278
#endif
991
278
    if (all_normal || (!dinfo.progressive_mode && !all_progressive)) {
992
      /* Explicitly disable progressive mode. */
993
113
      cinfo.scan_info = NULL;
994
113
      cinfo.num_scans = 0;
995
165
    } else if (all_progressive || dinfo.progressive_mode) {
996
      /* Enable progressive mode. */
997
104
      jpeg_simple_progression(&cinfo);
998
104
    }
999
278
    cinfo.optimize_coding = TRUE;
1000
278
#ifdef HAVE_ARITH_CODE
1001
278
    if (arith_mode >= 0)
1002
0
      cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE);
1003
278
#endif
1004
278
    if (dinfo.saw_JFIF_marker && (save_jfif || strip_none)) {
1005
43
      cinfo.write_JFIF_header = TRUE;
1006
235
    } else {
1007
235
      cinfo.write_JFIF_header = FALSE;
1008
235
    }
1009
278
    if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) {
1010
      /* If outputting Adobe marker, don't write JFIF marker... */
1011
0
      cinfo.write_JFIF_header = FALSE;
1012
0
    }
1013
1014
    /* Write image */
1015
278
    jpeg_write_coefficients(&cinfo, coef_arrays);
1016
1017
    /* Write markers */
1018
278
    write_markers(&dinfo,&cinfo);
1019
1020
278
  }
1021
1022
1.31k
  jpeg_finish_compress(&cinfo);
1023
1.31k
  outsize = outbuffersize + extrabuffersize;
1024
1.31k
  if (verbose_mode > 2)
1025
0
    fprintf(log_fh, " (output image size: %ld (%lu))", outsize, extrabuffersize);
1026
1027
1.31k
  if (target_size != 0 && !retry) {
1028
    /* Perform (binary) search to try to reach target file size... */
1029
1030
1.01k
    long osize = outsize/1024;
1031
1.01k
    long isize = insize/1024;
1032
1.01k
    long tsize = target_size;
1033
1034
1.01k
    if (verbose_mode > 1)
1035
0
      fprintf(log_fh, "(size=%ld)",outsize);
1036
1.01k
    if (tsize < 0) {
1037
      /* 64-bit intermediate, as target_size * insize can overflow
1038
         a 32-bit long (files > ~21MB on ILP32/LLP64 platforms) */
1039
1.01k
      tsize = (long)(((long long)(-target_size) * insize / 100) / 1024);
1040
1.01k
      if (tsize < 1)
1041
478
        tsize = 1;
1042
1.01k
    }
1043
1044
1.01k
    if (osize == tsize || searchdone || tsize > isize) {
1045
375
      if (searchdone < 42 && lastsize > 0) {
1046
140
        if (labs(osize-tsize) > labs(lastsize-tsize)) {
1047
11
          if (verbose_mode)
1048
0
            fprintf(log_fh,"(revert to %d)",oldquality);
1049
11
          searchdone = 42;
1050
11
          quality = oldquality;
1051
11
          goto binary_search_loop;
1052
11
        }
1053
140
      }
1054
364
      if (verbose_mode)
1055
0
        fprintf(log_fh," ");
1056
1057
644
    } else {
1058
644
      int newquality;
1059
644
      double dif = abs(oldquality-quality) / 2.0;
1060
1061
644
      if (osize > tsize)
1062
505
        newquality = quality - dif;
1063
139
      else
1064
139
        newquality = quality + dif + 0.5;
1065
1066
644
      if (dif < 1.0)
1067
9
        searchdone = 1;
1068
644
      if (newquality < 0) {
1069
63
        newquality = 0;
1070
63
        searchdone = 1;
1071
63
      }
1072
644
      if (newquality > 100) {
1073
44
        newquality = 100;
1074
44
        searchdone = 1;
1075
44
      }
1076
1077
644
      oldquality = quality;
1078
644
      quality = newquality;
1079
644
      lastsize = osize;
1080
644
      if (verbose_mode)
1081
0
        fprintf(log_fh,"(try %d)",quality);
1082
644
      goto binary_search_loop;
1083
644
    }
1084
1.01k
  }
1085
1086
656
  jpeg_finish_decompress(&dinfo);
1087
656
  free_line_buf(&buf, dinfo.output_height);
1088
1089
656
  if (retry_mode) {
1090
0
    if ((retry == 0 || retry == 2) && quality >= 0 && outsize <= insize) {
1091
      /* Retry compression until output file stops getting smaller
1092
         or we hit max limit of iterations (10)... */
1093
0
      if (retry_count == 0)
1094
0
        last_retry_size = outsize + 1;
1095
0
      if (++retry_count < 10 && outsize < last_retry_size) {
1096
0
        if (tmpbuffer)
1097
0
          free(tmpbuffer);
1098
0
        tmpbuffer = outbuffer;
1099
0
        tmpbuffersize = outbuffersize;
1100
0
        outbuffer = NULL;
1101
0
        last_retry_size = outsize;
1102
0
        retry = 2;
1103
0
        if (verbose_mode)
1104
0
          fprintf(log_fh, "(retry%d: %ld) ", retry_count, outsize);
1105
0
        goto retry_point;
1106
0
      }
1107
0
    }
1108
0
    if (retry == 2) {
1109
0
      if (verbose_mode)
1110
0
        fprintf(log_fh, "(retry done: %ld) ", outsize);
1111
0
      if (outsize > last_retry_size) {
1112
0
        if (outbuffer)
1113
0
          free(outbuffer);
1114
0
        outbuffer = tmpbuffer;
1115
0
        outbuffersize = tmpbuffersize;
1116
0
        outsize = outbuffersize + extrabuffersize;
1117
0
        tmpbuffer = NULL;
1118
0
      }
1119
0
    }
1120
0
  }
1121
1122
  /* If auto_mode, try both progressive and non-progressive... */
1123
656
  if (auto_mode) {
1124
0
    int newmode = (dinfo.progressive_mode ? 0 : 1);
1125
0
    if (retry != 3) {
1126
0
      if (newmode)
1127
0
        all_progressive = 1;
1128
0
      else
1129
0
        all_normal = 1;
1130
0
      if (tmpbuffer)
1131
0
        free(tmpbuffer);
1132
0
      tmpbuffer = outbuffer;
1133
0
      tmpbuffersize = outbuffersize;
1134
0
      outbuffer = NULL;
1135
0
      last_retry_size = outsize;
1136
0
      retry = 3;
1137
0
      if (verbose_mode)
1138
0
        fprintf(log_fh, "(retry w/%s) ", (newmode ? "progressive" : "normal"));
1139
0
      goto retry_point;
1140
0
    } else {
1141
0
      if (verbose_mode > 1)
1142
0
        fprintf(log_fh, "(automode done: %ld) ", outsize);
1143
0
      auto_mode = 0;
1144
0
      if (outsize > last_retry_size) {
1145
0
        if (verbose_mode)
1146
0
          fprintf(log_fh, "(revert to %s) ", (!newmode ? "progressive" : "normal"));
1147
0
        all_progressive = 0;
1148
0
        all_normal = 0;
1149
0
        if (outbuffer)
1150
0
          free(outbuffer);
1151
0
        outbuffer = tmpbuffer;
1152
0
        outbuffersize = tmpbuffersize;
1153
0
        outsize = outbuffersize + extrabuffersize;
1154
0
        tmpbuffer = NULL;
1155
0
      }
1156
0
    }
1157
0
  }
1158
1159
  /* In case "lossy" compression resulted larger file than original, retry with "lossless"... */
1160
656
  if (quality >= 0 && outsize >= insize && retry != 1) {
1161
280
    retry = 1;
1162
280
    if (verbose_mode)
1163
0
      fprintf(log_fh, "(retry w/lossless) ");
1164
280
    goto retry_point;
1165
280
  }
1166
1167
376
  fclose(infile);
1168
1169
376
  ratio = (insize - outsize) * 100.0 / insize;
1170
376
  if (!quiet_mode || csv)
1171
214
    fprintf(log_fh,csv ? "%ld,%ld,%0.2f," : "%ld --> %ld bytes (%0.2f%%), ",insize,outsize,ratio);
1172
376
  if (rate) {
1173
214
    *rate = (ratio < 0 ? 0.0 : ratio);
1174
214
  }
1175
1176
376
  if ((outsize < insize && ratio >= threshold) || force) {
1177
77
    if (saved) {
1178
77
      *saved = (insize - outsize) / 1024.0;
1179
77
    }
1180
77
    if (!quiet_mode || csv)
1181
77
      fprintf(log_fh,csv ? "optimized\n" : "optimized.\n");
1182
77
    if (noaction) {
1183
0
      res = 0;
1184
0
      goto exit_point;
1185
0
    }
1186
1187
77
    if (stdout_mode) {
1188
0
      outfname=NULL;
1189
0
      set_filemode_binary(stdout);
1190
0
      if (fwrite(outbuffer,outbuffersize,1,stdout) != 1)
1191
0
        fatal("%s, write failed to stdout",(stdin_mode ? "stdin" : filename));
1192
0
      if (save_extra && extrabuffersize > 0) {
1193
0
        if (fwrite(extrabuffer,extrabuffersize,1,stdout) != 1)
1194
0
          fatal("%s, write failed to stdout",(stdin_mode ? "stdin" : filename));
1195
0
      }
1196
77
    } else {
1197
77
      if (preserve_perms && !dest) {
1198
        /* make backup of the original file */
1199
0
        int newlen = snprintf(tmpfilename, sizeof(tmpfilename),
1200
0
            "%s.jpegoptim.bak", newname);
1201
0
        if (newlen < 0 || (size_t)newlen >= sizeof(tmpfilename))
1202
0
          fatal("temp filename too long: %s", tmpfilename);
1203
1204
0
        if (verbose_mode > 1)
1205
0
          fprintf(log_fh,"%s, creating backup as: %s\n", filename, tmpfilename);
1206
0
        if (file_exists(tmpfilename))
1207
0
          fatal("%s, backup file already exists: %s", filename, tmpfilename);
1208
0
        if (copy_file(newname,tmpfilename))
1209
0
          fatal("%s, failed to create backup: %s", filename, tmpfilename);
1210
0
        if ((outfile=create_file(newname))==NULL)
1211
0
          fatal("%s, error opening output file: %s", filename, newname);
1212
0
        outfname = newname;
1213
77
      } else {
1214
77
        if (!(outfile = create_temp_file(tmpdir, "jpegoptim", tmpfilename, sizeof(tmpfilename))))
1215
0
          fatal("error creating temporary file: %s", tmpfilename);
1216
77
        outfname = tmpfilename;
1217
77
      }
1218
1219
77
      if (verbose_mode > 1)
1220
0
        fprintf(log_fh,"writing %lu bytes to file: %s\n", outbuffersize, outfname);
1221
77
      if (fwrite(outbuffer, outbuffersize, 1, outfile) != 1) {
1222
0
        write_error = 1;
1223
77
      } else if (save_extra && extrabuffersize > 0) {
1224
0
        if (verbose_mode > 1)
1225
0
          fprintf(log_fh,"writing %lu bytes to file: %s\n", extrabuffersize, outfname);
1226
0
        if (fwrite(extrabuffer, extrabuffersize, 1, outfile) != 1)
1227
0
          write_error = 1;
1228
0
      }
1229
77
      if (fclose(outfile) != 0)
1230
0
        write_error = 1;
1231
1232
77
      if (write_error) {
1233
0
        if (preserve_perms && !dest) {
1234
          /* original file was truncated already, so restore it from the backup... */
1235
0
          warn("%s, write failed, restoring from backup: %s", filename, tmpfilename);
1236
0
          if (rename_file(tmpfilename, newname))
1237
0
            fatal("write failed to file: %s (failed to restore backup: %s)",
1238
0
              newname, tmpfilename);
1239
0
          if (chmod(newname, (file_stat->st_mode & 0777)) != 0)
1240
0
            warn("failed to restore file mode: %s", newname);
1241
0
          if (chown(newname,
1242
0
              (geteuid()==0 ? file_stat->st_uid : -1),
1243
0
              file_stat->st_gid) != 0)
1244
0
            warn("failed to restore file group/owner: %s", newname);
1245
0
          fatal("write failed to file: %s (original restored)", newname);
1246
0
        } else {
1247
          /* remove the incomplete temporary file... */
1248
0
          delete_file(outfname);
1249
0
          fatal("write failed to file: %s", outfname);
1250
0
        }
1251
0
      }
1252
77
    }
1253
1254
77
    if (outfname) {
1255
77
      if (preserve_mode) {
1256
        /* preserve file modification time */
1257
0
        if (verbose_mode > 1)
1258
0
          fprintf(log_fh,"set file modification time same as in original: %s\n",
1259
0
            outfname);
1260
0
#if defined(HAVE_UTIMENSAT) && defined(HAVE_STRUCT_STAT_ST_MTIM)
1261
0
        struct timespec time_save[2];
1262
0
        time_save[0].tv_sec = 0;
1263
0
        time_save[0].tv_nsec = UTIME_OMIT; /* omit atime */
1264
0
        time_save[1] = file_stat->st_mtim;
1265
0
        if (utimensat(AT_FDCWD,outfname,time_save,0) != 0)
1266
0
          warn("failed to reset output file time/date");
1267
#else
1268
        struct utimbuf time_save;
1269
        time_save.actime=file_stat->st_atime;
1270
        time_save.modtime=file_stat->st_mtime;
1271
        if (utime(outfname,&time_save) != 0)
1272
          warn("failed to reset output file time/date");
1273
#endif
1274
0
      }
1275
1276
77
      if (preserve_perms && !dest) {
1277
        /* original file was already replaced, remove backup... */
1278
0
        if (verbose_mode > 1)
1279
0
          fprintf(log_fh,"removing backup file: %s\n", tmpfilename);
1280
0
        if (delete_file(tmpfilename))
1281
0
          warn("failed to remove backup file: %s", tmpfilename);
1282
77
      } else {
1283
        /* make temp file to be the original file... */
1284
1285
        /* preserve file mode */
1286
77
        if (chmod(outfname,(file_stat->st_mode & 0777)) != 0)
1287
0
          warn("failed to set output file mode");
1288
1289
        /* preserve file group (and owner if run by root) */
1290
77
        if (chown(outfname,
1291
77
            (geteuid()==0 ? file_stat->st_uid : -1),
1292
77
            file_stat->st_gid) != 0)
1293
0
          warn("failed to reset output file group/owner");
1294
1295
77
        if (verbose_mode > 1)
1296
0
          fprintf(log_fh,"renaming: %s to %s\n", outfname, newname);
1297
77
        if (rename_file(outfname, newname))
1298
0
          fatal("cannot rename temp file");
1299
77
      }
1300
77
    }
1301
299
  } else {
1302
299
    if (!quiet_mode || csv)
1303
137
      fprintf(log_fh,csv ? "skipped\n" : "skipped.\n");
1304
299
    if (stdout_mode) {
1305
0
      set_filemode_binary(stdout);
1306
0
      if (fwrite(inbuffer, in_image_size, 1, stdout) != 1)
1307
0
        fatal("%s, write failed to stdout",
1308
0
          (stdin_mode ? "stdin" : filename));
1309
0
    }
1310
299
  }
1311
1312
376
  res = 0;
1313
1314
508
 exit_point:
1315
508
  if (inbuffer)
1316
508
    free(inbuffer);
1317
508
  if (outbuffer)
1318
378
    free(outbuffer);
1319
508
  if (tmpbuffer)
1320
0
    free(tmpbuffer);
1321
508
  if (extrabuffer)
1322
0
    free(extrabuffer);
1323
508
  jpeg_destroy_compress(&cinfo);
1324
508
  jpeg_destroy_decompress(&dinfo);
1325
1326
508
  return res;
1327
376
}
1328
1329
1330
#ifdef PARALLEL_PROCESSING
1331
int wait_for_worker(FILE *log_fh)
1332
0
{
1333
0
  FILE *p;
1334
0
  struct worker *w;
1335
0
  struct pollfd pfds[MAX_WORKERS];
1336
0
  int pfd_slot[MAX_WORKERS];
1337
0
  char buf[1024];
1338
0
  int wstatus;
1339
0
  pid_t pid;
1340
0
  int nfds, i, j, e;
1341
0
  int state = 0;
1342
0
  double val;
1343
0
  double rate = 0.0;
1344
0
  double saved = 0.0;
1345
1346
1347
  /* Wait for activity on worker pipes. Pipe must be drained before
1348
     calling wait(), as worker cannot exit if it is blocked writing
1349
     to a full pipe... */
1350
0
  nfds = 0;
1351
0
  for (j = 0; j < MAX_WORKERS; j++) {
1352
0
    if (workers[j].pid < 0)
1353
0
      continue;
1354
0
    pfds[nfds].fd = workers[j].read_pipe;
1355
0
    pfds[nfds].events = POLLIN;
1356
0
    pfd_slot[nfds] = j;
1357
0
    nfds++;
1358
0
  }
1359
0
  if (nfds < 1)
1360
0
    return -1;
1361
1362
0
  while (poll(pfds, nfds, -1) < 0) {
1363
0
    if (errno != EINTR)
1364
0
      fatal("poll() failed");
1365
0
  }
1366
1367
  /* Prefer a worker that has already exited (closed its pipe)... */
1368
0
  w = NULL;
1369
0
  for (i = 0; i < nfds; i++) {
1370
0
    if (pfds[i].revents & POLLHUP) {
1371
0
      j = pfd_slot[i];
1372
0
      w = &workers[j];
1373
0
      break;
1374
0
    }
1375
0
  }
1376
0
  if (!w) {
1377
0
    for (i = 0; i < nfds; i++) {
1378
0
      if (pfds[i].revents & (POLLIN | POLLERR)) {
1379
0
        j = pfd_slot[i];
1380
0
        w = &workers[j];
1381
0
        break;
1382
0
      }
1383
0
    }
1384
0
  }
1385
0
  if (!w)
1386
0
    fatal("poll() returned no active worker");
1387
0
  pid = w->pid;
1388
1389
0
  p = fdopen(w->read_pipe, "r");
1390
0
  if (!p) fatal("fdopen failed()");
1391
0
  while (fgets(buf, sizeof(buf), p)) {
1392
0
    if (verbose_mode > 2)
1393
0
      fprintf(log_fh, "worker[%d] PIPE: %s", pid, buf);
1394
0
    if (state == 0 && buf[0] == '\n') {
1395
0
      state=1;
1396
0
      continue;
1397
0
    }
1398
0
    if (state == 1 && !strncmp(buf, "STAT", 4)) {
1399
0
      state=2;
1400
0
      continue;
1401
0
    }
1402
0
    if (state >= 2) {
1403
0
      if (sscanf(buf, "%lf", &val) == 1) {
1404
0
        if (state == 2) {
1405
0
          rate = val;
1406
0
        }
1407
0
        else if (state == 3) {
1408
0
          saved = val;
1409
0
          average_count++;
1410
0
          average_rate += rate;
1411
0
          total_save += saved;
1412
0
        }
1413
0
      }
1414
0
      state++;
1415
0
      continue;
1416
0
    }
1417
0
    if (state == 0)
1418
0
      fprintf(log_fh, "%s", buf);
1419
0
  }
1420
0
  fclose(p);
1421
1422
0
  if (waitpid(pid, &wstatus, 0) < 0)
1423
0
    fatal("waitpid() failed for worker[%d]", pid);
1424
1425
0
  if (WIFEXITED(wstatus)) {
1426
0
    e = WEXITSTATUS(wstatus);
1427
0
    if (verbose_mode)
1428
0
      fprintf(log_fh, "worker[%d] [slot=%d] exited: %d\n",
1429
0
        pid, j, e);
1430
0
    if (e == 1) {
1431
0
      decompress_err_count++;
1432
0
    } else if (e == 2) {
1433
0
      compress_err_count++;
1434
0
    } else if (e != 0) {
1435
      /* worker died via fatal() or other unexpected error,
1436
         count it so program exit status reflects the failure... */
1437
0
      compress_err_count++;
1438
0
      warn("worker[%d] failed with status: %d", pid, e);
1439
0
    }
1440
0
  } else {
1441
0
    fatal("worker[%d] killed", pid);
1442
0
  }
1443
1444
0
  w->pid = -1;
1445
0
  w->read_pipe = -1;
1446
0
  worker_count --;
1447
1448
0
  return pid;
1449
0
}
1450
#endif
1451
1452
1453
#ifndef BUILD_FOR_OSS_FUZZ // Libfuzzer provides its own fuzzer
1454
/****************************************************************************/
1455
int main(int argc, char **argv)
1456
{
1457
  struct stat file_stat;
1458
  char tmpfilename[MAXPATHLEN + 1],tmpdir[MAXPATHLEN + 1];
1459
  char newname[MAXPATHLEN + 1], dest_path[MAXPATHLEN + 1];
1460
  char namebuf[MAXPATHLEN + 2];
1461
  const char *filename;
1462
  int arg_idx;
1463
  int res;
1464
  double rate, saved;
1465
  FILE *log_fh;
1466
#ifdef PARALLEL_PROCESSING
1467
  struct worker *w;
1468
  int pipe_fd[2];
1469
  pid_t pid;
1470
1471
1472
  /* Allocate table to keep track of child processes... */
1473
  if (!(workers = calloc(MAX_WORKERS, sizeof(struct worker))))
1474
    fatal("not enough memory");
1475
  for (int i = 0; i < MAX_WORKERS; i++) {
1476
    workers[i].pid = -1;
1477
    workers[i].read_pipe = -1;
1478
  }
1479
#endif
1480
1481
  umask(077);
1482
  signal(SIGINT,own_signal_handler);
1483
  signal(SIGTERM,own_signal_handler);
1484
1485
  /* Parse command line parameters */
1486
  parse_arguments(argc, argv, dest_path, sizeof(dest_path));
1487
  log_fh = (stdout_mode ? stderr : stdout);
1488
  if (quiet_mode)
1489
    verbose_mode = 0;
1490
1491
  if (verbose_mode) {
1492
    if (quality >= 0 && target_size == 0)
1493
      fprintf(log_fh, "Image quality limit set to: %d\n", quality);
1494
    if (threshold >= 0)
1495
      fprintf(log_fh, "Compression threshold (%%) set to: %0.1lf\n", threshold);
1496
    if (all_normal)
1497
      fprintf(log_fh, "All output files will be non-progressive\n");
1498
    if (all_progressive)
1499
      fprintf(log_fh, "All output files will be progressive\n");
1500
    if (target_size > 0)
1501
      fprintf(log_fh, "Target size for output files set to: %d Kbytes.\n",
1502
        target_size);
1503
    if (target_size < 0)
1504
      fprintf(log_fh, "Target size for output files set to: %d%%\n",
1505
        -target_size);
1506
#ifdef PARALLEL_PROCESSING
1507
    if (max_workers > 0)
1508
      fprintf(log_fh, "Using maximum of %d parallel threads\n", max_workers);
1509
#endif
1510
  }
1511
1512
1513
  if (stdin_mode) {
1514
    /* Process just one file, if source is stdin... */
1515
    res = optimize(stderr, NULL, NULL, NULL, &file_stat, NULL, NULL,
1516
      auto_mode, all_normal, all_progressive);
1517
    return (res == 0 ? 0 : 1);
1518
  }
1519
1520
  arg_idx = (optind > 0 ? optind : 1);
1521
  if (files_from == NULL && argc <= arg_idx) {
1522
    if (!quiet_mode)
1523
      fprintf(stderr, PROGRAMNAME ": file argument(s) missing\n"
1524
        "Try '" PROGRAMNAME " --help' for more information.\n");
1525
    exit(1);
1526
  }
1527
1528
1529
  /* Main loop to process input files */
1530
  do {
1531
    if (files_from) {
1532
      if (!fgetstr(namebuf, sizeof(namebuf), files_from))
1533
        break;
1534
      filename = namebuf;
1535
    } else {
1536
      filename = argv[arg_idx];
1537
    }
1538
1539
    if (*filename == 0)
1540
      continue;
1541
    if (verbose_mode > 1)
1542
      fprintf(log_fh, "processing file: %s\n", filename);
1543
    if (strnlen(filename, MAXPATHLEN + 1) > MAXPATHLEN) {
1544
      warn("skipping too long filename: %s", filename);
1545
      continue;
1546
    }
1547
1548
    if (noaction) {
1549
      newname[0] = 0;
1550
    } else {
1551
      /* generate tmp dir & new filename */
1552
      if (dest) {
1553
        strncopy(tmpdir, dest_path, sizeof(tmpdir));
1554
        strncopy(newname, dest_path, sizeof(newname));
1555
        if (!splitname(filename, tmpfilename, sizeof(tmpfilename)))
1556
          fatal("splitname() failed for: %s", filename);
1557
        /* Check that the destination path fits in the buffer, as
1558
           silent truncation could write to a wrong destination file... */
1559
        if (strlen(dest_path) + strlen(tmpfilename) >= sizeof(newname)) {
1560
          warn("skipping, destination path too long: %s%s",
1561
            dest_path, tmpfilename);
1562
          continue;
1563
        }
1564
        strncatenate(newname, tmpfilename, sizeof(newname));
1565
      } else {
1566
        if (!splitdir(filename, tmpdir, sizeof(tmpdir)))
1567
          fatal("splitdir() failed for: %s", filename);
1568
        strncopy(newname, filename, sizeof(newname));
1569
      }
1570
    }
1571
1572
    if (file_exists(filename)) {
1573
      if (!is_file(filename, &file_stat)) {
1574
        if (is_directory(filename))
1575
          warn("skipping directory: %s", filename);
1576
        else
1577
          warn("skipping special file: %s", filename);
1578
        continue;
1579
      }
1580
    } else {
1581
      warn("file not found: %s", filename);
1582
      continue;
1583
    }
1584
1585
#ifdef PARALLEL_PROCESSING
1586
    if (max_workers > 1) {
1587
      /* Multi process mode, run up to max_workers processes simultaneously... */
1588
1589
      if (worker_count >= max_workers) {
1590
        // wait for a worker to exit...
1591
        wait_for_worker(log_fh);
1592
      }
1593
      if (pipe(pipe_fd) < 0)
1594
        fatal("failed to open pipe");
1595
      /* flush any buffered output so child process does not
1596
         inherit it (and print it again when exiting) ... */
1597
      fflush(NULL);
1598
      pid = fork();
1599
      if (pid < 0)
1600
        fatal("fork() failed");
1601
      if (pid == 0) {
1602
        /* Child process starts here... */
1603
        if (files_from)
1604
          fclose(files_from);
1605
        close(pipe_fd[0]);
1606
        FILE *p;
1607
1608
        if (!(p = fdopen(pipe_fd[1],"w")))
1609
          fatal("worker: fdopen failed");
1610
1611
        res = optimize(p, filename, newname, tmpdir, &file_stat, &rate, &saved,
1612
          auto_mode, all_normal, all_progressive);
1613
        if (res == 0)
1614
          fprintf(p, "\n\nSTATS\n%lf\n%lf\n", rate, saved);
1615
        exit(res);
1616
      } else {
1617
        /* Parent continues here... */
1618
        int j;
1619
1620
        close(pipe_fd[1]);
1621
1622
        w = NULL;
1623
        for (j = 0; j < MAX_WORKERS; j++) {
1624
          if (workers[j].pid < 0) {
1625
            w = &workers[j];
1626
            break;
1627
          }
1628
        }
1629
        if (!w)
1630
          fatal("no space to start a new worker (%d)", worker_count);
1631
        w->pid = pid;
1632
        w->read_pipe = pipe_fd[0];
1633
        worker_count++;
1634
        if (verbose_mode > 0)
1635
          fprintf(log_fh, "worker[%d] [slot=%d] started\n", pid, j);;
1636
      }
1637
1638
    } else
1639
#endif
1640
    {
1641
      /* Single process mode, process one file at a time... */
1642
1643
      res = optimize(log_fh, filename, newname, tmpdir, &file_stat, &rate, &saved,
1644
        auto_mode, all_normal, all_progressive);
1645
      if (res == 0) {
1646
        average_count++;
1647
        average_rate += rate;
1648
        total_save += saved;
1649
      } else if (res == 1) {
1650
        decompress_err_count++;
1651
      } else if (res == 2) {
1652
        compress_err_count++;
1653
      }
1654
    }
1655
1656
  } while (files_from || ++arg_idx < argc);
1657
1658
1659
#ifdef PARALLEL_PROCESSING
1660
  /* Wait for any child processes to exit... */
1661
  if (max_workers > 1) {
1662
    if (verbose_mode) {
1663
      fprintf(log_fh, "Waiting for %d workers to finish...\n", worker_count);
1664
    }
1665
    while ((pid = wait_for_worker(log_fh)) > 0) {
1666
      if (verbose_mode > 2)
1667
        fprintf(log_fh, "worker[%d] done\n", pid);
1668
    }
1669
  }
1670
#endif
1671
1672
  if (totals_mode && !quiet_mode)
1673
    fprintf(log_fh, "Average ""compression"" (%ld files): %0.2f%% (total saved %0.0fk)\n",
1674
      average_count, (average_count == 0 ? 0.0 : average_rate/average_count), total_save);
1675
1676
1677
  return (decompress_err_count > 0 || compress_err_count > 0 ? 1 : 0);;
1678
}
1679
#else
1680
void fuzz_set_target_size(const int new_target_size)
1681
1
{
1682
1
  target_size = new_target_size;
1683
1
}
1684
#endif /* BUILD_FOR_OSS_FUZZ */
1685
/* eof :-) */