mirror of
https://github.com/aaronsgiles/uuUndo.git
synced 2024-11-28 10:49:24 +00:00
1 line
17 KiB
C
1 line
17 KiB
C
/*
|
|
* uundo.c: extract a multi-part uuencoded archive Version 1.3a
|
|
* written by Aaron Giles 03/08/92
|
|
*
|
|
* usage:
|
|
* uundo [-hLloqv] file1 [file2 [...]]
|
|
* uundo [-hLloqv] < file
|
|
*
|
|
* options:
|
|
* -h header: write the article header of the earliest part to target.hdr
|
|
* -L lower all: convert all characters in all filenames to lower case
|
|
* -l lower: convert only all-upper-case filenames to lower case
|
|
* -o overwrite: automatically overwrite existing files without permission
|
|
* -q query: allow overwriting of existing files, but ask permission first
|
|
* -v verbose: display part numbers and status info
|
|
*
|
|
* special features:
|
|
* - does not require all parts to be in order*
|
|
* - attempts to identify missing parts*
|
|
* - works on uuencoded files included in shar archives
|
|
* - can accept either single or multiple files on the command line
|
|
* - can accept stream input from stdin
|
|
* (* requires part numbering informating in the subject line of each part)
|
|
*
|
|
* bugs/requests/modifications to:
|
|
* a-giles@uchicago.edu -or-
|
|
* gile@midway.uchicago.edu -or-
|
|
* giles@hep.uchicago.edu
|
|
*
|
|
* philosophy:
|
|
* I will try my best to keep up with any bug reports (first priority) or
|
|
* new feature requests. Note that my primary goal is robustness, so that
|
|
* requests which would overly complicate things or result in "flaky" code
|
|
* will very likely not be put in.
|
|
*
|
|
* copyright control:
|
|
* This program is public domain, though copyrighted (c) 1992 by its
|
|
* author, Aaron Giles. If you wish to distribute modified copies of
|
|
* this program, please contact the author first.
|
|
*
|
|
* standard disclaimer:
|
|
* Use this program at your own risk. The author takes no responsibility
|
|
* for any loss of data or damage that results from using this program.
|
|
*
|
|
* known problems/limitations:
|
|
* - cannot handle multiple target files in the same input file (sorry!)
|
|
* - can't yet accept input piped from rn or trn
|
|
*
|
|
* to be added in future versions:
|
|
* - support for saving header information
|
|
* - support for piping, allowing use with rn/trn
|
|
*
|
|
* version history:
|
|
* 1.0 (2/11/92) - initial release
|
|
* 1.1 (2/13/92) - added stdin support for preliminary use with rn
|
|
* no longer so restrictive on the first line of each part
|
|
* added verbose option
|
|
* added a warning to overwrite existing files
|
|
* added an option to turn off this warning
|
|
* now attempts to make file writeable before overwriting
|
|
* fixed problem with "BEGIN..." lines fooling the decoder
|
|
* 1.2 (2/21/92) - added force lowercase options
|
|
* changed procedure dealing with conflicting filenames
|
|
* added query option
|
|
* now uses process ID in temporary names to avoid conflicts
|
|
* output directory can be set via UUNDO environment variable
|
|
* 1.21 (2/28/92) - fixed bug that would alter directory attributes if none
|
|
* of the input files were found
|
|
* 1.3 (3/08/92) - added support for maximum filename length
|
|
* - refined handling of jumbled and unlabelled parts
|
|
* - fixed the aforementioned bug -- again
|
|
* - added option to write header of earliest part to a file
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
/*
|
|
* Platform-specific definitions
|
|
*/
|
|
#define PATHSEP '/' /* path separator character */
|
|
#define MAXNAMELEN 32 /* maximum length of a given filename */
|
|
#define TEMPNAME "/usr/tmp/uu" /* prefix for temporary files */
|
|
|
|
/*
|
|
* Arbitrary global definitions
|
|
*/
|
|
#define MAXLEN 256 /* maximum length of strings/pathnames */
|
|
#define MAXPARTS 256 /* maximum number of parts in file */
|
|
#define BUFSIZE 32768 /* size of temporary buffer */
|
|
|
|
/*
|
|
* Header storage structure
|
|
*/
|
|
typedef struct header_str *header_ptr;
|
|
struct header_str {
|
|
char *line;
|
|
header_ptr next;
|
|
};
|
|
|
|
/*
|
|
* Global variables
|
|
*/
|
|
int debug = 0; /* debug flag */
|
|
int header = 0; /* header flag */
|
|
int Lower = 0; /* all lower case flag */
|
|
int lower = 0; /* lower case flag */
|
|
int overwrite = 0; /* overwrite flag */
|
|
int query = 0; /* query flag */
|
|
int verbose = 0; /* verbose flag */
|
|
|
|
int part, total; /* current part number and total count */
|
|
int pending[MAXPARTS]; /* table of pending entries */
|
|
char lastsubj[MAXLEN]; /* text of the last subject line read */
|
|
|
|
int partswritten; /* total number of parts written to target */
|
|
long int byteswritten; /* total number of bytes written to target */
|
|
|
|
FILE *targetfile; /* stream pointer for target file */
|
|
char targetname[MAXLEN]; /* name of target file */
|
|
char *targetpath; /* pointer to path for output directory */
|
|
int targetmode; /* file mode for final target */
|
|
|
|
header_ptr hdrroot = NULL; /* linked list of header lines */
|
|
header_ptr hdrlast = NULL; /* last group of header lines read */
|
|
int hdrpart; /* part associated with this header */
|
|
|
|
pid_t pid; /* process ID of this program */
|
|
|
|
/*
|
|
* tempname:
|
|
* put temporary file name for part num into string variable name
|
|
*/
|
|
char *tempname(int num, char *name) {
|
|
strcpy(name, TEMPNAME);
|
|
sprintf(name+strlen(name), "%ld.%03i", pid, num);
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* filename:
|
|
* combine path, filename, and iteration, checking for length
|
|
*/
|
|
char *filename(char *dest, char *path, char *file, int iter) {
|
|
char *p, *q;
|
|
char suffix[4];
|
|
|
|
*dest = 0;
|
|
if (path) strcat(dest, path);
|
|
if ((!path) || (dest[strlen(dest) - 1] != PATHSEP)) {
|
|
dest[strlen(dest) + 1] = 0;
|
|
dest[strlen(dest)] = PATHSEP;
|
|
}
|
|
q = dest + strlen(dest);
|
|
for (p = file + strlen(file); (*(p - 1) != PATHSEP) && (p > file); p--);
|
|
strncat(dest, p, MAXNAMELEN);
|
|
if (iter) {
|
|
if (iter > 0) sprintf(suffix, ".%02i", iter);
|
|
else strcpy(suffix, ".hdr");
|
|
if (strlen(q) > (MAXNAMELEN - 3))
|
|
strcpy(q + MAXNAMELEN - 3, suffix);
|
|
else
|
|
strcpy(q + strlen(q), suffix);
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* createfile:
|
|
* attempt to create the named file
|
|
*/
|
|
FILE *createfile(char *name, char *type) {
|
|
FILE *file;
|
|
if (!(file = fopen(name, "w")))
|
|
fprintf(stderr, "uundo: unable to create %s file %s\n", type, name);
|
|
return file;
|
|
}
|
|
|
|
/*
|
|
* openfile:
|
|
* attempt to open the named file for reading
|
|
*/
|
|
FILE *openfile(char *name, char *type) {
|
|
FILE *file;
|
|
if (!(file = fopen(name, "r")))
|
|
fprintf(stderr, "uundo: unable to open %s file %s\n", type, name);
|
|
return file;
|
|
}
|
|
|
|
/*
|
|
* deletefile:
|
|
* remove the named file
|
|
*/
|
|
void deletefile(char *name) {
|
|
chmod(name, 0666);
|
|
if (unlink(name))
|
|
fprintf(stderr, "uundo: unable to remove file %s\n", name);
|
|
}
|
|
|
|
/*
|
|
* checkfile:
|
|
* check for existence of target file before overwriting
|
|
*/
|
|
int checkfile(char *name) {
|
|
FILE *file;
|
|
char s[MAXLEN];
|
|
int count = 0;
|
|
|
|
while (file = fopen(filename(targetname, targetpath, name, count++), "r")) {
|
|
fclose(file);
|
|
if (overwrite) {
|
|
deletefile(targetname);
|
|
return 1;
|
|
} else if (query) {
|
|
fprintf(stderr, "uundo: %s already exists: overwrite? ", name);
|
|
gets(s);
|
|
if ((*s == 'Y') || (*s == 'y')) {
|
|
deletefile(targetname);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* readline:
|
|
* input a line and strip the newline character
|
|
*/
|
|
int readline(FILE *file, char *s) {
|
|
int l;
|
|
|
|
if (!fgets(s, MAXLEN, file)) return 0;
|
|
if ((l = strlen(s)) == (MAXLEN - 1)) {
|
|
fprintf(stderr, "uundo: invalid input file format\n");
|
|
exit(1);
|
|
}
|
|
s[l - 1] = 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* addhdr:
|
|
* add a header line to the end of the linked list
|
|
*/
|
|
header_ptr addhdr(header_ptr root, char *line) {
|
|
header_ptr new, cur;
|
|
|
|
if (new = (header_ptr)malloc(sizeof(struct header_str))) {
|
|
new->next = NULL;
|
|
if (new->line = (char *)malloc(strlen(line) + 1)) {
|
|
strcpy(new->line, line);
|
|
if (root) {
|
|
for (cur = root; cur->next; cur = cur->next);
|
|
cur->next = new;
|
|
} else root = new;
|
|
return root;
|
|
}
|
|
}
|
|
fprintf(stderr, "uundo: out of memory!");
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* killhdr:
|
|
* kill the linked list of header lines
|
|
*/
|
|
header_ptr killhdr(header_ptr root) {
|
|
header_ptr cur, next;
|
|
|
|
if (!root) return NULL;
|
|
for (cur = root; cur; cur = next) {
|
|
free(cur->line);
|
|
next = cur->next;
|
|
free(cur);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* writehdr:
|
|
* write out the linked list of headers
|
|
*/
|
|
void writehdr(header_ptr root) {
|
|
FILE *hdrfile;
|
|
char hdrname[MAXLEN];
|
|
|
|
if ((!header) || (!root)) return;
|
|
filename(hdrname, targetpath, targetname, -1);
|
|
if (!(hdrfile = createfile(hdrname, "header"))) exit(1);
|
|
for ( ; root->next; root = root->next)
|
|
fprintf(hdrfile, "%s\n", root->line);
|
|
fclose(hdrfile);
|
|
}
|
|
|
|
/*
|
|
* uuline:
|
|
* determine if the given line is a correct uuencoded line
|
|
*/
|
|
int uuline(char *s, int full) {
|
|
int l, len, max, min;
|
|
|
|
if ((strncasecmp(s, "end", 3) == 0) ||
|
|
(strncasecmp(s, "begin", 5) == 0) ||
|
|
((l = s[0] - ' ') & 0xc0)) return 0;
|
|
min = ((l * 4) + 2) / 3;
|
|
max = ((min + 3) / 4) * 4 + 1;
|
|
len = strlen(s) - 1;
|
|
if ((len < min) || (len > max)) return 0;
|
|
if (!full && ((l == 45) || (l == 0))) return 1;
|
|
for (s++; *s; s++)
|
|
if ((*s < ' ') || (*s > '`')) return 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* headerstate:
|
|
* determine whether we should be saving (1) or ignoring (0) header lines
|
|
*/
|
|
int headerstate(char *line, int oldstate) {
|
|
if (oldstate) {
|
|
if ((line[0] == '#') && (strstr(line, "/bin/sh"))) return 0;
|
|
return 1;
|
|
} else {
|
|
while (!isspace(*line) && *line && (*line != ':')) line++;
|
|
if (*line == ':') return 1;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* decode:
|
|
* decode a uuencoded line and output the binary data to ouf
|
|
*/
|
|
void decode(FILE *outf, char *line) {
|
|
int count = line[0] - ' ';
|
|
int i, j;
|
|
|
|
if (debug) printf("%s\n", line);
|
|
for (j = 1; line[j]; j++) line[j] = (line[j] - ' ') & 0x3f;
|
|
for (i = 0, j = 1; i < count; i += 3, j += 4) {
|
|
line[i] = ((line[j] << 2) | (line[j+1] >> 4)) & 0xff;
|
|
line[i+1] = ((line[j+1] << 4) | (line[j+2] >> 2)) & 0xff;
|
|
line[i+2] = ((line[j+2] << 6) | (line[j+3] )) & 0xff;
|
|
}
|
|
if (count) {
|
|
if (outf == targetfile) byteswritten += count;
|
|
if (count -= fwrite(line, 1, count, outf)) {
|
|
fprintf(stderr, "uundo: unable to write to output file\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* unpend:
|
|
* append pending file to the target
|
|
*/
|
|
void unpend(int number) {
|
|
int i;
|
|
FILE *inf;
|
|
char inname[MAXLEN];
|
|
char buffer[BUFSIZE];
|
|
|
|
if (!(inf = openfile(tempname(number, inname), "temporary"))) exit(1);
|
|
while (i = fread(buffer, 1, BUFSIZE, inf)) {
|
|
if (fwrite(buffer, 1, i, targetfile) != i) {
|
|
fprintf(stderr, "uundo: unable to append temporary file %s\n", inname);
|
|
exit(1);
|
|
} else
|
|
byteswritten += i;
|
|
}
|
|
fclose(inf);
|
|
deletefile(inname);
|
|
pending[number] = 0;
|
|
}
|
|
|
|
/*
|
|
* parse:
|
|
* determine part number and total number from subject header line
|
|
*/
|
|
void parse(char *p) {
|
|
char *q;
|
|
int i;
|
|
|
|
part = -1;
|
|
for (; *p; p++) {
|
|
switch (*p) {
|
|
case 'P': case 'p':
|
|
if (isalpha(*(p - 1)) || (strncasecmp(p, "part", 4)) ||
|
|
isalpha(*(p + 4))) continue;
|
|
p += 4;
|
|
break;
|
|
case '(': case '{': case '[':
|
|
p++;
|
|
break;
|
|
case 'O': case 'o':
|
|
if (isalpha(*(p - 1)) || (strncasecmp(p, "of", 2)) ||
|
|
isalpha(*(p + 2))) continue;
|
|
case '|':
|
|
case '/':
|
|
for (q = p; isspace(*(q - 1)); q--);
|
|
if (!isdigit(*(q - 1))) continue;
|
|
while (isdigit(*(q - 1))) q--;
|
|
if (isalpha(*(q - 1))) continue;
|
|
p = q;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
i = (int)strtol(p, &q, 10);
|
|
if (p == q) { p--; continue; }
|
|
part = i;
|
|
for (p = q; isspace(*p); p++);
|
|
switch (*p) {
|
|
case '|':
|
|
case '/':
|
|
p++;
|
|
break;
|
|
case 'o': case 'O':
|
|
if (strncasecmp(p, "of", 2) == 0) {
|
|
p+=2;
|
|
break;
|
|
}
|
|
default:
|
|
p--;
|
|
continue;
|
|
}
|
|
while (isspace(*p)) p++;
|
|
i = (int)strtol(p, &q, 10);
|
|
if (p == q) { p--; continue; }
|
|
total = i;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* initfile:
|
|
* parse the uuencode header line and initialize the output file
|
|
*/
|
|
int initfile(char *p) {
|
|
char *q;
|
|
|
|
if (partswritten > 0) {
|
|
fprintf(stderr, "uundo: found extra part 1, ignored\n");
|
|
return 1;
|
|
}
|
|
while (isspace(*p) && *p) p++;
|
|
if (*p) targetmode = (int)strtol(p, (char **)NULL, 8);
|
|
else targetmode = 0666;
|
|
while (!isspace(*p) && *p) p++;
|
|
while (isspace(*p) && *p) p++;
|
|
if (!*p) {
|
|
strcpy(p, "a.out");
|
|
fprintf(stderr, "uundo: filename not found, using a.out instead\n");
|
|
}
|
|
for (q = p; *q && !islower(*q); q++);
|
|
if (Lower || (lower && !*q))
|
|
for (q = p; *q; *q = tolower(*q), q++);
|
|
if (!checkfile(p)) exit(1);
|
|
if (!(targetfile = createfile(targetname, "output"))) exit(1);
|
|
partswritten = 0;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* startpart:
|
|
* figure out where to output the newly-found part
|
|
*/
|
|
FILE *startpart() {
|
|
char name[MAXLEN];
|
|
FILE *outf;
|
|
|
|
if (partswritten > 0)
|
|
while (pending[partswritten + 1]) unpend(++partswritten);
|
|
if ((part == -1) && (partswritten < 0)) {
|
|
fprintf(stderr, "uundo: unnumbered part found out of order; ignored\n");
|
|
return (FILE *)NULL;
|
|
}
|
|
if ((part == -1) || (part == (partswritten + 1)))
|
|
return targetfile;
|
|
if ((part <= partswritten) || (pending[part])) {
|
|
fprintf(stderr, "uundo: found extra part %i, ignored\n", part);
|
|
return (FILE *)NULL;
|
|
}
|
|
pending[part] = 1;
|
|
if (!(outf = createfile(tempname(part, name), "temporary"))) exit(1);
|
|
return outf;
|
|
}
|
|
|
|
/*
|
|
* init:
|
|
* initialize pending array and do general setup
|
|
*/
|
|
int init(int argc, char *argv[]) {
|
|
int i;
|
|
char *p;
|
|
|
|
pid = getpid();
|
|
targetpath = getenv("UUNDO");
|
|
strcpy(lastsubj, "");
|
|
for (i = 1; argv[i] && (*argv[i] == '-'); i++) {
|
|
for (p = argv[i] + 1; *p; p++) {
|
|
switch (*p) {
|
|
case 'd': debug = 1; break;
|
|
case 'h': header = 1; break;
|
|
case 'L': Lower = 1; break;
|
|
case 'l': lower = 1; break;
|
|
case 'o': overwrite = 1; break;
|
|
case 'q': query = 1; break;
|
|
case 'v': verbose = 1; break;
|
|
}
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* initglobals:
|
|
* reset all global variables in anticipation of next file
|
|
*/
|
|
void initglobals() {
|
|
int i;
|
|
|
|
for (i = 0; i < MAXPARTS; pending[i++] = 0);
|
|
part = -1;
|
|
total = 0;
|
|
partswritten = -2;
|
|
byteswritten = 0;
|
|
targetname[0] = 0;
|
|
targetmode = 0;
|
|
hdrroot = killhdr(hdrroot);
|
|
hdrlast = killhdr(hdrlast);
|
|
hdrpart = MAXPARTS;
|
|
}
|
|
|
|
/*
|
|
* findstart:
|
|
* parse the header and skip garbage before uuencoded data
|
|
*/
|
|
FILE *findstart(FILE *inf) {
|
|
FILE *outf;
|
|
char line[MAXLEN];
|
|
int ignore = 0, save = 0;
|
|
|
|
while (readline(inf, line)) {
|
|
if (!line[0]) continue;
|
|
if ((header) && (save = headerstate(line, save)))
|
|
hdrlast = addhdr(hdrlast, line);
|
|
if (strncasecmp(line, "Subject:", 8) == 0) {
|
|
ignore = 0;
|
|
parse(line + 8);
|
|
} else if (strncmp(line, "begin ", 6) == 0) {
|
|
if (!(ignore = initfile(line + 6)))
|
|
return targetfile;
|
|
} else if (!ignore && uuline(line, 1) &&
|
|
(line[0] != '#') && (line[0] != '-') && (line[0] != ' ')) {
|
|
if (outf = startpart()) {
|
|
decode(outf, line);
|
|
return outf;
|
|
} else ignore = 1;
|
|
}
|
|
}
|
|
return (FILE *)NULL;
|
|
}
|
|
|
|
/*
|
|
* partstatus:
|
|
* display current part number and keep correct header info.
|
|
*/
|
|
void partstatus() {
|
|
if (part < 0) part = partswritten + 1;
|
|
if (header) {
|
|
if (part < hdrpart) {
|
|
hdrroot = killhdr(hdrroot);
|
|
hdrroot = hdrlast;
|
|
hdrpart = part;
|
|
hdrlast = NULL;
|
|
} else hdrlast = killhdr(hdrlast);
|
|
}
|
|
if (verbose) {
|
|
if (total)
|
|
fprintf(stderr, "uundo: found part %i of %i\n", part, total);
|
|
else
|
|
fprintf(stderr, "uundo: found part %i\n", part);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cleanup:
|
|
* clean up our little mess
|
|
*/
|
|
void cleanup() {
|
|
char name[MAXLEN];
|
|
int i, found = 0;
|
|
|
|
for (i = 0; i < MAXPARTS; found += pending[i++]);
|
|
if ((partswritten < 0) && (found))
|
|
fprintf(stderr, "uundo: missing file part 1\n");
|
|
else if (partswritten >= 0) {
|
|
while (pending[partswritten + 1]) unpend(++partswritten);
|
|
if ((total) && (partswritten < total)) {
|
|
fprintf(stderr, "uundo: missing part %i of %i; target removed\n",
|
|
partswritten + 1, total);
|
|
if (partswritten >= 0) deletefile(targetname);
|
|
} else {
|
|
if (targetmode) chmod(targetname, targetmode);
|
|
if (verbose)
|
|
fprintf(stderr, "Wrote file %s (%i parts, %ld bytes)\n", targetname,
|
|
partswritten, byteswritten);
|
|
}
|
|
if (partswritten >= 0) fclose(targetfile);
|
|
}
|
|
for (i = 0; i < MAXPARTS; i++)
|
|
if (pending[i]) deletefile(tempname(i, name));
|
|
if (header) {
|
|
writehdr(hdrroot);
|
|
hdrroot = killhdr(hdrroot);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* main program loop
|
|
*/
|
|
int main(int argc, char *argv[]) {
|
|
FILE *infile = stdin, *outfile;
|
|
int fptr, start;
|
|
char line[MAXLEN];
|
|
|
|
start = init(argc, argv);
|
|
initglobals();
|
|
for (fptr = start;
|
|
(fptr < argc) || ((fptr == start) && (argc == start)); fptr++) {
|
|
if ((argc > start) && (!(infile = openfile(argv[fptr], "input"))))
|
|
continue;
|
|
while (outfile = findstart(infile)) {
|
|
partstatus();
|
|
while (readline(infile, line)) {
|
|
if (!uuline(line, 0)) break;
|
|
decode(outfile, line);
|
|
}
|
|
if (outfile != targetfile)
|
|
fclose(outfile);
|
|
else
|
|
partswritten++;
|
|
}
|
|
fclose(infile);
|
|
}
|
|
cleanup();
|
|
}
|