/*******************************************************************
 * JPEGoptim
 * Copyright (c) Timo Kokkonen, 1996-2022.
 * All Rights Reserved.
 *
 * requires libjpeg (Independent JPEG Group's JPEG software
 *                     release 6a or later...)
 *
 * $Id: 352c3829afac35179e9199bee7daca8dbcdb139e $
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <dirent.h>
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_GETOPT_H && HAVE_GETOPT_LONG
#include <getopt.h>
#else
#include "getopt.h"
#endif
#include <signal.h>
#include <string.h>
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>
#include <time.h>
#include <math.h>

#include "jpegoptim.h"


#define VERSIO "1.4.7"
#define COPYRIGHT  "Copyright (C) 1996-2022, Timo Kokkonen"


#define LOG_FH (logs_to_stdout ? stdout : stderr)

#define FREE_LINE_BUF(buf,lines)  {			\
		int j;					\
		for (j=0;j<lines;j++) free(buf[j]);	\
		free(buf);				\
		buf=NULL;				\
	}

#define STRNCPY(dest,src,n) { strncpy(dest,src,n); dest[n-1]=0; }

struct my_error_mgr {
	struct jpeg_error_mgr pub;
	jmp_buf setjmp_buffer;
	int     jump_set;
};
typedef struct my_error_mgr * my_error_ptr;

const char *rcsid = "$Id: 352c3829afac35179e9199bee7daca8dbcdb139e $";


int verbose_mode = 0;
int quiet_mode = 0;
int global_error_counter = 0;
int preserve_mode = 0;
int preserve_perms = 0;
int overwrite_mode = 0;
int totals_mode = 0;
int stdin_mode = 0;
int stdout_mode = 0;
int noaction = 0;
int quality = -1;
int retry = 0;
int dest = 0;
int force = 0;
int save_exif = 1;
int save_iptc = 1;
int save_com = 1;
int save_icc = 1;
int save_xmp = 1;
int save_adobe = 0;
int save_jfxx = 0;
int strip_none = 0;
int threshold = -1;
int csv = 0;
int all_normal = 0;
int all_progressive = 0;
int target_size = 0;
int logs_to_stdout = 1;
#ifdef HAVE_ARITH_CODE
int arith_mode = -1;
#endif
int nofix_mode = 0;
char last_error[JMSG_LENGTH_MAX+1];

struct option long_options[] = {
	{"verbose",0,0,'v'},
	{"help",0,0,'h'},
	{"quiet",0,0,'q'},
	{"max",1,0,'m'},
	{"totals",0,0,'t'},
	{"noaction",0,0,'n'},
	{"dest",1,0,'d'},
	{"force",0,0,'f'},
	{"version",0,0,'V'},
	{"overwrite",0,0,'o'},
	{"preserve",0,0,'p'},
	{"preserve-perms",0,0,'P'},
	{"strip-all",0,0,'s'},
	{"strip-none",0,&strip_none,1},
	{"strip-com",0,&save_com,0},
	{"strip-exif",0,&save_exif,0},
	{"strip-iptc",0,&save_iptc,0},
	{"strip-icc",0,&save_icc,0},
	{"strip-xmp",0,&save_xmp,0},
	{"strip-jfxx",0,&save_jfxx,0},
	{"strip-adobe",0,&save_adobe,0},
	{"keep-com",0,&save_com,1},
	{"keep-exif",0,&save_exif,1},
	{"keep-iptc",0,&save_iptc,1},
	{"keep-icc",0,&save_icc,1},
	{"keep-xmp",0,&save_xmp,1},
	{"keep-jfxx",0,&save_jfxx,1},
	{"keep-adobe",0,&save_adobe,1},
	{"threshold",1,0,'T'},
	{"csv",0,0,'b'},
	{"all-normal",0,&all_normal,1},
	{"all-progressive",0,&all_progressive,1},
	{"size",1,0,'S'},
	{"stdout",0,&stdout_mode,1},
	{"stdin",0,&stdin_mode,1},
#ifdef HAVE_ARITH_CODE
	{"all-arith",0,&arith_mode,1},
	{"all-huffman",0,&arith_mode,0},
#endif
	{"nofix",0,&nofix_mode,1},
	{0,0,0,0}
};


/*****************************************************************/

METHODDEF(void)	my_error_exit (j_common_ptr cinfo)
{
	my_error_ptr myerr = (my_error_ptr)cinfo->err;

	(*cinfo->err->output_message) (cinfo);
	if (myerr->jump_set)
		longjmp(myerr->setjmp_buffer,1);
	else
		fatal("fatal error");
}


METHODDEF(void) my_output_message (j_common_ptr cinfo)
{
	char buffer[JMSG_LENGTH_MAX+1];

	(*cinfo->err->format_message)((j_common_ptr)cinfo,buffer);
	buffer[sizeof(buffer)-1]=0;

	if (verbose_mode)
		fprintf(LOG_FH," (%s) ",buffer);

	global_error_counter++;
	strncpy(last_error,buffer,sizeof(last_error));
}


void print_usage(void)
{
	fprintf(stderr,PROGRAMNAME " v" VERSIO "  " COPYRIGHT "\n");

	fprintf(stderr,
		"Usage: " PROGRAMNAME " [options] <filenames> \n\n"
		"  -d<path>, --dest=<path>\n"
		"                    specify alternative destination directory for \n"
		"                    optimized files (default is to overwrite originals)\n"
		"  -f, --force       force optimization\n"
		"  -h, --help        display this help and exit\n"
		"  -m<quality>, --max=<quality>\n"
		"                    set maximum image quality factor (disables lossless\n"
		"                    optimization mode, which is by default on)\n"
		"                    Valid quality values: 0 - 100\n"
		"  -n, --noaction    don't really optimize files, just print results\n"
		"  -S<size>, --size=<size>\n"
		"                    Try to optimize file to given size (disables lossless\n"
		"                    optimization mode). Target size is specified either in\n"
		"                    kilo bytes (1 - n) or as percentage (1%% - 99%%)\n"
		"  -T<threshold>, --threshold=<threshold>\n"
		"                    keep old file if the gain is below a threshold (%%)\n"
		"  -b, --csv         print progress info in CSV format\n"
		"  -o, --overwrite   overwrite target file even if it exists (meaningful\n"
		"                    only when used with -d, --dest option)\n"
		"  -p, --preserve    preserve file timestamps\n"
		"  -P, --preserve-perms\n"
		"                    preserve original file permissions by overwriting it\n"
		"  -q, --quiet       quiet mode\n"
		"  -t, --totals      print totals after processing all files\n"
		"  -v, --verbose     enable verbose mode (positively chatty)\n"
		"  -V, --version     print program version\n\n"
		"  -s, --strip-all   strip all markers from output file\n"
		"  --strip-none      do not strip any markers\n"
		"  --strip-adobe     strip Adobe (APP14) markers from output file\n"
		"  --strip-com       strip Comment markers from output file\n"
		"  --strip-exif      strip Exif markers from output file\n"
		"  --strip-iptc      strip IPTC/Photoshop (APP13) markers from output file\n"
		"  --strip-icc       strip ICC profile markers from output file\n"
		"  --strip-jfxx      strip JFXX (JFIF Extension) markers from output file\n"
		"  --strip-xmp       strip XMP markers markers from output file\n"
		"\n"
		"  --keep-adobe      preserve any Adobe (APP14) markers\n"
		"  --keep-com        preserve any Comment markers\n"
		"  --keep-exif       preserve any Exif markers\n"
		"  --keep-iptc       preserve any IPTC/Photoshop (APP13) markers\n"
		"  --keep-icc        preserve any ICC profile markers\n"
		"  --keep-jfxx       preserve any JFXX (JFIF Extension) markers\n"
		"  --keep-xmp        preserve any XMP markers markers\n"
		"\n"
		"  --all-normal      force all output files to be non-progressive\n"
		"  --all-progressive force all output files to be progressive\n"
#ifdef HAVE_ARITH_CODE
		"  --all-arith       force all output files to use arithmetic coding\n"
		"  --all-huffman     force all output files to use Huffman coding\n"
#endif
		"  --stdout          send output to standard output (instead of a file)\n"
		"  --stdin           read input from standard input (instead of a file)\n"
		"  --nofix           skip processing of input files if they contain any errors\n"
		"\n\n");
}


void print_version()
{
	struct jpeg_error_mgr jcerr, *err;


#ifdef  __DATE__
	printf(PROGRAMNAME " v%s  %s (%s)\n",VERSIO,HOST_TYPE,__DATE__);
#else
	printf(PROGRAMNAME " v%s  %s\n",VERSIO,HOST_TYPE);
#endif
	printf(COPYRIGHT "\n\n");
	printf("This program comes with ABSOLUTELY NO WARRANTY. This is free software,\n"
		"and you are welcome to redistirbute it under certain conditions.\n"
		"See the GNU General Public License for more details.\n\n");

	if (!(err=jpeg_std_error(&jcerr)))
		fatal("jpeg_std_error() failed");

	printf("\nlibjpeg version: %s\n%s\n",
		err->jpeg_message_table[JMSG_VERSION],
		err->jpeg_message_table[JMSG_COPYRIGHT]);
}


void own_signal_handler(int a)
{
	if (verbose_mode > 1)
		fprintf(stderr,PROGRAMNAME ": signal: %d\n",a);
	exit(1);
}


void write_markers(struct jpeg_decompress_struct *dinfo,
		struct jpeg_compress_struct *cinfo)
{
	jpeg_saved_marker_ptr mrk;
	int write_marker;

	if (!cinfo || !dinfo)
		fatal("invalid call to write_markers()");

	mrk=dinfo->marker_list;
	while (mrk) {
		write_marker=0;

		/* check for markers to save... */

		if (save_com && mrk->marker == JPEG_COM)
			write_marker++;

		if (save_iptc && mrk->marker == IPTC_JPEG_MARKER)
			write_marker++;

		if (save_exif && mrk->marker == EXIF_JPEG_MARKER &&
			mrk->data_length >= EXIF_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, EXIF_IDENT_STRING, EXIF_IDENT_STRING_SIZE))
			write_marker++;

		if (save_icc && mrk->marker == ICC_JPEG_MARKER &&
			mrk->data_length >= ICC_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, ICC_IDENT_STRING, ICC_IDENT_STRING_SIZE))
			write_marker++;

		if (save_xmp && mrk->marker == XMP_JPEG_MARKER &&
			mrk->data_length >= XMP_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, XMP_IDENT_STRING, XMP_IDENT_STRING_SIZE))
			write_marker++;

		if (save_jfxx && mrk->marker == JFXX_JPEG_MARKER &&
			mrk->data_length >= JFXX_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, JFXX_IDENT_STRING, JFXX_IDENT_STRING_SIZE))
			write_marker++;

		if (save_adobe && cinfo->write_Adobe_marker == FALSE &&
			mrk->marker == ADOBE_JPEG_MARKER &&
			mrk->data_length >= ADOBE_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, ADOBE_IDENT_STRING, ADOBE_IDENT_STRING_SIZE)) {
			write_marker++;
		}

		if (strip_none)
			write_marker++;


		/* libjpeg emits some markers automatically so skip these to avoid duplicates... */

		/* skip JFIF (APP0) marker */
		if ( mrk->marker == JFIF_JPEG_MARKER &&
			mrk->data_length >= JFIF_IDENT_STRING_SIZE &&
			!memcmp(mrk->data, JFIF_IDENT_STRING, JFIF_IDENT_STRING_SIZE)) {
			if (verbose_mode > 2)
				fprintf(LOG_FH, " (skip JFIF v%u.%02u marker) ",
					mrk->data[5], mrk->data[6]);
			write_marker=0;
		}


		if (write_marker)
			jpeg_write_marker(cinfo,mrk->marker,mrk->data,mrk->data_length);

		mrk=mrk->next;
	}
}


/*****************************************************************/
int main(int argc, char **argv)
{
	struct jpeg_decompress_struct dinfo;
	struct jpeg_compress_struct cinfo;
	struct my_error_mgr jcerr,jderr;
	JSAMPARRAY buf = NULL;
	jvirt_barray_ptr *coef_arrays = NULL;
	char marker_str[256];
	char tmpfilename[MAXPATHLEN],tmpdir[MAXPATHLEN];
	char newname[MAXPATHLEN], dest_path[MAXPATHLEN];
	volatile int i;
	int c,j, searchcount, searchdone;
	int opt_index = 0;
	long insize = 0, outsize = 0, lastsize = 0;
	long inpos;
	int oldquality;
	double ratio;
	struct stat file_stat;
	jpeg_saved_marker_ptr cmarker;
	unsigned char *outbuffer = NULL;
	size_t outbuffersize = 0;
	unsigned char *inbuffer = NULL;
	size_t inbuffersize = 0;
	size_t inbufferused = 0;
	char *outfname = NULL;
	FILE *infile = NULL, *outfile = NULL;
	int marker_in_count, marker_in_size;
	int compress_err_count = 0;
	int decompress_err_count = 0;
	long average_count = 0;
	double average_rate = 0.0, total_save = 0.0;


	if (rcsid)
		; /* so compiler won't complain about "unused" rcsid string */

	umask(077);
	signal(SIGINT,own_signal_handler);
	signal(SIGTERM,own_signal_handler);

	/* initialize decompression object */
	dinfo.err = jpeg_std_error(&jderr.pub);
	jpeg_create_decompress(&dinfo);
	jderr.pub.error_exit=my_error_exit;
	jderr.pub.output_message=my_output_message;
	jderr.jump_set = 0;

	/* initialize compression object */
	cinfo.err = jpeg_std_error(&jcerr.pub);
	jpeg_create_compress(&cinfo);
	jcerr.pub.error_exit=my_error_exit;
	jcerr.pub.output_message=my_output_message;
	jcerr.jump_set = 0;


	/* parse command line parameters */
	while(1) {
		opt_index=0;
		if ((c = getopt_long(argc,argv,"d:hm:nstqvfVpPoT:S:b",
						long_options, &opt_index)) == -1)
			break;

		switch (c) {
		case 'm':
		{
			int tmpvar;

			if (sscanf(optarg,"%d",&tmpvar) == 1) {
				quality=tmpvar;
				if (quality < 0) quality=0;
				if (quality > 100) quality=100;
			}
			else
				fatal("invalid argument for -m, --max");
		}
		break;
		case 'd':
			if (realpath(optarg,dest_path)==NULL)
				fatal("invalid destination directory: %s", optarg);
			if (!is_directory(dest_path))
				fatal("destination not a directory: %s", dest_path);
			strncat(dest_path,DIR_SEPARATOR_S,sizeof(dest_path)-strlen(dest_path)-1);

			if (verbose_mode)
				fprintf(stderr,"Destination directory: %s\n",dest_path);
			dest=1;
			break;
		case 'v':
			verbose_mode++;
			break;
		case 'h':
			print_usage();
			exit(0);
			break;
		case 'q':
			quiet_mode=1;
			break;
		case 't':
			totals_mode=1;
			break;
		case 'n':
			noaction=1;
			break;
		case 'f':
			force=1;
			break;
		case 'b':
			csv=1;
			quiet_mode=1;
			break;
		case '?':
			exit(1);
		case 'V':
			print_version();
			exit(0);
			break;
		case 'o':
			overwrite_mode=1;
			break;
		case 'p':
			preserve_mode=1;
			break;
		case 'P':
			preserve_perms=1;
			break;
		case 's':
			save_exif=0;
			save_iptc=0;
			save_com=0;
			save_icc=0;
			save_xmp=0;
			save_adobe=0;
			save_jfxx=0;
			break;
		case 'T':
		{
			int tmpvar;
			if (sscanf(optarg,"%d",&tmpvar) == 1) {
				threshold=tmpvar;
				if (threshold < 0) threshold=0;
				if (threshold > 100) threshold=100;
			}
			else fatal("invalid argument for -T, --threshold");
		}
		break;
		case 'S':
		{
			unsigned int tmpvar;
			if (sscanf(optarg,"%u",&tmpvar) == 1) {
				if (tmpvar > 0 && tmpvar < 100 &&
					optarg[strlen(optarg)-1] == '%' ) {
					target_size=-tmpvar;
				} else {
					target_size=tmpvar;
				}
				quality=100;
			}
			else fatal("invalid argument for -S, --size");
		}
		break;
		}
	}


	/* check for '-' option indicating input is from stdin... */
	i=1;
	while (argv[i]) {
		if (argv[i][0]=='-' && argv[i][1]==0)
			stdin_mode=1;
		i++;
	}

	if (stdin_mode)
		stdout_mode=1;
	if (stdout_mode)
		logs_to_stdout=0;

	if (all_normal && all_progressive)
		fatal("cannot specify both --all-normal and --all-progressive");

	if (verbose_mode) {
		if (quality>=0 && target_size==0)
			fprintf(stderr,"Image quality limit set to: %d\n",quality);
		if (threshold>=0)
			fprintf(stderr,"Compression threshold (%%) set to: %d\n",threshold);
		if (all_normal)
			fprintf(stderr,"All output files will be non-progressive\n");
		if (all_progressive)
			fprintf(stderr,"All output files will be progressive\n");
		if (target_size > 0)
			fprintf(stderr,"Target size for output files set to: %d Kbytes.\n",
				target_size);
		if (target_size < 0)
			fprintf(stderr,"Target size for output files set to: %d%%\n",
				-target_size);
	}

	i=(optind > 0 ? optind : 1);
	if (argc <= i) {
		if (!quiet_mode) fprintf(stderr,PROGRAMNAME ": file argument(s) missing\n"
					"Try '" PROGRAMNAME " --help' for more information.\n");
		exit(1);
	}

	/* loop to process the input files */
	do {
		if (stdin_mode) {
			infile=stdin;
			set_filemode_binary(infile);
		} else {
			if (i >= argc || !argv[i][0])
				continue;
			if (strlen(argv[i]) >= MAXPATHLEN) {
				warn("skipping too long filename: %s",argv[i]);
				continue;
			}

			if (!noaction) {
				/* generate tmp dir & new filename */
				if (dest) {
					STRNCPY(tmpdir,dest_path,sizeof(tmpdir));
					STRNCPY(newname,dest_path,sizeof(newname));
					if (!splitname(argv[i],tmpfilename,sizeof(tmpfilename)))
						fatal("splitname() failed for: %s",argv[i]);
					strncat(newname,tmpfilename,sizeof(newname)-strlen(newname)-1);
				} else {
					if (!splitdir(argv[i],tmpdir,sizeof(tmpdir)))
						fatal("splitdir() failed for: %s",argv[i]);
					STRNCPY(newname,argv[i],sizeof(newname));
				}
			}

		retry_point:

			if (file_exists(argv[i])) {
				if (!is_file(argv[i],&file_stat)) {
					if (is_directory(argv[i]))
						warn("skipping directory: %s",argv[i]);
					else
						warn("skipping special file: %s",argv[i]);
					continue;
				}
			} else {
				warn("file not found: %s",argv[i]);
				continue;
			}
			if ((infile = fopen(argv[i],"rb")) == NULL) {
				warn("cannot open file: %s", argv[i]);
				continue;
			}
		}

		if (setjmp(jderr.setjmp_buffer)) {
			/* error handler for decompress */
		abort_decompress:
			jpeg_abort_decompress(&dinfo);
			fclose(infile);
			if (buf) FREE_LINE_BUF(buf,dinfo.output_height);
			if (!quiet_mode || csv)
				fprintf(LOG_FH,csv ? ",,,,,error\n" : " [ERROR]\n");
			decompress_err_count++;
			jderr.jump_set=0;
			continue;
		} else {
			jderr.jump_set=1;
		}

		if (!retry && (!quiet_mode || csv)) {
			fprintf(LOG_FH,csv ? "%s," : "%s ",(stdin_mode?"stdin":argv[i])); fflush(LOG_FH);
		}

		/* prepare to decompress */
		if (stdin_mode) {
			if (inbuffer)
				free(inbuffer);
			inbuffersize=65536;
			inbuffer=malloc(inbuffersize);
			if (!inbuffer)
				fatal("not enough memory");
		}
		global_error_counter=0;
		jpeg_save_markers(&dinfo, JPEG_COM, 0xffff);
		for (j=0;j<=15;j++) {
			jpeg_save_markers(&dinfo, JPEG_APP0+j, 0xffff);
		}
		jpeg_custom_src(&dinfo, infile, &inbuffer, &inbuffersize, &inbufferused, 65536);
		jpeg_read_header(&dinfo, TRUE);

		/* check for Exif/IPTC/ICC/XMP markers */
		marker_str[0]=0;
		marker_in_count=0;
		marker_in_size=0;
		cmarker=dinfo.marker_list;

		while (cmarker) {
			marker_in_count++;
			marker_in_size+=cmarker->data_length;

			if (cmarker->marker == EXIF_JPEG_MARKER &&
				cmarker->data_length >= EXIF_IDENT_STRING_SIZE &&
				!memcmp(cmarker->data,EXIF_IDENT_STRING,EXIF_IDENT_STRING_SIZE))
				strncat(marker_str,"Exif ",sizeof(marker_str)-strlen(marker_str)-1);

			if (cmarker->marker == IPTC_JPEG_MARKER)
				strncat(marker_str,"IPTC ",sizeof(marker_str)-strlen(marker_str)-1);

			if (cmarker->marker == ICC_JPEG_MARKER &&
				cmarker->data_length >= ICC_IDENT_STRING_SIZE &&
				!memcmp(cmarker->data,ICC_IDENT_STRING,ICC_IDENT_STRING_SIZE))
				strncat(marker_str,"ICC ",sizeof(marker_str)-strlen(marker_str)-1);

			if (cmarker->marker == XMP_JPEG_MARKER &&
				cmarker->data_length >= XMP_IDENT_STRING_SIZE &&
				!memcmp(cmarker->data,XMP_IDENT_STRING,XMP_IDENT_STRING_SIZE))
				strncat(marker_str,"XMP ",sizeof(marker_str)-strlen(marker_str)-1);

			if (cmarker->marker == JFXX_JPEG_MARKER &&
				cmarker->data_length >= JFXX_IDENT_STRING_SIZE &&
				!memcmp(cmarker->data, JFXX_IDENT_STRING, JFXX_IDENT_STRING_SIZE))
				strncat(marker_str,"JFXX ",sizeof(marker_str)-strlen(marker_str)-1);

			cmarker=cmarker->next;
		}


		if (verbose_mode > 1) {
			fprintf(LOG_FH,"%d markers found in input file (total size %d bytes)\n",
				marker_in_count,marker_in_size);
			fprintf(LOG_FH,"coding: %s\n", (dinfo.arith_code == TRUE ? "Arithmetic" : "Huffman"));
		}
		if (!retry && (!quiet_mode || csv)) {
			fprintf(LOG_FH,csv ? "%dx%d,%dbit,%c," : "%dx%d %dbit %c ",(int)dinfo.image_width,
				(int)dinfo.image_height,(int)dinfo.num_components*8,
				(dinfo.progressive_mode?'P':'N'));

			if (!csv) {
				fprintf(LOG_FH,"%s",marker_str);
				if (dinfo.saw_Adobe_marker) fprintf(LOG_FH,"Adobe ");
				if (dinfo.saw_JFIF_marker) fprintf(LOG_FH,"JFIF ");
			}
			fflush(LOG_FH);
		}

		if ((insize=file_size(infile)) < 0)
			fatal("failed to stat() input file");

		/* decompress the file */
		if (quality >= 0 && !retry) {
			jpeg_start_decompress(&dinfo);

			/* allocate line buffer to store the decompressed image */
			buf = malloc(sizeof(JSAMPROW)*dinfo.output_height);
			if (!buf) fatal("not enough memory");
			for (j=0;j<dinfo.output_height;j++) {
				buf[j]=malloc(sizeof(JSAMPLE)*dinfo.output_width*
					dinfo.out_color_components);
				if (!buf[j])
					fatal("not enough memory");
			}

			while (dinfo.output_scanline < dinfo.output_height) {
				jpeg_read_scanlines(&dinfo,&buf[dinfo.output_scanline],
						dinfo.output_height-dinfo.output_scanline);
			}
		} else {
			coef_arrays = jpeg_read_coefficients(&dinfo);
		}

		inpos=ftell(infile);
		if (inpos > 0 && inpos < insize) {
			if (!quiet_mode)
				fprintf(LOG_FH, " (Extraneous data found after end of JPEG image) ");
			if (nofix_mode)
				global_error_counter++;
		}

		if (!retry && !quiet_mode) {
			fprintf(LOG_FH,(global_error_counter==0 ? " [OK] " : " [WARNING] "));
			fflush(LOG_FH);
		}

		if (stdin_mode)
			insize = inbufferused;

		if (nofix_mode && global_error_counter != 0) {
			/* skip files containing any errors (warnings) */
			goto abort_decompress;
		}

		if (dest && !noaction) {
			if (file_exists(newname) && !overwrite_mode) {
				if (!quiet_mode)
					fprintf(LOG_FH, " (target file already exists) ");
				goto abort_decompress;
			}
		}


		if (setjmp(jcerr.setjmp_buffer)) {
			/* error handler for compress failures */
			jpeg_abort_compress(&cinfo);
			jpeg_abort_decompress(&dinfo);
			fclose(infile);
			if (!quiet_mode)
				fprintf(LOG_FH," [Compress ERROR: %s]\n",last_error);
			if (buf)
				FREE_LINE_BUF(buf,dinfo.output_height);
			compress_err_count++;
			jcerr.jump_set=0;
			continue;
		} else {
			jcerr.jump_set=1;
		}

		lastsize = 0;
		searchcount = 0;
		searchdone = 0;
		oldquality = 200;
		if (target_size != 0) {
			/* always start with quality 100 if -S option specified... */
			quality = 100;
		}


	binary_search_loop:

		/* allocate memory buffer that should be large enough to store the output JPEG... */
		if (outbuffer)
			free(outbuffer);
		outbuffersize=insize + 32768;
		outbuffer=malloc(outbuffersize);
		if (!outbuffer)
			fatal("not enough memory");

		/* setup custom "destination manager" for libjpeg to write to our buffer */
		jpeg_memory_dest(&cinfo, &outbuffer, &outbuffersize, 65536);


		if (quality >= 0 && !retry) {
			/* lossy "optimization" ... */

			cinfo.in_color_space=dinfo.out_color_space;
			cinfo.input_components=dinfo.output_components;
			cinfo.image_width=dinfo.image_width;
			cinfo.image_height=dinfo.image_height;
			jpeg_set_defaults(&cinfo);
			jpeg_set_quality(&cinfo,quality,TRUE);
			if (all_normal) {
				/* Explicitly disables progressive if libjpeg had it on by default */
				cinfo.scan_info = NULL;
				cinfo.num_scans = 0;
			} else if (dinfo.progressive_mode || all_progressive) {
				jpeg_simple_progression(&cinfo);
			}
			cinfo.optimize_coding = TRUE;
#ifdef HAVE_ARITH_CODE
			if (arith_mode >= 0)
				cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE);
#endif
			if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) {
				/* If outputting Adobe marker, dont write JFIF marker... */
				cinfo.write_JFIF_header = FALSE;
			}

			j=0;
			jpeg_start_compress(&cinfo,TRUE);

			/* write markers */
			write_markers(&dinfo,&cinfo);

			/* write image */
			while (cinfo.next_scanline < cinfo.image_height) {
				jpeg_write_scanlines(&cinfo,&buf[cinfo.next_scanline],
						dinfo.output_height);
			}

		} else {
			/* lossless "optimization" ... */

			jpeg_copy_critical_parameters(&dinfo, &cinfo);
			if (all_normal) {
				/* Explicitly disables progressive if libjpeg had it on by default */
				cinfo.scan_info = NULL;
				cinfo.num_scans = 0;
			} else if ( dinfo.progressive_mode || all_progressive ) {
				jpeg_simple_progression(&cinfo);
			}
			cinfo.optimize_coding = TRUE;
#ifdef HAVE_ARITH_CODE
			if (arith_mode >= 0)
				cinfo.arith_code = (arith_mode > 0 ? TRUE : FALSE);
#endif
			if (dinfo.saw_Adobe_marker && (save_adobe || strip_none)) {
				/* If outputting Adobe marker, dont write JFIF marker... */
				cinfo.write_JFIF_header = FALSE;
			}

			/* write image */
			jpeg_write_coefficients(&cinfo, coef_arrays);

			/* write markers */
			write_markers(&dinfo,&cinfo);

		}

		jpeg_finish_compress(&cinfo);
		outsize=outbuffersize;

		if (target_size != 0 && !retry) {
			/* perform (binary) search to try to reach target file size... */

			long osize = outsize/1024;
			long isize = insize/1024;
			long tsize = target_size;

			if (tsize < 0) {
				tsize=((-target_size)*insize/100)/1024;
				if (tsize < 1)
					tsize = 1;
			}

			if (osize == tsize || searchdone || searchcount >= 8 || tsize > isize) {
				if (searchdone < 42 && lastsize > 0) {
					if (labs(osize-tsize) > labs(lastsize-tsize)) {
						if (verbose_mode) fprintf(LOG_FH,"(revert to %d)",oldquality);
						searchdone=42;
						quality=oldquality;
						goto binary_search_loop;
					}
				}
				if (verbose_mode)
					fprintf(LOG_FH," ");

			} else {
				int newquality;
				int dif = floor((abs(oldquality-quality)/2.0)+0.5);
				if (osize > tsize) {
					newquality=quality-dif;
					if (dif < 1) { newquality--; searchdone=1; }
					if (newquality < 0) { newquality=0; searchdone=2; }
				} else {
					newquality=quality+dif;
					if (dif < 1) { newquality++; searchdone=3; }
					if (newquality > 100) { newquality=100; searchdone=4; }
				}
				oldquality=quality;
				quality=newquality;

				if (verbose_mode)
					fprintf(LOG_FH,"(try %d)",quality);

				lastsize=osize;
				searchcount++;
				goto binary_search_loop;
			}
		}

		if (buf)
			FREE_LINE_BUF(buf,dinfo.output_height);
		jpeg_finish_decompress(&dinfo);
		fclose(infile);


		if (quality >= 0 && outsize >= insize && !retry && !stdin_mode) {
			if (verbose_mode)
				fprintf(LOG_FH,"(retry w/lossless) ");
			retry=1;
			goto retry_point;
		}

		retry=0;
		ratio=(insize-outsize)*100.0/insize;
		if (!quiet_mode || csv)
			fprintf(LOG_FH,csv ? "%ld,%ld,%0.2f," : "%ld --> %ld bytes (%0.2f%%), ",insize,outsize,ratio);
		average_count++;
		average_rate+=(ratio<0 ? 0.0 : ratio);

		if ((outsize < insize && ratio >= threshold) || force) {
			total_save+=(insize-outsize)/1024.0;
			if (!quiet_mode || csv)
				fprintf(LOG_FH,csv ? "optimized\n" : "optimized.\n");
			if (noaction)
				continue;

			if (stdout_mode) {
				outfname=NULL;
				set_filemode_binary(stdout);
				if (fwrite(outbuffer,outbuffersize,1,stdout) != 1)
					fatal("%s, write failed to stdout",(stdin_mode?"stdin":argv[i]));
			} else {
				if (preserve_perms && !dest) {
					/* make backup of the original file */
					int newlen = snprintf(tmpfilename,sizeof(tmpfilename),"%s.jpegoptim.bak",newname);
					if (newlen >= sizeof(tmpfilename))
						warn("temp filename too long: %s", tmpfilename);

					if (verbose_mode > 1 && !quiet_mode)
						fprintf(LOG_FH,"%s, creating backup as: %s\n",(stdin_mode?"stdin":argv[i]),tmpfilename);
					if (file_exists(tmpfilename))
						fatal("%s, backup file already exists: %s",(stdin_mode?"stdin":argv[i]),tmpfilename);
					if (copy_file(newname,tmpfilename))
						fatal("%s, failed to create backup: %s",(stdin_mode?"stdin":argv[i]),tmpfilename);
					if ((outfile=fopen(newname,"wb"))==NULL)
						fatal("%s, error opening output file: %s",(stdin_mode?"stdin":argv[i]),newname);
					outfname=newname;
				} else {
#ifdef HAVE_MKSTEMPS
					/* rely on mkstemps() to create us temporary file safely... */
					int newlen = snprintf(tmpfilename,sizeof(tmpfilename),
							"%sjpegoptim-%d-%d.XXXXXX.tmp", tmpdir, (int)getuid(), (int)getpid());
					if (newlen >= sizeof(tmpfilename))
						warn("temp filename too long: %s", tmpfilename);
					int tmpfd = mkstemps(tmpfilename,4);
					if (tmpfd < 0)
						fatal("%s, error creating temp file %s: mkstemps() failed",(stdin_mode?"stdin":argv[i]),tmpfilename);
					if ((outfile = fdopen(tmpfd,"wb")) == NULL)
#else
						/* if platform is missing mkstemps(), try to create at least somewhat "safe" temp file... */
						snprintf(tmpfilename,sizeof(tmpfilename),
							"%sjpegoptim-%d-%d.%ld.tmp", tmpdir, (int)getuid(), (int)getpid(),(long)time(NULL));
					if ((outfile = fopen(tmpfilename,"wb")) == NULL)
#endif
						fatal("error opening temporary file: %s",tmpfilename);
					outfname=tmpfilename;
				}

				if (verbose_mode > 1 && !quiet_mode)
					fprintf(LOG_FH,"writing %lu bytes to file: %s\n",
						(long unsigned int)outbuffersize, outfname);
				if (fwrite(outbuffer,outbuffersize,1,outfile) != 1)
					fatal("write failed to file: %s", outfname);
				fclose(outfile);
			}

			if (outfname) {

				if (preserve_mode) {
					/* preserve file modification time */
					if (verbose_mode > 1 && !quiet_mode)
						fprintf(LOG_FH,"set file modification time same as in original: %s\n", outfname);
#if defined(HAVE_UTIMENSAT) && defined(HAVE_STRUCT_STAT_ST_MTIM)
					struct timespec time_save[2];
					time_save[0].tv_sec = 0;
					time_save[0].tv_nsec = UTIME_OMIT;	/* omit atime */
					time_save[1] = file_stat.st_mtim;
					if (utimensat(AT_FDCWD,outfname,time_save,0) != 0)
						warn("failed to reset output file time/date");
#else
					struct utimbuf time_save;
					time_save.actime=file_stat.st_atime;
					time_save.modtime=file_stat.st_mtime;
					if (utime(outfname,&time_save) != 0)
						warn("failed to reset output file time/date");
#endif
				}

				if (preserve_perms && !dest) {
					/* original file was already replaced, remove backup... */
					if (verbose_mode > 1 && !quiet_mode)
						fprintf(LOG_FH,"removing backup file: %s\n", tmpfilename);
					if (delete_file(tmpfilename))
						warn("failed to remove backup file: %s",tmpfilename);
				} else {
					/* make temp file to be the original file... */

					/* preserve file mode */
					if (chmod(outfname,(file_stat.st_mode & 0777)) != 0)
						warn("failed to set output file mode");

					/* preserve file group (and owner if run by root) */
					if (chown(outfname,
							(geteuid()==0 ? file_stat.st_uid : -1),
							file_stat.st_gid) != 0)
						warn("failed to reset output file group/owner");

					if (verbose_mode > 1 && !quiet_mode)
						fprintf(LOG_FH,"renaming: %s to %s\n",outfname,newname);
					if (rename_file(outfname,newname))
						fatal("cannot rename temp file");
				}
			}
		} else {
			if (!quiet_mode || csv)
				fprintf(LOG_FH,csv ? "skipped\n" : "skipped.\n");
			if (stdout_mode) {
				set_filemode_binary(stdout);
				if (fwrite(inbuffer,insize,1,stdout) != 1)
					fatal("%s, write failed to stdout",(stdin_mode?"stdin":argv[i]));
			}
		}


	} while (++i<argc && !stdin_mode);


	if (totals_mode && !quiet_mode)
		fprintf(LOG_FH,"Average ""compression"" (%ld files): %0.2f%% (%0.0fk)\n",
			average_count, average_rate/average_count, total_save);
	jpeg_destroy_decompress(&dinfo);
	jpeg_destroy_compress(&cinfo);
	if (outbuffer)
		free(outbuffer);
	if (inbuffer)
		free(inbuffer);

	return (decompress_err_count > 0 || compress_err_count > 0 ? 1 : 0);;
}

/* eof :-) */
