/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 :-) */ |