/**************************************************************************************************
	$Header: /pub/cvsroot/yencode/src/ydecode.c,v 1.27 2002/03/16 05:29:14 bboy Exp $

	Copyright (C) 2002  Don Moore <bboy@bboy.net>

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at Your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**************************************************************************************************/

#include "y.h"
#include "file.h"


int		opt_verbose = 1;							/* Should the program be verbose in its operation? */
int		opt_debug = 0;								/* Debug output? */
int		opt_overwrite = 0;						/* Overwrite existing files? */
char		*opt_output_dir = NULL;					/* Write output to this directory */
int		opt_recursive = 0;						/* Recursive scan? */
int		opt_scan = 0;								/* Scan files? */
int		opt_keep = 0;								/* Keep output files with errors? */
int		opt_keep_paths = 0;						/* Keep paths in filenames? */
int		opt_list = 0;								/* Only list files found? */
int		opt_test = 0;								/* Test files? */
int		opt_strict = 0;							/* Strict test? */
int		opt_delete = 0;							/* Delete files after decoding? */

char		**dirs = (char **)NULL;					/* List of directories to process */
int		num_dirs = 0;								/* Number of directories in `dirs' list */

YDECFILE	**yfiles = (YDECFILE **)NULL;			/* List of files to process */
int		num_yfiles = 0;							/* Number of items in yfiles */

/* Macro for passing filenames to usermsg() */
#define YNAMES(y)	(y && y->output_filename) ? y->output_filename : _("<UNKNOWN>"), \
						(y && y->input_filename) ? y->input_filename : _("<UNKNOWN>")


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	USAGE
	Display program usage information.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void
usage(int status)
{
	if (status != EXIT_SUCCESS)
	{
		fprintf(stderr, _("Try `%s --help' for more information."), progname);
		fputs("\n", stderr);
	}
	else
	{
		printf(_("Usage: %s [OPTION]... [FILE]..."), progname);
		puts("");
      puts(_("Usenet file decoder."));
		puts("");
//		puts("----------------------------------------------------------------------------78");
		puts(_("  -d, --debug       output extra debugging information while running"));
		puts(_("  -D, --delete      delete input files if decoded successfully"));
		puts(_("  -f, --force       overwrite existing files, never prompt"));
		puts(_("  -k, --keep        keep output files that contain errors"));
		puts(_("  -l, --list        find and list input file information only"));
		puts(_("  -o, --output=DIR  create output in DIR instead of the current dir"));
		puts(_("  -p, --paths       maintain paths in output filenames"));
		puts(_("  -q, --quiet       inhibit all messages written to the standard output"));
		puts(_("  -r, --recursive   scan directories recursively"));
		puts(_("  -s, --scan        scan files if any parts are missing"));
		puts(_("  -t, --test        do not write output files (just test archives)"));
		puts(_("      --strict      perform strict format checks when testing/decoding?"));
		puts(_("      --verify      synonym for `--test --strict'"));
		puts(_("      --help        display this help and exit"));
		puts(_("      --version     output version information and exit"));
		puts("");
      puts(_("Report bugs to bugs@yencode.org."));
	}
	exit(status);
}
/*--- usage() -----------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	QUEUE_FILE
	Adds a file to the file or dir list.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
queue_file(char *filename, int *file_errs)
{
	struct stat st;

	if ((stat(filename, &st)))
	{
		*file_errs += 1;
		return (void)WarnERR("%s", filename);
	}

	/* Add directory to input_dirs */
	if (S_ISDIR(st.st_mode))
	{
		dirs = (char **)xrealloc(dirs, (num_dirs + 1) * sizeof(char *));

		while (filename[strlen(filename)-1] == '/')		/* Strip trailing slashes */
			filename[strlen(filename)-1] = '\0';

		dirs[num_dirs++] = filename;
	}

	/* Add file to yfiles */
	if (S_ISREG(st.st_mode))
	{
		YDECFILE *y = ydecfile_create(filename, opt_strict);

		/* Don't treat non-yEnc files as errors.. Just ignore them */
		if (!y)
			return;

		yfiles = (YDECFILE **)xrealloc(yfiles, (num_yfiles + 1) * sizeof(YDECFILE *));
		yfiles[num_yfiles++] = y;
	}

	/* Ignore unsupported file types */
}
/*--- queue_file() ------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	CMDLINE
	Process command line options.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
cmdline(int argc, char **argv)
{
	int  optc, optindex;
	char *optstr;
	struct option const longopts[] =
	{
		{"debug",			no_argument,			NULL,	'd'},
		{"delete",			no_argument,			NULL,	'D'},
		{"force",			no_argument,			NULL,	'f'},
		{"keep",				no_argument,			NULL,	'k'},
		{"list",				no_argument,			NULL,	'l'},
		{"output",			required_argument,	NULL,	'o'},
		{"paths",			no_argument,			NULL,	'p'},
		{"quiet",			no_argument,			NULL,	'q'},
		{"recursive",		no_argument,			NULL,	'r'},
		{"recurse",			no_argument,			NULL,	'R'},
		{"scan",				no_argument,			NULL,	's'},
		{"test",				no_argument,			NULL,	't'},
		{"strict",			no_argument,			0,		0},
		{"verify",			no_argument,			0,		0},
		{"help",				no_argument,			0,		0},
		{"version",			no_argument,			0,		0},

		{NULL, 0, NULL, 0}
	};
	int	file_errs = 0;

	opt_verbose = 1;
	optstr = getoptstr(longopts);
	while ((optc = getopt_long(argc, argv, optstr, longopts, &optindex)) != -1)
	{
		switch (optc)
		{
			case 0:
				{
					const char *opt = longopts[optindex].name;

					if (!strcmp(opt, "version"))									// --version
					{
						printf("%s - " PACKAGE " " VERSION "\n", short_progname);
						exit(EXIT_SUCCESS);
					}
					else if (!strcmp(opt, "help"))								// --help
						usage(EXIT_SUCCESS);
					else if (!strcmp(opt, "strict"))								// --strict
						opt_strict = 1;
					else if (!strcmp(opt, "verify"))								// --verify
						opt_strict = opt_test = 1;
				}
				break;

			case 'd':																	// -d, --debug
				opt_debug = opt_verbose = 1;
				break;

			case 'D':																	// -D, --delete
				opt_delete = 1;
				break;

			case 'f':																	// -f, --force
				opt_overwrite = 1;
				break;

			case 'k':																	// -k, --keep
				opt_keep = 1;
				break;

			case 'l':																	// -l, --list
				opt_list = 1;
				break;

			case 'o':																	// -o, --output=DIR
				{
					struct stat st;
					if (stat(optarg, &st))
						ErrERR("%s", optarg);
					if (!S_ISDIR(st.st_mode))
						Err("%s: %s", optarg, _("not a directory"));
					opt_output_dir = optarg;
					while (opt_output_dir[strlen(opt_output_dir)-1] == '/')
						opt_output_dir[strlen(opt_output_dir)-1] = '\0';
				}
				break;

			case 'p':																	// -p, --paths
				opt_keep_paths = 1;
				break;

			case 'q':																	// -q, --quiet
				opt_debug = opt_verbose = 0;
				break;

			case 'R':
			case 'r':																	// -r, --recursive
				opt_scan = opt_recursive = 1;
				break;

			case 's':																	// -s, --scan
				opt_scan = 1;
				break;

			case 't':																	// -t, --test
				opt_test = 1;
				break;

			default:
				usage(EXIT_FAILURE);
		}
	}

	/* Set these options for the routines in "error.c" in the library */
	err_debug = opt_debug;
	err_verbose = opt_verbose;

	while (optind < argc)
		queue_file(argv[optind++], &file_errs);
	if (file_errs)
		exit(EXIT_FAILURE);

	/* If nothing was specified, scan by default */
	if (!opt_scan && !opt_recursive && !num_yfiles)
		opt_scan = 1;
}
/*--- cmdline() ---------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SCAN_FILES
	Scans the current directory, the output directory, and any directories recursively.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
scan_files(const char *path)
{
	DIR				*dirf;
	struct dirent	*dn;
	struct stat		st;
	char				filename[PATH_MAX];

	if (!(dirf = opendir(path ? path : ".")))
		return;
	while ((dn = readdir(dirf)))
	{
		if (dn->d_name[0] == '.' && dn->d_name[1] == '\0')
			continue;
		if (dn->d_name[0] == '.' && dn->d_name[1] == '.' && dn->d_name[2] == '\0')
			continue;
		if (!path)
			strncpy(filename, dn->d_name, sizeof(filename)-1);
		else
			snprintf(filename, sizeof(filename), "%s/%s", path, dn->d_name);
		if (stat(filename, &st))
			continue;
		if (S_ISDIR(st.st_mode) && opt_recursive)
			scan_files(filename);
		else if (!S_ISDIR(st.st_mode))
		{
			YDECFILE *y = ydecfile_create(filename, opt_strict);
			if (y)
			{
				yfiles = (YDECFILE **)xrealloc(yfiles, (num_yfiles + 1) * sizeof(YDECFILE *));
				yfiles[num_yfiles++] = y;
			}
		}
	}
	closedir(dirf);
}
/*--- scan_files() ------------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SCAN_FOR_FILES
	Scans directories if necessary.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
scan_for_files(void)
{
	/* Do general recursive scan if no dirs were specified on the command line */
	if (!num_dirs)
		scan_files(NULL);
	else															/* Scan specified dirs */
	{
		register int ct;

		for (ct = 0; ct < num_dirs; ct++)
			scan_files(dirs[ct]);
	}
}
/*--- scan_for_files() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	FLAG_MULTIPART_FILE
	Attempts to determine which of the input files (if any) are multipart archives.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
flag_multipart_file(YDECFILE *y)
{
	register int ct;

	/* If the part number is 0 (i.e. part not present or '0' explicitly specified) consider
		it a single part archive always */
	if (y->header->part == 0)
		return;

	/* If we found another file with the same name, it's a multipart */
	for (ct = 0; ct < num_yfiles; ct++)
	{
		if (!strcmp(y->header->name, yfiles[ct]->header->name))
		{
			y->multipart = 1;
			return;
		}
	}
}
/*--- flag_multipart_file() ---------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	SET_OUTPUT_FILENAME
	Sets output_filename for each item in the list of files.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
set_output_filename(YDECFILE *y)
{
	char	buf[PATH_MAX];											/* Copy of input filename for mangling */
	char	*path, *file;											/* Path and file components */
	char	outfile[PATH_MAX];									/* Output filename buffer */

	strncpy(buf, y->header->name, sizeof(buf)-1);
	outfile[0] = '\0';

	/* Start with opt_output_dir if present */
	if (opt_output_dir)
	{
		strncat(outfile, opt_output_dir, sizeof(outfile) - strlen(outfile) - 1);
		if (outfile[strlen(outfile)-1] != '/')
			strncat(outfile, "/", sizeof(outfile) - strlen(outfile) - 1);
	}

	/* Filename with path */
	if ((file = strrchr(buf, '/')))
	{
		*file++ = '\0';
		path = buf;

		if (opt_output_dir && *path == '/')					/* Make absolute path relative */
			path++;

		if (opt_keep_paths)
		{
			strncat(outfile, path, sizeof(outfile) - strlen(outfile) - 1);
			if (outfile[strlen(outfile)-1] != '/')
				strncat(outfile, "/", sizeof(outfile) - strlen(outfile) - 1);
		}
	}
	/* Filename without a path */
	else
	{
		path = NULL;
		file = buf;
	}

	strncat(outfile, file, sizeof(outfile) - strlen(outfile) - 1);
	y->output_filename = xstrdup(outfile);
}
/*--- set_output_filename() ---------------------------------------------------------------------*/


/* Macro to "output" a byte using the output buffer */
#define OUT(c)																					\
	outbuf[ob] = (unsigned char)c; 														\
	if (++ob == BUFSIZ) 																		\
	{ 																								\
		if (out && (fwrite(outbuf, sizeof(unsigned char), ob, out) != ob))	\
			ErrERR("%s", y->output_filename);											\
		ob = 0;																					\
	}

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	YDECODE_DATA
	Reads data from `in', decodes the data, and outputs to `out'.
	Returns 0 on success, -1 on error.
	Optionally sets pcrc, crc, encsize (encoded size), and decsize (decoded size).
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
ydecode_data(YDECFILE *y, FILE *in, FILE *out, crc32_t *crc32p, crc32_t *pcrc32p,
				 size_t *elen, size_t *dlen)
{
	unsigned char inbuf[BUFSIZ], outbuf[BUFSIZ];			/* Input buffer */
	register unsigned char *b;									/* Current location in `buf' */
	register int ob;												/* Current offset in `outbuf' */
	register int linect;
	register unsigned long lineno;
	register int last_line_length, char_escaped;

	ob = linect = last_line_length = char_escaped = 0;
	lineno = 0;

	while (fgets(inbuf, sizeof(inbuf), in))
	{
		if (YKEYWORD_END(inbuf))
			break;

		/* If strict checking is enabled, warn if we found a line of invalid length */
		if (opt_strict && last_line_length && y->header && (last_line_length != *y->header->line))
			if (!char_escaped || (char_escaped && (last_line_length - 1 != *y->header->line)))
				Notice(_("%s:%lu: invalid line length %d (should be %lu)"),
						  y->input_filename, lineno + y->line_offset, last_line_length, *y->header->line);

		lineno++;

		for (b = inbuf; *b; b++)
		{
			/* If strict checking is enabled, warn about invalid first and/or last characters in line */
			if (opt_strict)
			{
				if (linect == 0)
				{
					if (*b == ' ')
						Verbose("%s:%lu:%d: %s", y->input_filename, lineno + y->line_offset, linect+1,
								  _("line begins with an unescaped space"));
					else if (*b == '\t')
						Verbose("%s:%lu:%d: %s", y->input_filename, lineno + y->line_offset, linect+1,
								  _("line begins with an unescaped TAB"));
					else if (*b == '.' && *(b+1) != '.')
						Verbose("%s:%lu:%d: %s", y->input_filename, lineno + y->line_offset, linect+1,
								  _("line begins with an unescaped single dot"));
				}
				if (*(b+1) == '\r' || *(b+1) == '\n')
				{
					if (*b == ' ')
						Verbose("%s:%lu:%d: %s", y->input_filename, lineno + y->line_offset, linect+1,
								  _("line ends with an unescaped space"));
					else if (*b == '\t')
						Verbose("%s:%lu:%d: %s", y->input_filename, lineno + y->line_offset, linect+1,
								  _("line ends with an unescaped TAB"));
				}
			}

			/* If this is the beginning of a line, and it's a plain dot, and the next character is also a dot,
				move one character forward. */
			if (linect == 0 && *b == '.' && *(b+1) == '.')
				b++;

			/* For all relevant characters, set char_escaped if it was escaped (for strict checking line length) */
			if (*b != '\n' && *b != '\r')
				char_escaped = (*b == '=');

			switch (*b)
			{
				case '\r':
					continue;

				case '\n':
					last_line_length = linect;
					linect = 0;
					continue;

				case '=':
					if (elen) *elen += 1;
					b++; linect++;
					*b = YUNESCAPE(*b);
					if (opt_strict && !YESCAPE_MAKES_SENSE(*b))
						Verbose("%s:%lu:%d: %s: %02X", y->input_filename, lineno + y->line_offset, linect + 1,
								  _("byte escaped for no good reason"), *b);
					/* FALLTHROUGH */

				default:
					*b = YDECODE(*b);
					if (elen) *elen += 1;
					if (dlen) *dlen += 1;
					if (crc32p)  CRC_UPDATE(*crc32p, *b);
					if (pcrc32p) CRC_UPDATE(*pcrc32p, *b);
					OUT(*b);
					linect++;
					break;
			}
		}
	}
	if (ob && out && (fwrite(outbuf, sizeof(unsigned char), ob, out) != ob))
		ErrERR("%s", y->output_filename);
	if (ferror(in))
		ErrERR("%s", y->input_filename);
	if (out && ferror(out))
		ErrERR("%s", y->output_filename);

	return (0);
}
/*--- ydecode_data() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	YDECODE_SINGLE
	Decodes the specified single part file.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
ydecode_single(YDECFILE *y)
{
	FILE		*in, *out;											/* Input and output file pointers */
	crc32_t	crc32;												/* CRC value */
	int		errors;												/* Number of errors found */
	char		errmsg[PATH_MAX];									/* Error message to insert when renaming */
	size_t	encodedsize, decodedsize;						/* Length of encoded and decoded data */

	encodedsize = decodedsize = (size_t)0;
	errors = 0;
	errmsg[0] = '\0';

	if (!(in = fopen(y->input_filename, "r")))
		ErrERR("%s", y->input_filename);
	if (fseek(in, y->data_start, SEEK_SET))				/* Move input fp to data start */
		ErrERR("%s", y->input_filename);

	if (opt_test)
		out = NULL;
	else if (!(out = open_output_file(y->output_filename, opt_overwrite, y->input_filename)))
		return (-1);

	/* Decode the data */
	CRC_START(crc32);
	ydecode_data(y, in, out, &crc32, NULL, &encodedsize, &decodedsize);
	CRC_FINISH(crc32);

	/* Clean up */
	fclose(in);
	if (out)
		fclose(out);

	/* Check for errors */
	if (y->header->size && (*y->header->size != decodedsize))
	{
		usermsg(YNAMES(y), 1, 1, _("file size mismatch"),
				  "(%s=%s %s=%s)",
				  _("ybegin"), comma1(*y->header->size),
				  _("actual"), comma2(decodedsize));
		errors++;
		snprintf(errmsg, sizeof(errmsg), "size-%u", decodedsize);
	}
	if (y->footer->crc32 && (*y->footer->crc32 != crc32))
	{
		usermsg(YNAMES(y), 1, 1, _("CRC mismatch"),
				  "(%s=%08x %s=%08x)",
				  _("yend"), *y->footer->crc32,
				  _("actual"), crc32);
		errors++;
		snprintf(errmsg, sizeof(errmsg), "crc-%08x", crc32);
	}
	else if (opt_debug && y->footer->crc32 && (*y->footer->crc32 == crc32))
		usermsg(YNAMES(y), 1, 1, _("CRC OK"),
				  "(%s=%08x %s=%08x)",
				  _("yend"), *y->footer->crc32,
				  _("actual"), crc32);
	else if (opt_debug && !y->footer->crc32)
		usermsg(YNAMES(y), 1, 1, _("no CRC for file"), NULL);

	if (errors && !opt_keep && !opt_test)
	{
		if (!unlink(y->output_filename))
			usermsg(y->output_filename, NULL, 0, 0, _("errors found, output file removed"), NULL);
		else
			usermsg(y->output_filename, NULL, 0, 0, _("unable to remove output file"), strerror(errno));
	}
	else if (!errors && opt_verbose)
	{
		if (opt_delete && !opt_test)
		{
			if (unlink(y->input_filename))
				usermsg(y->output_filename, NULL, 1, 1, _("file OK"), "(%.2f%%) (%s: %s)",
						  PCT(decodedsize,encodedsize), _("input file not deleted"), strerror(errno));
			else
				usermsg(y->output_filename, NULL, 1, 1, _("file OK"), "(%.2f%%) (%s)",
						  PCT(decodedsize,encodedsize), _("input file deleted"));
		}
		else
			usermsg(y->output_filename, NULL, 1, 1, _("file OK"), "(%.2f%%)",
					  PCT(decodedsize,encodedsize));
	}
	else if (errors && *errmsg && !opt_test)
	{
		char *newname = rename_output_file(y->output_filename, errmsg);

		if (newname)
			usermsg(y->output_filename, NULL, 0, 0, NULL, _("file renamed to `%s'"), newname);
		else
			usermsg(y->output_filename, NULL, 0, 0, _("unable to rename output file"), strerror(errno));
	}

	return (0);
}
/*--- ydecode_single() --------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	VERIFY_MULTI_FILE
	Verifies a single file of a multipart archive.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
verify_multi_file(int part, int total, int first, int last, unsigned long *begin, int *errors)
{
	YDECFILE	*y = NULL;												/* File currently being processed */
	int	ct;														/* Current position in yfiles */

	/* Find the yfile containing this part number */
	for (ct = first; ct < last && !y; ct++)
		if (yfiles[ct]->header->part && (*yfiles[ct]->header->part == part))
			y = yfiles[ct];

	/* Part number not found - try to find it based on begin offset */
	if (!y)
	{
		for (ct = first; ct < last && !y; ct++)
			if (yfiles[ct]->part && yfiles[ct]->part->begin && (*yfiles[ct]->part->begin == *begin))
			{
				usermsg(YNAMES(yfiles[ct]), 0, 0, NULL,
						  _("file designated as part %d really begins where part %d should"),
						  *yfiles[ct]->header->part, part);
				y = yfiles[ct];
				*errors += 1;
			}
	}

	/* Part number STILL not found - part is missing */
	if (!y)
	{
		usermsg(yfiles[first]->output_filename, NULL, part, total, _("part missing"), NULL);
		*errors += 1;
		return;
	}

	/* Make sure a `=ypart' was present */
	if (!y->part)
	{
		usermsg(YNAMES(y), part, total, _("missing `=ypart' header"), NULL);
		*errors += 1;
		return;
	}

	/* Make sure this part begins where we think it should */
	if (y->part->begin && (*y->part->begin != *begin))
	{
		usermsg(YNAMES(y), part, total, _("incorrect beginning offset"),
				  "(%s: %s %s: %s)",
				  _("want"), comma1(*begin),
				  _("got"), comma2(*y->part->begin));
		*errors += 1;
	}

	/* Make sure size of part was specified */
	if (!y->footer->size)
	{
		usermsg(YNAMES(y), part, total, _("size of part not specified in `=yend'"), NULL);
		*errors += 1;
		return;
	}

	/* Advance begin pointer for next file */
	*begin += *y->footer->size;

	return;
}
/*--- verify_multi_file() -----------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	VERIFY_MULTI
	Examines all parts of the current archive.  Attempts to determine the total number of parts,
	and if all parts of this archive are present.
	Returns 0 if everything looks OK, or the number of errors found.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
verify_multi(int first, int last, int *total)
{
	int	ct;														/* Current position in yfiles */
	int	errors = 0;												/* Number of errors found */
	int	part = 0, highpart = 0;								/* Counters for calculating total parts */
	unsigned long	next_begin;												/* Next expected `begin' value */

	/* Look for a total part number specified in any part of the archive.  Save the highest part
		number found and use it if no other total is known */
	for (*total = 0, ct = first; ct < last; ct++)
	{
		if (yfiles[ct]->header->total)
			*total = *yfiles[ct]->header->total;
		if (yfiles[ct]->header->part && (*yfiles[ct]->header->part > highpart))
			highpart = *yfiles[ct]->header->part;
	}
	if (*total)
		Debug(_("%s: %d parts total"), yfiles[first]->output_filename, *total);
	else
	{
		*total = highpart;
		Debug(_("%s: %d parts total (estimated)"), yfiles[first]->output_filename, *total);
	}

	/* Now examine the file list one at a time, making sure each part is present and with the
		correct `begin' offset */
	for (next_begin = 1, part = 1; part <= *total; part++)
	{
		Debug(_("%s: verifying part %d of %d"), yfiles[first]->output_filename, part, *total);
		verify_multi_file(part, *total, first, last, &next_begin, &errors);
	}

	return (errors);
}
/*--- verify_multi() ----------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	YDECODE_MULTI
	Decodes the specified multipart file beginning at the record specified by `pos' in yfiles.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int
ydecode_multi(int *first_pos)
{
	int		first, last;										/* Offset of first and last file in group */
	YDECFILE	*y = yfiles[*first_pos];						/* File currently being processed */
	FILE		*in, *out = NULL;									/* Input and output file pointers */
	int		errors = 0;											/* Number of errors found */
	int		ct;													/* Current position in yfiles */
	crc32_t	pcrc32, crc32;										/* CRC of current part and total file */
	size_t	encodedsize = 0, decodedsize = 0;			/* Length of encoded and decoded data */
	size_t	encpart, decpart;									/* Length of data, this file */
	int		verify_errors;										/* Number of errors found in verification */
	int		total;												/* Total number of parts */
	char		errmsg[PATH_MAX];									/* Error message to insert when renaming */

	errmsg[0] = '\0';

	/* Get offset in `yfiles' for last file in this file group */
	first = *first_pos;
	for (last = first;
		  last < num_yfiles && !strcmp(yfiles[last]->header->name, yfiles[first]->header->name);
	     last++)
		/* DONOTHING */;
	*first_pos = last - 1;

	/* Verify consistency of header data for all parts before decoding to save time */
	if ((verify_errors = verify_multi(first, last, &total)) && !opt_keep)
	{
		usermsg(yfiles[first]->output_filename, NULL, 0, 0, _("errors found, nothing done"), NULL);
		return (-1);
	}

	if (opt_test)
		out = NULL;
	else if (!(out = open_output_file(yfiles[first]->output_filename, opt_overwrite,
				  								 yfiles[first]->input_filename)))
		return (-1);

	/* Process each file in part */
	CRC_START(crc32);
	for (ct = first; ct < last; ct++)
	{
		y = yfiles[ct];

		if (!(in = fopen(y->input_filename, "r")))
			ErrERR("%s", y->input_filename);
		if (fseek(in, y->data_start, SEEK_SET))			/* Move input fp to data start */
			ErrERR("%s", y->input_filename);
		encpart = decpart = 0;

		/*
		**  Decode the file
		*/
		CRC_START(pcrc32);
		ydecode_data(y, in, out, &crc32, &pcrc32, &encpart, &decpart);
		CRC_FINISH(pcrc32);
		encodedsize += encpart;
		decodedsize += decpart;
		fclose(in);

		if (y->footer->size && (*y->footer->size != decpart))
		{
			usermsg(YNAMES(y), *y->header->part, total, _("file size mismatch"),
					  "(%s=%s %s=%s)",
					  _("yend"), comma1(*y->footer->size),
					  _("actual"), comma2(decpart));
			snprintf(errmsg, sizeof(errmsg), "psize-%u", decpart);
			errors++;
		}
		if (y->footer->pcrc32 && (*y->footer->pcrc32 != pcrc32))
		{
			usermsg(YNAMES(y), *y->header->part, total, _("CRC mismatch"),
					  "(%s=%08x %s=%08x)",
					  _("yend"), *y->footer->pcrc32,
					  _("actual"), pcrc32);
			snprintf(errmsg, sizeof(errmsg), "pcrc-%08x", pcrc32);
			errors++;
		}
		else if (opt_debug && y->footer->pcrc32 && (*y->footer->pcrc32 == pcrc32))
			usermsg(YNAMES(y), *y->header->part, total, _("CRC OK"),
					  "(%s=%08x %s=%08x)",
					  _("yend"), *y->footer->pcrc32,
					  _("actual"), pcrc32);
		else if (opt_debug && !y->footer->pcrc32)
			usermsg(YNAMES(y), *y->header->part, total, _("no CRC for part"), NULL);
		else if (opt_debug)
			usermsg(YNAMES(y), *y->header->part, total, _("file OK"), "(%.2f%%)", PCT(decpart, encpart));
	}
	CRC_FINISH(crc32);

	if (out)
		fclose(out);

	if (y->header->size && (*y->header->size != decodedsize))
	{
		usermsg(y->output_filename, NULL, 0, 0, _("file size mismatch"),
				  "(%s=%s %s=%s)",
				  _("ybegin"), comma1(*y->header->size),
				  _("actual"), comma2(decodedsize));
		snprintf(errmsg, sizeof(errmsg), "size-%u", decodedsize);
		errors++;
	}
	if (y->footer->crc32 && (*y->footer->crc32 != crc32))
	{
		usermsg(y->output_filename, NULL, 0, 0, _("CRC mismatch"),
				  "(%s=%08x %s=%08x)",
				  _("yend"), *y->footer->crc32,
				  _("actual"), crc32);
		snprintf(errmsg, sizeof(errmsg), "crc-%08x", crc32);
		errors++;
	}
	else if (opt_debug && y->footer->crc32 && (*y->footer->crc32 == crc32))
		usermsg(y->output_filename, NULL, 0, 0, _("CRC OK"),
				  "(%s=%08x %s=%08x)",
				  _("yend"), *y->footer->crc32,
				  _("actual"), crc32);
	else if (opt_debug && !y->footer->crc32)
		usermsg(y->output_filename, NULL, 0, 0, _("no CRC for file"), NULL);

	/* Delete output file if errors occurred, output summary */
	if (errors && !opt_keep && !opt_test)
	{
		if (!unlink(y->output_filename))
			usermsg(y->output_filename, NULL, 0, 0, _("errors found, output file removed"), NULL);
		else
			usermsg(y->output_filename, NULL, 0, 0, _("unable to remove output file"), strerror(errno));
	}
	else if (errors && *errmsg && !opt_test)
	{
		char *newname = rename_output_file(y->output_filename, errmsg);

		if (newname)
			usermsg(y->output_filename, NULL, 0, 0, NULL, _("file renamed to `%s'"), newname);
		else
			usermsg(y->output_filename, NULL, 0, 0, _("unable to rename output file"), strerror(errno));
	}
	if (errors)
		return (-1);

	/* Anything below here is working with a successfully decoded file */
	if (opt_verbose)
	{
		if (verify_errors)
			usermsg(y->output_filename, NULL, 0, 0, _("errors detected in multipart archive"), NULL);
		else
		{
			char	msgbuf[80];

			if (total == 1)
				snprintf(msgbuf, sizeof(msgbuf), _("%d part OK, file OK"), total);
			else
				snprintf(msgbuf, sizeof(msgbuf), _("%d parts OK, file OK"), total);

			/* Remember to delete all files in the set */
			if (opt_delete && !opt_test)
			{
				for (ct = first; ct < last; ct++)
				{
					y = yfiles[ct];
					if (unlink(y->input_filename))
						WarnERR("%s: error deleting file", y->input_filename);
				}
				usermsg(y->output_filename, NULL, 0, 0, msgbuf, "(%.2f%%) (%s)",
						  PCT(decodedsize,encodedsize),
						  (total == 1) ? _("input file deleted") : _("input files deleted"));
			}
			else
				usermsg(y->output_filename, NULL, 0, 0, msgbuf, "(%.2f%%)", PCT(decodedsize,encodedsize));
		}
	}

	return (0);
}
/*--- ydecode_multi() ---------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	OUTPUT_FILE_LIST
	Outputs a list of files found with information about each.
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static void
output_file_list(void)
{
	FILE	*out;
	register int ct;

	if (opt_list)
		out = stdout;
	else
		out = stderr;

	for (ct = 0; ct < num_yfiles; ct++)
	{
		YDECFILE *y = yfiles[ct];

		fprintf(out, "%10s: \"%s\"\n", _("input"), y->input_filename);
		fprintf(out, "%10s: \"%s\"\n", _("output"), y->output_filename);
		fprintf(out, "%10s: \"%s\"\n", _("filename"), y->header->name);
		fprintf(out, "%10s: %s\n", _("size"), y->header->size ? comma(*y->header->size) : _("UNSPECIFIED"));

		if (y->part)
		{
			fprintf(out, "%10s: %s\n", _("begin"), y->part->begin ? comma(*y->part->begin) : _("UNSPECIFIED"));
			fprintf(out, "%10s: %s\n", _("end"), y->part->end ? comma(*y->part->end) : _("UNSPECIFIED"));
		}
		fprintf(out, "%10s: %s\n", _("psize"), y->footer->size ? comma(*y->footer->size) : _("UNSPECIFIED"));
		if (y->footer->pcrc32)
			fprintf(out, "%10s: %08x\n", _("pcrc32"), *y->footer->pcrc32);
		else
			fprintf(out, "%10s: %s\n", _("pcrc32"), _("UNSPECIFIED"));
		if (y->footer->crc32)
			fprintf(out, "%10s: %08x\n", _("crc32"), *y->footer->crc32);
		else
			fprintf(out, "%10s: %s\n", _("crc32"), _("UNSPECIFIED"));

		if (y->multipart)
		{
			if (y->header->part && y->header->total)
				fprintf(out, _("%10s  multipart archive, part %d of %d\n"), " ", *y->header->part, *y->header->total);
			else if (y->header->part)
				fprintf(out, _("%10s  multipart archive, part %d\n"), " ", *y->header->part);
		}
		else
			fprintf(out, _("%10s  single part archive\n"), " ");

		fprintf(out, "\n");
	}
	if (opt_list)
		exit(EXIT_SUCCESS);
}
/*--- output_file_list() ------------------------------------------------------------------------*/


/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	MAIN
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int
main(int argc, char **argv)
{
	int	ct;

	set_progname(argv[0]);
	setlocale(LC_ALL, "");
	bindtextdomain(PACKAGE, LOCALEDIR);
	textdomain(PACKAGE);
	cmdline(argc, argv);

	/* Scan for files if requested */
	if (opt_scan || opt_recursive)
		scan_for_files();

	/* If there are no input files at this point, there's nothing else we can do */
	if (!num_yfiles)
	{
		Warn(_("no input files found"));
		usage(EXIT_FAILURE);
	}

	/* Process the file list */
	for (ct = 0; ct < num_yfiles; ct++)
	{
		flag_multipart_file(yfiles[ct]);
		set_output_filename(yfiles[ct]);
	}
	qsort(yfiles, num_yfiles, sizeof(YDECFILE *), ydecfile_cmp);

	/* Output file list for --list option (or debug) */
	if (opt_list || err_debug)
		output_file_list();

	/* Decode all files in the list */
	for (ct = 0; ct < num_yfiles; ct++)
	{
		if (yfiles[ct]->multipart)
			ydecode_multi(&ct);
		else
			ydecode_single(yfiles[ct]);
	}

	return (EXIT_SUCCESS);
}
/*--- main() ------------------------------------------------------------------------------------*/

/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1