home *** CD-ROM | disk | FTP | other *** search
- /* -------------------------------------------------------------------------- **
- * Program: AuDecode
- *
- * UUdecoding for the Amiga.
- *
- * Written by Christopher R. Hertel
- * Email: crh@bubble.mooses.affinity.mn.org
- * -------------------------------------------------------------------------- **
- * Copyright (C) 1994 Christopher R. Hertel
- *
- * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- * -------------------------------------------------------------------------- **
- *
- * $Log: AuDecode.c,v $
- * Revision 1.2 94/02/15 21:42:29 CRH
- * Quick change to the construction of the version string.
- *
- * Revision 1.1 94/02/15 21:39:24 CRH
- * Small fixes to the help messages.
- * ,
- *
- * Revision 1.0 94/02/06 16:26:16 CRH
- * Initial revision
- *
- * -------------------------------------------------------------------------- **
- * My understanding of uuencoding/decoding is based on the knowledge that I
- * gained by reading the uuencode/decode source provided with AmigaUUCP
- * V1.16. Many thanks to the authors of that software! The following are
- * comments from their code, which are included as my way of giving credit
- * where credit is due.
- *
- * /begin/
- * Written by Mark Horton
- * Modified by ajr (Alan J Rosenthatl,flaps@utcsri.UUCP) to use checksums
- * Modified by fnf (Fred Fish,well!fnf) to use Keith Pyle's suggestion for
- * compatibility
- * Modified by bcn (Bryce Nesbitt,ucbvax!hoser!bryce) to enable CTRL-C for
- * Amiga Lattice C. Added a transparent file size trailer for later check.
- * Changed fopen from "r" to "rb" for Messy-dos machines (thanks to Andrew
- * Wylie)
- * [uudecode.c:]
- * Modified by bcn (Bryce Nesbitt,ucbvax!cogsci!bryce) to fix a misleading
- * error message on the Amiga port, to fix a bug that prevented decoding
- * certain files, to work even if trailing spaces have been removed from a
- * file, to check the filesize (if present), to add some error checking, to
- * loop for multiple decodes from a single file, and to handle common
- * BITNET mangling. Kludged around a missing string function in Aztec
- * C. Changed "r" to "rb" and "w" to "wb" for Messy-dos machines
- * (Thanks to Andrew Wylie).
- * /end/
- *
- * -------------------------------------------------------------------------- **
- * This program uses the AuCode module, which contains most of the uudecoding
- * functionality. 90% of *this* file is dedicated to command line parsing,
- * file I/O, and error checking. This file was written to compile under SAS
- * Amiga C 5.10 or later. I use the following command line:
- *
- * lc -L AuDecode.c AuCode.c
- *
- * CRH
- * -------------------------------------------------------------------------- **
- */
-
- #include <stdio.h> /* Standard I/O */
- #include <string.h> /* Standard string functions */
- #include <stdlib.h> /* Standard C functions. */
- #include <ctype.h> /* C iswhatsit() macros. */
- #include <time.h> /* C time functions. */
- #include <sys/types.h> /* Unix types. */
- #include <sys/stat.h> /* File status functions. */
- #include "AuCode.h" /* Amiga-Usenet coding module. */
-
- /* -------------------------------------------------------------------------- **
- * Defines...
- *
- * FALSE - Zero, the standard C value for not true.
- * TRUE - If it's not false, it must be true. Mustn't it?
- *
- * MAXCS_WARN - Maximum number of checksum warning messages to report
- * before we get annoying.
- */
- #define FALSE 0
- #define TRUE (~0)
- #define MAXCS_WARN 10
-
- /* -------------------------------------------------------------------------- **
- * Macros...
- * Max - This is your basic max() macro. I implement it here in
- * order to avoid the inclusion of extra header files.
- */
- #define Max(A,B) (((A)>(B))?(A):(B))
-
- /* -------------------------------------------------------------------------- **
- * Typedefs...
- *
- * boolean - Your basic True/False options.
- */
-
- typedef char boolean;
-
- /* -------------------------------------------------------------------------- **
- * Global constants...
- * vers - Program version string.
- * HelpTxt - An array of help strings.
- */
-
- const char *vers = "\0$VER$Id: AuDecode.c,v 1.2 94/02/15 21:42:29 CRH Exp $";
-
- const char *HelpTxt[] =
- {
- "Copyright (C) 1994 by Christopher R. Hertel\n",
- "This program decodes uuencoded files.\n",
- "Usage:",
- "\tAuDecode [options] [infile]\n",
- "where [infile] = an input file. This is the source files that is",
- " to be decoded. If no file is specified, then ",
- " standard input will be read.",
- " [options] = command line switches, as follows:\n",
- " -? or -h = display this help message.",
- " -a = if the output file already exists, open it in",
- " append mode. The default is to overwrite it.",
- " -d<n> = decode specific occurance. If the input stream",
- " contains several encoded files, this switch will",
- " allow you to select a single encoded file to",
- " decode. The default is to decode all files.",
- " -i = display index only. This switch causes AuDecode",
- " to scan the input for encoded files. The files",
- " *will not be decoded*, but a sequentially numbered",
- " list of names will be written to standard output.",
- " -m<mode> = output file mode (Unix protection scheme).",
- " (Not fully implemented yet.)",
- " -n<filename> = specify the output filename. Uuencoded files",
- " contain a default target filename, which is",
- " generally the same as the input file name received",
- " by the encoder. The decoding program will use this",
- " name as the default output filename. The -n option",
- " allows you to override the default target filename.",
- " Note: If the -d option is not specified, -n assumes -d1.",
- " -o = send output to the standard output device (stdout).",
- " This option is similar to -n, except that it",
- " instructs AuDecode to redirect output to standard",
- " output, rather than to a file. This is useful if",
- " you wish to use command line redirection. This",
- " option will override -n.",
- " Note: Unlike -n, -o does not assume -d1. If you do not",
- " specify an occurance, all encoded files will be",
- " decoded to stdout.",
- " -s = status display. The encoder will display the",
- " converted byte count (via standard error) as",
- " decoding progresses.",
- NULL
- }; /* HelpTxt[] */
-
- /* -------------------------------------------------------------------------- **
- * Globals Variables...
- */
-
- /* Process parameters Default actions */
- boolean AppendFlg = FALSE; /* Open output in overwrite mode. */
- boolean DecodeFlg = TRUE; /* Yes we decode (no, we don't just index). */
- boolean StatusFlg = FALSE; /* No, we don't display statistics. */
- boolean StdOutFlg = FALSE; /* Don't send output to stdout. */
- int OutFlMode = 0; /* Output file mode. 0=default. */
- int SelectOcc = 0; /* Zero means "select all occurances". */
- int badcsums = 0; /* Number of bad checksums encountered. */
-
- /* File and path specifications. */
- char *OutFlName = NULL; /* Output file name (default=encoded name). */
- char *SrcFlName = NULL; /* Name of input file (default=stdin). */
-
- /* Processing buffers. */
- char linebufr[au_cdMAX_LINE]; /* Input buffer. */
- char tmpbufr[au_cdMAX_LINE]; /* Scratch buffer. */
-
-
- /* -------------------------------------------------------------------------- **
- * Functions...
- */
-
- static void HelpMsg( void )
- /* ------------------------------------------------------------------------ **
- * This function sends the help message text to stderr.
- *
- * Notes: For the Amiga, this code could easily be replaced with calls to
- * locale.library under OS version 2.1 or greater.
- * ------------------------------------------------------------------------ **
- */
- {
- int i;
-
- printf( "%s\n", &vers[2] ); /* Print version info. */
- for( i = 0; HelpTxt[i]; i++ ) /* Display each line of help text. */
- printf( "%s\n", HelpTxt[i] );
- } /* HelpMsg */
-
- static boolean ParseCmdLine( int argc, char *argv[] )
- /* ------------------------------------------------------------------------ **
- * This function parses and responds to command line input.
- *
- * Input: argc - Count of arguments in argv.
- * argv - An array of pointers to character strings.
- * Basically, the previous two are the same standard C
- * input parameters that are fed into main().
- *
- * Output: A boolean. TRUE if we parsed successfully and can continue
- * processing, else FALSE.
- *
- * Notes: If there *was* no command line input, then we were run from the
- * workbench. We don't know how to do that yet, so we error out.
- * If only one argument, we display a brief message to stderr, and
- * just keep on going (assume redirected input).
- * If the first argument is a '?', or if a -h or -? switch is read
- * from the command line, we will dump the help text and exit.
- * ------------------------------------------------------------------------ **
- */
- {
- int i;
- char *Source;
-
- if( 0 == argc ) /* No arguments = Amiga Workbench */
- return( FALSE );
-
- if( 1 == argc ) /* A signle argument may indicate redirected I/O. */
- { /* Output to stderr is still safe. */
- fprintf( stderr, "[%s] Type '%s ?' for help.\n", &vers[2], argv[0] );
- fprintf( stderr, "Copyright (C) 1994 Christopher R. Hertel\n" );
- fprintf( stderr, "Ctrl-C <return> to cancel.\n" );
- return( TRUE );
- }
-
- if( '?' == argv[1][0] ) /* If first character of argv[1] is a '?'...*/
- { /* ...then send help (and exit). */
- HelpMsg();
- return( FALSE );
- }
-
- /* Now we can attempt to parse the command line. */
-
- for( i = 1; i < argc; i++ )
- /* For each command line parameter... */
- {
- Source = argv[i];
- if( '-' == *Source )
- switch( Source[1] )
- {
- case '?': /* Dump help text & return */
- case 'H':
- case 'h':
- HelpMsg();
- return( FALSE );
- case 'A': /* Open output files in append mode. */
- case 'a':
- AppendFlg = TRUE;
- break;
- case 'D': /* Select a specific occurance. */
- case 'd':
- sscanf( &(Source[2]), "%d", &(SelectOcc) );
- break;
- case 'I': /* Produce index only. */
- case 'i':
- DecodeFlg = FALSE;
- break;
- case 'S': /* Display status information. */
- case 's':
- StatusFlg = TRUE;
- break;
- case 'M': /* Read mode from cmd line. */
- case 'm':
- if( OutFlMode )
- fprintf( stderr, "Warning: Multiple -m values; last one wins.\n" );
- sscanf( &Source[2], "%o", &OutFlMode );
- break;
- case 'N': /* Set output file name. */
- case 'n':
- if( StdOutFlg )
- fprintf( stderr, "Warning: -o and -n conflict; -o wins.\n" );
- else
- {
- if( OutFlName )
- {
- fprintf( stderr, "Error: Output filename specified twice.\n" );
- return( FALSE );
- }
- else
- OutFlName = &Source[2];
- }
- break;
- case 'O': /* Output to stdout. */
- case 'o':
- if( OutFlName )
- {
- fprintf( stderr, "Warning: -o and -n conflict; -o wins.\n" );
- OutFlName = NULL;
- }
- StdOutFlg = TRUE;
- break;
- default:
- fprintf( stderr, "Warning: Unknown option: %s\n", Source );
- } /* switch */
- else /* Get input file name. */
- {
- if( SrcFlName )
- {
- fprintf( stderr, "Error: Multiple input files specified.\n" );
- fprintf( stderr, "[%s], [%s]\n", SrcFlName, Source );
- fprintf( stderr, "This program handles only one source at a time. " );
- fprintf( stderr, "Sorry.\n" );
- return( FALSE );
- }
- SrcFlName = Source;
- }
- } /* for */
-
- return( TRUE );
- } /* ParseCmdLine */
-
- char *ParseBegin( char *Source, int *modeptr )
- /* ------------------------------------------------------------------------ **
- * This function identifies the file name and file mode that are typically
- * stored in the uuencoded 'begin' line.
- *
- * Input: Source - a pointer to the string buffer that contains the
- * 'begin' line.
- * modeptr - a pointer to an integer. The mode will be read from
- * the begin line, and stored in the location indicated
- * by modeptr.
- *
- * Output: A pointer to the file name substring of the begin line. That
- * is, a pointer to the position within Source[] at which the file
- * name is assumed to start.
- *
- * Notes: This function will remove any trailing control characters from
- * the Source string by placing a nul byte ('\0') after the last
- * non-control character.
- *
- * This function should probably be moved into the AuCode module.
- * ------------------------------------------------------------------------ **
- */
- {
- int i;
- char *p;
-
- sscanf( &Source[5], " %o", modeptr ); /* Read Mode */
- for( i = 5; isspace( Source[i] ); i++ ); /* Skip pre-mode spaces. */
- for( ; isdigit( Source[i] ); i++ ); /* Skip mode. */
- for( ; isspace( Source[i] ); i++ ); /* Skip post-mode spaces. */
- p = &Source[i]; /* Should be start of name. */
- for( i = strlen(p); iscntrl(p[i]); --i ); /* Find last non-controlchar*/
- p[i+1] = '\0'; /* Trim off control chars */
- return( p );
- } /* ParseBegin */
-
- int ParseSize( void )
- /* ------------------------------------------------------------------------ **
- * This function parses the 'size' line that is often included folloing a
- * uuencoded block.
- *
- * Input: None.
- *
- * Ouptut: The size, in bytes, of the original file as reported by the
- * 'size' line, or zero (0) if there was no size line.
- *
- * This function should probably be moved into the AuCode module.
- * ------------------------------------------------------------------------ **
- */
- {
- int tmp = 0;
-
- if( 0 == strncmp( "size", linebufr, 4 ) ) /* If line starts w/"size" */
- sscanf( &linebufr[4], " %ld", &tmp ); /* Read size from string. */
- return( tmp );
- } /* ParseSize */
-
- boolean Index( FILE *inF )
- /* ------------------------------------------------------------------------ **
- * This function works its way through the occurance of an encoded output
- * file within the input file.
- *
- * Input:
- * inF - A pointer to the input file (so that we can read from it).
- *
- * Output: TRUE if the end of the occurance was found. FALSE if the
- * end of the input file was found before the end of the
- * encoded occurance.
- * ------------------------------------------------------------------------ **
- */
- {
- char *name;
- int namelen;
- int mode = 0;
- int size = 0;
- long bytcnt = 0;
-
- name = ParseBegin( linebufr, &mode );
- namelen = strlen( name );
-
- if( namelen > 35 ) /* Print file name. */
- printf( "%-34.34s+ ", name );
- else
- printf( "%-35.*s ", namelen, name );
-
- if( mode > 0 )
- printf( "%4o ", mode ); /* Print file mode. */
- else
- printf( "???? " ); /* ...or ?s if no mode. */
-
- if( StatusFlg )
- /* Status "ON" version. */
- {
- int lncnt = 0;
-
- for(;;)
- {
- if( !(fgets( linebufr, au_cdMAX_LINE, inF )) )
- { /* EOF before end/n = error.*/
- fprintf( stderr,
- "\nError: Found End-of-File before end of encoded data.\n" );
- return( FALSE );
- }
- if( strncmp( "end\n", linebufr, 4 ) )
- {
- lncnt++;
- bytcnt += au_cdDECD( linebufr[0] );
- if( 0 == (lncnt % 10) )
- printf( "%9ld\b\b\b\b\b\b\b\b\b", bytcnt );
- }
- else
- break;
- }
- }
- else
- /* Status "OFF" version (identical to above, but without status output). */
- {
- for(;;)
- {
- if( !(fgets( linebufr, au_cdMAX_LINE, inF )) )
- { /* EOF before end/n = error.*/
- fprintf( stderr,
- "\nError: Found End-of-File before end of encoded data.\n" );
- return( FALSE );
- }
- if( strncmp( "end\n", linebufr, 4 ) )
- bytcnt += au_cdDECD( linebufr[0] );
- else
- break;
- }
- }
- printf( "%9ld ", bytcnt ); /* Print totaled byte count.*/
-
- if( fgets( linebufr, au_cdMAX_LINE, inF ) ) /* Try to read past "end". */
- size = ParseSize(); /* ...and read "size" line. */
-
- if( size ) /* Print recorded size. */
- printf( "%12ld%s\n", size, (size==bytcnt)?"":" *" );
- else
- printf( "-Not provided-\n" ); /* No size value found. */
-
- return( TRUE );
- } /* Index */
-
- void ChecksumWarn( int lineno )
- /* ------------------------------------------------------------------------ **
- * This function prints a checksum warning message. Note that each file
- * is allowed a maximum of MAXCS_WARN warnings.
- *
- * Input: lineno - The line number. This will be printed as part of the
- * error message.
- *
- * Output: None.
- * ------------------------------------------------------------------------ **
- */
- {
- badcsums++;
- if( badcsums <= MAXCS_WARN )
- {
- fprintf( stderr, "Warning: Bad checksum at encoded line #%d.\n", lineno );
- if( badcsums == MAXCS_WARN )
- fprintf( stderr, " Maximum checksum warnings reached.\n" );
- }
- } /* ChecksumWarn */
-
- boolean Decode( FILE *inF )
- /* ------------------------------------------------------------------------ **
- * This function decodes a single encoded file, from 'begin' line to 'end'
- * and (if present) 'size'.
- *
- * Input: inF - Input file handle.
- *
- * Output: TRUE if decoding was successful, else FALSE.
- * ------------------------------------------------------------------------ **
- */
- {
- FILE *fOut = stdout;
- char *name;
- int mode = 0;
- int size = 0;
- int lncnt = 0;
- int ErrCD;
- long cnt;
- long totcnt = 0;
-
- badcsums = 0; /* Init checksum warning count. */
- name = ParseBegin( linebufr, &mode ); /* Get filename & mode from input */
- if( OutFlMode ) /* Override mode? */
- mode = OutFlMode;
-
- if( StdOutFlg ) /* Override filename with stdout? */
- name = "Standard output";
- else
- {
- if( OutFlName ) /* Override filename with -n? */
- name = OutFlName;
- else
- name = strdup( name );
- fOut = fopen( name, (AppendFlg)?"ab":"wb" );
- if( !fOut )
- {
- fprintf( stderr, "Error: Unable to open output file: [%s].\n", name );
- return( FALSE );
- }
- }
-
- if( StatusFlg )
- /* Status "ON" version. */
- {
- for(;;)
- {
- if( !(fgets( linebufr, au_cdMAX_LINE, inF )) )
- { /* EOF before end/n = error.*/
- fprintf( stderr,
- "Error: Found End-of-File before end of encoded data.\n" );
- return( FALSE );
- }
- if( strncmp( "end\n", linebufr, 4 ) )
- {
- lncnt++;
- (void)au_cdCleanln( linebufr, au_cdMAX_LINE, tmpbufr, au_cdMAX_LINE );
- cnt = au_cdDecodeln( tmpbufr, linebufr, au_cdMAX_LINE, &ErrCD );
- if( au_cdErr_Checksum == ErrCD )
- ChecksumWarn( lncnt );
- if( 1 != fwrite( linebufr, cnt, 1, fOut ) )
- {
- fprintf( stderr, "Error: Failure writing to output file.\n" );
- perror( "Error" );
- return( FALSE );
- }
- totcnt += cnt;
- if( 0 == (lncnt % 10) )
- fprintf( stderr, "%-30.30s: %9ld\r", name, totcnt );
- }
- else
- break;
- }
- fprintf( stderr, "%-30.30s: %9ld bytes.\n", name, totcnt );
- }
- else
- /* Status "OFF" version (identical to above, but without status output). */
- {
- for(;;)
- {
- if( !(fgets( linebufr, au_cdMAX_LINE, inF )) )
- { /* EOF before end/n = error.*/
- fprintf( stderr,
- "Error: Found End-of-File before end of encoded data.\n" );
- return( FALSE );
- }
- if( strncmp( "end\n", linebufr, 4 ) )
- {
- lncnt++;
- (void)au_cdCleanln( linebufr, au_cdMAX_LINE, tmpbufr, au_cdMAX_LINE );
- cnt = au_cdDecodeln( tmpbufr, linebufr, au_cdMAX_LINE, &ErrCD );
- if( au_cdErr_Checksum == ErrCD )
- ChecksumWarn( lncnt );
- if( 1 != fwrite( linebufr, cnt, 1, fOut ) )
- {
- fprintf( stderr, "Error: Failure writing to output file.\n" );
- perror( "system" );
- return( FALSE );
- }
- totcnt += cnt;
- }
- else
- break;
- }
- }
-
- if( !StdOutFlg )
- {
- fclose( fOut );
- /* mode handling goes here. */
- /* chmod( name, mode ); */
- }
-
- if( fgets( linebufr, au_cdMAX_LINE, inF ) ) /* Try to read past "end". */
- size = ParseSize(); /* ...and read "size" line. */
-
- if( size && (size != totcnt) )
- {
- fprintf( stderr, "Warning: Decoded size [%d] ", totcnt );
- fprintf( stderr, "does not match original size [%d].\n", size );
- }
-
- return( TRUE );
- } /* Decode */
-
- long ScanFile( FILE *inF )
- /* ------------------------------------------------------------------------ **
- * This function scans the input file (inF) for 'begin' lines (which
- * indicate the start of a uuencoded file). For each 'begin' line, this
- * function calls either Decode() or Index() to process the encoded file.
- *
- * Input: inF - Input file handle.
- *
- * Output: The number of encoded files found and processed.
- * ------------------------------------------------------------------------ **
- */
- {
- boolean tmp = TRUE; /* Did Decode() or Index() succeed/fail? */
- long count = 0; /* Number of files processed. */
-
- while( fgets( linebufr, au_cdMAX_LINE, inF ) ) /* While not EOF */
- {
- while( 0 == strncmp( "begin", linebufr, 5 ) )
- {
- /* While current line is "begin". */
- count++;
- if( (0 != SelectOcc) && (SelectOcc != count) )
- {
- /* If we've selected a specific occurance, and this isn't it, then
- * skip this one.
- */
- if( !fgets( linebufr, au_cdMAX_LINE, inF ) )
- return( count ); /* return if there are no more to be done. */
- }
- else
- {
- /* These functions will each read one entire encoded block. */
- if( DecodeFlg )
- {
- Decode( inF );
- if( badcsums > 0 )
- fprintf( stderr, "Warning: There were %d checksum errors.\n",
- badcsums );
- }
- else
- {
- printf( "%5d ", count ); /* Print index number. */
- Index( inF );
- }
- /* Exit on error or if we've found & processed the selected occurance.*/
- if( !tmp || (SelectOcc == count) )
- return( count );
- } /* else */
- } /* while */
- } /* while */
-
- return( count );
- } /* ScanFile */
-
- int main( int argc, char *argv[] )
- /* ------------------------------------------------------------------------ **
- * Mainline.
- * ------------------------------------------------------------------------ **
- */
- {
- FILE *inF = stdin;
- long count;
- long t = time( NULL );
-
- if( !ParseCmdLine( argc, argv ) ) /* Parse the command line. */
- return( 0 ); /* If parse failed, exit. */
-
- if( (OutFlName) && (0 == SelectOcc) ) /* Default action. If the user */
- SelectOcc = 1; /* specified an output file name, */
- /* but didn't specify a specific */
- /* file occurance, we assume file */
- /* occurance 1. */
-
- if( SrcFlName ) /* Open user-specified source file*/
- {
- inF = fopen( SrcFlName, "rb" );
- if( !(inF) )
- {
- fprintf( stderr, "Error: Unable to open file \"%s\".\n", SrcFlName );
- return( 0 );
- }
- }
-
- if( !(DecodeFlg) ) /* Display index header. */
- {
- printf( "\nSource File: %s\n", (SrcFlName)?(SrcFlName):("Standard Input") );
- printf( "Index Name Mode" );
- printf( " Bytes Reported Bytes\n" );
- printf( "----- ----------------------------------- ----" );
- printf( " --------- --------------\n" );
- }
-
- count = ScanFile( inF ); /* Scan the input file for encoded files. */
-
- if( SrcFlName ) /* Close the input file. */
- fclose( inF );
-
- if( StatusFlg )
- {
- t = time( NULL ) - t;
- fprintf( stderr, "\nTotal: %d encoded files processed ", count );
- switch( t )
- {
- case 0: fprintf( stderr, "in less than one second.\n" );
- break;
- case 1: fprintf( stderr, "in 1 second.\n" );
- break;
- default: fprintf( stderr, "in %ld seconds.\n", t );
- }
- }
-
- return( 0 );
- } /* main */
-
- /* ========================================================================== */
-