/**************************************************************************************************
$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