Coverage Report

Created: 2025-07-23 06:22

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