/* $Id: mkentry.c,v 1.3 2004/01/27 06:53:08 chongo Exp $ */
/*
 * Copyright (c) Leonid A. Broukhis, Simon Cooper, Landon Curt Noll and
 * Peter Seebach, 2004.
 *
 * All Rights Reserved.  Permission for personal, education or non-profit use
 * is granted provided this this copyright and notice are included in its
 * entirety and remains unaltered.  All other uses must receive prior
 * permission in writing from the contest judges.
 */
/*
 * mkentry - make an International Obfuscated C Code Contest entry
 *
 * usage:
 *	mkentry -r remarks -b build -p prog.c -o ioccc.entry
 *
 *	-r remarks		file with remarks about the entry
 *	-b build		file containing how prog.c should be built
 *	-p prog.c		the obfuscated program source file
 *	-o ioccc.entry		ioccc entry output file
 *
 * compile by:
 *	cc mkentry.c -o mkentry
 */
/*
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/*
 * WARNING:
 *
 * This program attempts to implement the IOCCC rules.  Every attempt
 * has been made to make sure that this program produces an entry that
 * conforms to the contest rules.  In all cases, where this program
 * differs from the contest rules, the contest rules will be used.  Be
 * sure to check with the contest rules before submitting an entry.
 *
 * FOR MORE INFORMATION:
 *
 *   You may contact the judges by sending EMail to the following address:
 *
 *	questions@ioccc.org	(not the address for submitting entries)
 *
 *  Questions and comments about the contest are welcome.
 *
 *  NOTE: You must include the words ``ioccc question'' in the
 *        subject of your EMail message when sending EMail to the judges.
 *
 *  Entries must be sent to:
 *
 *	entry@ioccc.org
 *
 *  but be sure to follow the rules and read the guidelines first!
 *
 *  NOTE: You must include the words ``ioccc entry'' in the subject
 *	  of your EMail when sending in your entry!
 *
 *  The rules and the guidelines may (and often do) change from year to
 *  year.  You should be sure you have the current rules and guidelines
 *  prior to submitting entries.  To obtain them, visit the following URL:
 *
 *	http://www.ioccc.org
 *
 * Because contest rules change from year to year, one should only use this
 * program for the year that it was intended.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

/* logic */
#ifndef TRUE
# define TRUE 1
#endif /* TRUE */
#ifndef FALSE
# define FALSE 0
#endif /* FALSE */
#define EOF_OK TRUE
#define EOF_NOT_OK FALSE

/* global limits */
#define RULE_YEAR 2004		/* NOTE: should match the current year */
#define START_DATE "07Jan2004 00:00 UTC" /* first confirmation received */
#define MAX_COL 79		/* max column a line should hit */
#define MAX_BUILD_SIZE 521	/* max how to build size */
#define MAX_PROGRAM_SIZE 4096	/* max program source size */
#define MAX_PROGRAM_SIZE2 2048	/* max program source size not counting
				   whitespace and {}; not followed by
				   whitespace or EOF */
#define MAX_TITLE_LEN 31	/* max chars in the title */
#define MAX_ENTRY_LEN 1		/* max length in the entry input line */
#define MAX_ENTRY 8		/* max number of entries per person per year */
#define MAX_FILE_LEN 1024	/* max filename length for a info file */

/* where to send entries */
#define ENTRY_USER "e.2004"
#define ENTRY_HOST "ioccc.org"

/* uuencode process - assumes ASCII */
#define UUENCODE(c) ((c) ? encode_str[(int)(c)&0x3f] : '`')
#define UUENCODE_LEN 45		/* max uuencode chunk size */
#define UUINFO_MODE 0444	/* mode of an info file's uuencode file */
#define UUBUILD_MODE 0444	/* mode of the build file's uuencode file */
#define UUBUILD_NAME "build"	/* name for the build file's uuencode file */
#define UUPROG_MODE 0444	/* mode of the program's uuencode file */
#define UUPROG_NAME "prog.c"	/* name for the program's uuencode file */

/* encode_str[(char)val] is the uuencoded character of val */
char encode_str[] = "`!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";

/* global declarations */
char *program;			/* our name */
long start_time;		/* the startup time */

/* forward declarations */
void parse_args(int argc, char **argv, char **rname,
	        char **bname, char **pname, char **oname);
void usage(int exitval);
FILE *open_remark(char *filename);
FILE *open_build(char *filename);
FILE *open_program(char *filename);
FILE *open_output(char *filename);
void output_entry(FILE *output, char *oname);
void output_remark(FILE *output, char *oname, FILE *remark, char *rname);
void output_author(FILE *output, char *oname);
void output_info(FILE *output, char *oname);
void output_build(FILE *output, char *oname, FILE *build, char *bname);
void output_program(FILE *output, char *oname, FILE *prog, char *pname);
void output_end(FILE *output, char *oname);
int get_line(char *buf, int siz, int maxcol);
void output_till_dot(FILE *output, char *oname, char *leader);
int col_len(char *string);
void check_io(FILE *stream, char *name, int eof_ok);
void uuencode(FILE *output, char *oname, FILE *infile,
	      char *iname, int umode, char *uname);

int
main(argc, argv)
    int argc;		/* arg count */
    char **argv;	/* the args */
{
    FILE *remark=NULL;	/* open remarks stream */
    FILE *build=NULL;	/* open build file stream */
    FILE *prog=NULL;	/* open program stream */
    FILE *output=NULL;	/* open output stream */
    char *rname=NULL;	/* file with remarks about the entry */
    char *bname=NULL;	/* file containing how prog.c should be built */
    char *pname=NULL;	/* the obfuscated program source file */
    char *oname=NULL;	/* ioccc entry output file */
    struct tm *tm;	/* startup time structure */

    /*
     * check on the year
     */
    start_time = time((long *)0);
    tm = gmtime(&start_time);
    if (tm->tm_year != RULE_YEAR-1900 &&
	(tm->tm_year != RULE_YEAR-1900+1 || tm->tm_mon != 0)) {
	fprintf(stderr,
	"%s: WARNING: this program applies to %d, which may differ from %d\n\n",
	    argv[0], RULE_YEAR, 1900+tm->tm_year);
    }

    /*
     * parse the command line args
     */
    parse_args(argc, argv, &rname, &bname, &pname, &oname);

    /*
     * open/check the input and output files
     *
     * We open and truncate the output file first, in case it is the same
     * as one of the input files.
     */
    output = open_output(oname);
    remark = open_remark(rname);
    build = open_build(bname);
    prog = open_program(pname);
    if (output==NULL || remark==NULL || build==NULL || prog==NULL) {
	exit(1);
    }

    /*
     * output each section
     */
    output_entry(output, oname);
    output_remark(output, oname, remark, rname);
    output_author(output, oname);
    output_info(output, oname);
    output_build(output, oname, build, bname);
    output_program(output, oname, prog, pname);
    output_end(output, oname);

    /*
     * flush the output
     */
    if (fflush(output) == EOF) {
	fprintf(stderr, "%s: flush error in %s: ", program, oname);
	perror("");
	exit(2);
    }

    /*
     * final words
     */
    printf("\nYour entry can be found in %s.  You should check this file\n",
	oname);
    printf("correct any problems and verify that the uudecode utility will\n");
    printf("correctly decode your build file and program.\n\n");
    printf("This program has been provided as a guide for submitters.  In\n");
    printf("cases where it conflicts with the rules, the rules shall apply.\n");
    printf("It is your responsibility to ensure that your entry conforms to\n");
    printf("the current rules.\n\n");
    printf("EMail your entries to:\n");
    printf("\t%s@%s\n\n", ENTRY_USER, ENTRY_HOST);
    printf("IMPORTANT NOTE: You must include the words:\n");
    printf("\tioccc entry\n\n");
    printf("in the subject of your EMail when sending in your entry!\n");
    printf("Failure to do so may result in the loss of your entry!\n");
    /* all done */
    return 0;
}

/*
 * parse_args - parse the command line args
 *
 * usage:
 *	argc	arg count
 *	argv	the args
 *	rname	file with remarks about the entry
 *	bname	file containing how prog.c should be built
 *	pname	the obfuscated program source file
 *	oname	ioccc entry output file
 *
 * Given the command line args, this function parses them and sets the
 * required name flags.  This function will return only if the command
 * line syntax is correct.
 */
void
parse_args(int argc, char **argv, char **rname,
	   char **bname, char **pname, char **oname)
{
    char *optarg;	/* -flag option operand */
    int flagname;	/* the name of the -flag */
    int i;

    /*
     * Not everyone has getopt, so we must parse args by hand.
     */
    program = argv[0];
    for (i=1; i < argc; ++i) {

	/* determine the flagname */
	if (argv[i][0] != '-') {
	    usage(1);
	    /*NOTREACHED*/
	}
	flagname = (int)argv[i][1];

	/* determine the flag's operand */
	if (flagname != '\0' && argv[i][2] != '\0') {
	    optarg = &argv[i][2];
	} else {
	    if (i+1 >= argc) {
		usage(2);
		/*NOTREACHED*/
	    } else {
		optarg = argv[++i];
	    }
	}

	/* save the flag's operand in the correct global variable */
	switch (flagname) {
	case 'r':
	    *rname = optarg;
	    break;
	case 'b':
	    *bname = optarg;
	    break;
	case 'p':
	    *pname = optarg;
	    break;
	case 'o':
	    *oname = optarg;
	    break;
	default:
	    usage(3);
	    /*NOTREACHED*/
	}
    }

    /*
     * verify that we have all of the required flags
     */
    if (*rname == NULL || *bname == NULL || *pname == NULL || *oname == NULL) {
	usage(4);
	/*NOTREACHED*/
    }
    return;
}

/*
 * usage - print a usage message and exit
 *
 * usage:
 *	exitval		exit with this value
 *
 * This function does not return.
 */
void
usage(int exitval)
{
    fprintf(stderr,
	"usage: %s -r remarks -b build -p prog.c -o ioccc.entry\n\n", program);
    fprintf(stderr, "\t-r remarks\tfile with remarks about the entry\n");
    fprintf(stderr, "\t-b build\tfile containing how prog.c should be built\n");
    fprintf(stderr, "\t-p prog.c\tthe obfuscated program source file\n");
    fprintf(stderr, "\t-o ioccc.entry\tioccc entry output file\n");
    exit(exitval);
}

/*
 * open_remark - open/check the remark file
 *
 * usage:
 *	filename	remark filename
 *
 * The remark file should be indented by 4 spaces, and should not extend
 * beyond column MAX_COL.  These are not requirements, so we only warn.
 *
 * This function returns NULL on I/O or format error.
 */
FILE *
open_remark(char *filename)
{
    FILE *stream;		/* the opened file stream */
    char buf[BUFSIZ+1];		/* input buffer */
    int toolong=0;		/* number of lines that are too long */
    int non_indent=0;		/* number of lines not indented by 4 spaces */

    /*
     * open the remark input file
     */
    stream = fopen(filename, "r");
    if (stream == NULL) {
	fprintf(stderr, "%s: cannot open remark file: %s: ",
	    program, filename);
	perror("");
	return(NULL);
    }

    /*
     * look at each line
     */
    while (fgets(buf, BUFSIZ, stream) != NULL) {

	/* count lines that do not start with 4 spaces */
	if (buf[0] != '\n' && strncmp(buf, "    ", 4) != 0) {
	    ++non_indent;
	}

	/* count long lines */
	if (col_len(buf) > MAX_COL) {
	    /* found a line that is too long */
	    ++toolong;
	}
    }

    /* watch for I/O errors */
    check_io(stream, filename, EOF_OK);

    /* note long lines if needed */
    if (toolong > 0) {
	fprintf(stderr,
	    "%s: WARNING: %d line(s) from %s extend beyond the 80th column\n",
	    program, toolong, filename);
	fprintf(stderr,
	    "%s:          This is ok, but it would be nice to avoid\n\n",
	    program);
    }

    /* note non-indented lines, if needed */
    if (non_indent > 0) {
	fprintf(stderr,
	    "%s: WARNING: %d line(s) from %s are not indented by 4 spaces\n",
	    program, non_indent, filename);
	fprintf(stderr,
	    "%s:          This is ok, but it would be nice to avoid\n\n",
	    program);
    }

    /* return the open file */
    rewind(stream);
    return(stream);
}

/*
 * open_build - open/check the build file
 *
 * usage:
 *	filename	build filename
 *
 * The how to build file must not be longer than MAX_BUILD_SIZE bytes.
 *
 * This function returns NULL on I/O or size error.
 */
FILE *
open_build(char *filename)
{
    FILE *stream;		/* the opened file stream */
    struct stat statbuf;	/* the status of the open file */

    /*
     * open the how to build input file
     */
    stream = fopen(filename, "r");
    if (stream == NULL) {
	fprintf(stderr, "%s: cannot open how to build file: %s: ",
	    program, filename);
	perror("");
	return(NULL);
    }

    /*
     * determine the size of the file
     */
    if (fstat(fileno(stream), &statbuf) < 0) {
	fprintf(stderr, "%s: cannot stat how to build file: %s: ",
	    program, filename);
	perror("");
	return(NULL);
    }
    if (statbuf.st_size > MAX_BUILD_SIZE) {
	fprintf(stderr,
	    "%s: FATAL: the how to build file: %s, is %ld bytes long\n",
	    program, filename, (long) statbuf.st_size);
	fprintf(stderr,
	    "%s:        it may not be longer than %d bytes\n",
	    program, MAX_BUILD_SIZE);
	return(NULL);
    }

    /* return the open file */
    return(stream);
}

/*
 * open_program - open/check the program source file
 *
 * usage:
 *	filename	source filename
 *
 * The program source file must be <= 3217 bytes.  The number of
 * non-whitespace and }{; chars not followed by whitespace must
 * be <= 1536 bytes.
 *
 * This function returns NULL on I/O or size error.
 */
FILE *
open_program(char *filename)
{
    FILE *stream;		/* the opened file stream */
    struct stat statbuf;	/* the status of the open file */
    int count;			/* special count size */
    int c;			/* the character read */
    int control_m;		/* 1 ==> warn about trailing ^M's */

    /*
     * open the program source input file
     */
    stream = fopen(filename, "r");
    if (stream == NULL) {
	fprintf(stderr, "%s: cannot open program source file: %s: ",
	    program, filename);
	perror("");
	exit(7);
    }

    /*
     * determine the size of the file
     */
    if (fstat(fileno(stream), &statbuf) < 0) {
	fprintf(stderr, "%s: cannot stat program source file: %s: ",
	    program, filename);
	perror("");
	return(NULL);
    }
    if (statbuf.st_size > MAX_PROGRAM_SIZE) {
	fprintf(stderr,
	    "%s: FATAL: the program source file: %s, is %ld bytes long\n",
	    program, filename, (long) statbuf.st_size);
	fprintf(stderr,
	    "%s:        it may not be longer than %d bytes\n",
	    program, MAX_PROGRAM_SIZE);
	return(NULL);
    }

    /*
     * count the non-whitespace, non {}; followed by whitespace chars
     */
    control_m = 0;
    count = 0;
    c = 0;
    while ((c=fgetc(stream)) != EOF) {

	/* look at non-whitespace */
	if (isascii(c) && !isspace(c) && c != '\r') {
	    switch (c) {
	    case '{':		/* count if not followed by EOF or whitespace */
	    case '}':
	    case ';':
		/* peek at next char */
		c = fgetc(stream);
		if (c != EOF && isascii(c) && !isspace(c) && c != '\r') {
		    /* not followed by whitespace or EOF, count it */
		    ungetc(c, stream);
		    ++count;
		}
		break;
	    default:
		++count;
		break;
	    }

	/* look for trailing ^M's */
	} else if (c == '\r') {
	    /* peek at next char */
	    c = fgetc(stream);
	    if (c != EOF && c == '\n') {
		control_m = 1;
	    }
	    /* put the peeking char back */
	    ungetc(c, stream);
	}
    }

    /* watch for I/O errors */
    check_io(stream, filename, EOF_OK);

    /* look at the special size */
    if (count > MAX_PROGRAM_SIZE2) {
	fprintf(stderr,
	    "%s: FATAL: the number of bytes that are non-whitespace, and\n",
	    program);
	fprintf(stderr,
	    "%s:        that are not '{', '}', ';' followed by whitespace\n",
	    program);
	fprintf(stderr,
	    "%s:        or EOF must be <= %d bytes\n",
	    program, MAX_PROGRAM_SIZE2);
	fprintf(stderr,
	    "%s:        in %s, %d bytes were found\n",
	    program, filename, count);
	return(NULL);
    }

    /* warn about trailing ^M's */
    if (control_m) {
	fprintf(stderr,
    	    "\nWARNING: Trailing Control-M's (\\r or \\015) found.\n"
	    "\t If these chars result in a compilation failure,\n"
	    "\t your entry may be rejected\n\n");
    }

    /* return the open file */
    rewind(stream);
    return(stream);
}

/*
 * open_output - open/check the entry output file
 *
 * usage:
 *	filename	output filename
 *
 * This function returns NULL on open error.
 */
FILE *
open_output(char *filename)
{
    FILE *stream;		/* the opened file stream */

    /*
     * open the ioccc entry output file
     */
    stream = fopen(filename, "w");
    if (stream == NULL) {
	fprintf(stderr, "%s: cannot open ioccc entry file for output: %s: ",
	    program, filename);
	perror("");
	exit(8);
    }

    /* return the open file */
    return(stream);
}

/*
 * output_entry - output the ---entry--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *
 * Read the needed information form stdin, and write the entry section.
 */
void
output_entry(FILE *output, char *oname)
{
    char title[MAX_TITLE_LEN+1+1];	/* the entry's title */
    char buf[MAX_COL+1+1];		/* I/O buffer */
    int entry=0;			/* entry number */
    int ret;				/* fields processed by fscanf */
    int ok_line=0;			/* 0 => the line is not ok */
    char skip;				/* input to skip */
    time_t epoch_sec;			/* seconds since the epoch */
    char *p;

    /*
     * write the start of the section
     */
    fprintf(output, "---entry---\n");
    check_io(output, oname, EOF_NOT_OK);

    /*
     * write the rule year
     */
    fprintf(output, "rule:\t%d\n", RULE_YEAR);
    check_io(output, oname, EOF_NOT_OK);

    /* determine if this is a fix */
    printf("Is this a fix, update or resubmittion to a ");
    printf("previous entry (enter y or n)? ");
    while (get_line(buf, 1+1, 0) <= 0 || !(buf[0]=='y' || buf[0]=='n')) {
	printf("\nplease answer y or n: ");
    }
    if (buf[0] == 'y') {
	fprintf(output, "fix:\ty\n");
	check_io(output, oname, EOF_NOT_OK);
	printf("\nBe sure that the title and entry number that you give\n");
	printf("are the same of as the entry you are replacing\n");
    } else {
	fprintf(output, "fix:\tn\n");
	check_io(output, oname, EOF_NOT_OK);
    }

    /*
     * write the title
     */
    printf("\nThe first character of your title should match [a-zA-Z0-9_=]\n");
    printf("The next 0 to %d characters should match [a-zA-Z0-9_=+-]\n\n",
	MAX_TITLE_LEN-1);
    printf("It is suggested, but not required, that the title should\n");
    printf("incorporate your username; in the case of multiple authors,\n");
    printf("consider using parts of the usernames of the authors.\n\n");
    printf("enter your title: ");
    do {
	/* prompt and read a line */
	if ((ok_line = get_line(title, MAX_TITLE_LEN+1, MAX_COL-9)) <= 0) {
	    printf("\ntitle is too long, please re-enter: ");
	    continue;
	}

	/* verify the pattern, not everyone has regexp, so do it by hand */
	if (!isascii((int)title[0]) ||
	    !(isalnum((int)title[0]) || title[0] == '_' || title[0] == '=')) {
	    printf("\ninvalid first character in the title\n\n");
	    printf("enter your title: ");
	    ok_line = 0;
	} else {
	    for (p=(&title[1]); *p != '\0' && *p != '\n'; ++p) {
		if (!isascii((int)*p) ||
		    !(isalnum((int)*p) ||
		      *p == '_' || *p == '=' || *p == '+' || *p == '-')) {
		    printf("\ninvalid character in the title\n\n");
		    printf("enter your title: ");
		    ok_line = 0;
		}
	    }
	}
    } while (ok_line <= 0);
    fprintf(output, "title:\t%s", title);
    check_io(output, oname, EOF_NOT_OK);

    /*
     * write the entry number
     */
    printf("\nEach person may submit up to %d entries per year.\n\n",
	MAX_ENTRY);
    printf("enter an entry number from 0 to %d inclusive: ", MAX_ENTRY-1);
    do {
	/* get a valid input line */
	fflush(stdout);
	ret = fscanf(stdin, "%d[\n]", &entry);
	check_io(stdin, "stdin", EOF_NOT_OK);
	/* skip over input until newline is found */
	do {
	    skip = fgetc(stdin);
	    check_io(stdin, "stdin", EOF_NOT_OK);
	    if (skip != '\n') {
		/* bad text in input, invalidate entry number */
		entry = -1;
	    }
	} while (skip != '\n');

	/* check if we have a number, and if it is in range */
	if (ret != 1 || entry < 0 || entry > MAX_ENTRY-1) {
	    printf(
	      "\nThe entry number must be between 0 and %d inclusive\n\n",
		MAX_ENTRY-1);
	    printf("enter the entry number: ");
	}
    } while (ret != 1 || entry < 0 || entry > MAX_ENTRY-1);
    fprintf(output, "entry:\t%d\n", entry);
    check_io(output, oname, EOF_NOT_OK);

    /*
     * write the submission date
     */
    /* returns a newline */
    epoch_sec = time(NULL);
    fprintf(output, "date:\t%s", asctime(gmtime(&epoch_sec)));
    check_io(output, oname, EOF_NOT_OK);

    /*
     * write the OS/machine host information
     */
    printf(
      "\nEnter the machine(s) and OS(s) under which your entry was tested.\n");
    output_till_dot(output, oname, "host:");
}

/*
 * output_remark - output the ---remark--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *	remark		stream to the file containing remark text
 *	rname		name of the remark file
 *
 * Read the needed information form stdin, and write the entry section.
 */
void
output_remark(FILE *output, char *oname, FILE *remark, char *rname)
{
    char buf[BUFSIZ+1];		/* input/output buffer */

    /*
     * write the start of the section
     */
    fprintf(output, "---remark---\n");
    check_io(output, oname, EOF_NOT_OK);

    /*
     * copy the remark file to the section
     */
    while (fgets(buf, BUFSIZ, remark) != NULL) {
	fputs(buf, output);
	check_io(output, oname, EOF_NOT_OK);
    }
    check_io(remark, rname, EOF_OK);

    /* be sure that the remark section ends with a newline */
    if (buf[strlen(buf)-1] != '\n') {
	fputc('\n', output);
	check_io(output, oname, EOF_NOT_OK);
    }
}

/*
 * output_author - output the ---author--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *
 * Read the needed information from stdin, and write the author section.
 * If multiple authors exist, multiple author sections will be written.
 */
void
output_author(FILE *output, char *oname)
{
    char buf[MAX_COL+1+1];	/* I/O buffer */
    int more_auths;		/* TRUE => more authors to note */
    int auth_cnt=0;		/* number of authors processed */

    /*
     * prompt the user for the author section
     */
    printf("\nEnter information about each author.  If your entry is after\n");
    printf("%s and before the contest deadline, the judges\n", START_DATE);
    printf("will attempt to EMail back a confirmation to the first author\n");

    /*
     * place author information for each author in an individual section
     */
    do {

	/* write the start of the section */
	fprintf(output, "---author---\n");
	check_io(output, oname, EOF_NOT_OK);

	/* write the author */
	printf("\nAuthor #%d name: ", ++auth_cnt);
	while (get_line(buf, MAX_COL+1, MAX_COL-9) <= 0) {
	    printf("\nname too long, please re-enter: ");
	}
	fprintf(output, "name:\t%s", buf);
	check_io(output, oname, EOF_NOT_OK);

	/* write the organization */
	printf("\nEnter the School/Company/Organization of author #%d\n",
	    auth_cnt);
	printf("\nAuthor #%d org: ", auth_cnt);
	while (get_line(buf, MAX_COL+1, MAX_COL-9) <= 0) {
	    printf("\nline too long, please re-enter: ");
	}
	fprintf(output, "org:\t%s", buf);
	check_io(output, oname, EOF_NOT_OK);

	/* write the address */
	printf(
	    "\nEnter the postal address for author #%d.  Be sure to include\n",
	    auth_cnt);
	printf("your country and do not include your name.\n");
	output_till_dot(output, oname, "addr:");

	/* write the EMail address */
	printf(
	    "\nEnter the EMail address for author #%d.  Use an address from\n",
	    auth_cnt);
	printf(
	    "a registered domain or well known site.  If you give several\n");
	printf("forms, list them one per line.\n");
	output_till_dot(output, oname, "email:");

	/* write the home page URL */
	printf(
	    "\nEnter the fully qualified home page URL for author #%d\n",
	    auth_cnt);
	printf("including the http: part or just enter none: ");
	while (get_line(buf, MAX_COL+1, MAX_COL-9) <= 0 ||
	       (strncmp(buf, "http://", sizeof("http://")-1) != 0 &&
		strcmp(buf, "none\n") != 0)) {
	      printf("\nURL too long, does not begin with http:// or\n");
	      printf("is not the word none\n");
	}
	fprintf(output, "url:\t%s", buf);
	check_io(output, oname, EOF_NOT_OK);

	/* write the anonymous status */
	printf("\nShould author #%d remain anonymous (enter y or n)? ",
	    auth_cnt);
	while (get_line(buf, 1+1, 0) <= 0 || !(buf[0]=='y' || buf[0]=='n')) {
	    printf("\nplease answer y or n: ");
	}
	fprintf(output, "anon:\t%s", buf);
	check_io(output, oname, EOF_NOT_OK);

	/* determine if there is another author */
	printf("\nIs there another author (enter y or n)? ");
	while (get_line(buf, 1+1, 0) <= 0 || !(buf[0]=='y' || buf[0]=='n')) {
	    printf("\nplease answer y or n: ");
	}
	if (buf[0] == 'y') {
	    more_auths = TRUE;
	} else {
	    more_auths = FALSE;
	}
    } while (more_auths == TRUE);
    return;
}

/*
 * output_info - output the ---info--- section(s)
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *
 * Read the needed information from stdin, and write the info section.
 * If multiple info files exist, multiple info sections will be written.
 */
void
output_info(FILE *output, char *oname)
{
    char infoname[MAX_FILE_LEN+1];	/* filename buffer */
    char yorn[1+1];		/* y or n answer */
    char *uuname;		/* name to uuencode as */
    FILE *infile;		/* info file stream */

    /*
     * prompt the user for info information
     */
    printf("\nInfo files should be used only to supplement your entry.\n");
    printf("For example, info files may provide sample input or detailed\n");
    printf("information about your entry.  Because they are supplemental,\n");
    printf("the entry should not require them to exist.\n\n");

    /*
     * while there is another info file to save, uuencode it
     */
    printf("Do you have a info file to include (enter y or n)? ");
    while (get_line(yorn, 1+1, 0) <= 0 || !(yorn[0]=='y' || yorn[0]=='n')) {
	printf("\nplease answer y or n: ");
    }
    while (yorn[0] == 'y') {

	/* read the filename */
	printf("\nEnter the info filename: ");
	while (get_line(infoname, MAX_FILE_LEN+1, 0) <= 0) {
	    printf("\nInfo filename too long, please re-enter: ");
	}

	/* compute the basename of the info filename */
	/* remove the trailing newline */
	uuname = &infoname[strlen(infoname)-1];
	*uuname = '\0';
	/* avoid rindex/shrrchr compat issues, do it by hand */
	for (--uuname; uuname > infoname; --uuname) {
	    if (*uuname == '/') {
		++uuname;
		break;
	    }
	}

	/* attempt to open the info file */
	infile = fopen(infoname, "r");
	if (infile == NULL) {
	    fprintf(stderr, "\n%s: cannot open info file: %s: ",
		program, infoname);
	    perror("");
	    continue;
	}

	/*
	 * write the start of the section
	 */
	fprintf(output, "---info---\n");
	check_io(output, oname, EOF_NOT_OK);

	/* uuencode the info file */
	uuencode(output, oname, infile, infoname, UUINFO_MODE, uuname);

	printf("\nDo you have another info file to include (enter y or n)? ");
	while (get_line(yorn, 1+1, 0) <= 0 || !(yorn[0]=='y' || yorn[0]=='n')) {
	    printf("\nplease answer y or n: ");
	}
    };
    return;
}

/*
 * output_build - output the ---build--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *	build		open build file stream
 *	bname		name of the build file
 *
 * Read the needed information from stdin, and write the build section.
 */
void
output_build(FILE *output, char *oname, FILE *build, char *bname)
{
    /*
     * write the start of the section
     */
    fprintf(output, "---build---\n");
    check_io(output, oname, EOF_NOT_OK);

    /*
     * uuencode the program file
     */
    uuencode(output, oname, build, bname, UUBUILD_MODE, UUBUILD_NAME);
    return;
}

/*
 * output_program - output the ---program--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *	prog		open program stream
 *	pname		name of program file
 *
 * Read the needed information form stdin, and write the program section.
 */
void
output_program(FILE *output, char *oname, FILE *prog, char *pname)
{
    /*
     * write the start of the section
     */
    fprintf(output, "---program---\n");
    check_io(output, oname, EOF_NOT_OK);

    /*
     * uuencode the program file
     */
    uuencode(output, oname, prog, pname, UUPROG_MODE, UUPROG_NAME);
    return;
}

/*
 * output_end - output the ---end--- section
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *
 * Read the needed information form stdin, and write the 'end section'.
 */
void
output_end(FILE *output, char *oname)
{
    /*
     * write the final section terminator
     */
    fprintf(output, "---end---\n");
    check_io(output, oname, EOF_NOT_OK);
    return;
}

/*
 * get_line - get an answer from stdin
 *
 * usage:
 *	buf		input buffer
 *	siz		length of input, including the newline
 *	maxcol		max col allowed, 0 => disable check
 *
 * This function will flush stdout, in case a prompt is pending, and
 * read in the answer.
 *
 * This function returns 0 if the line is too long, of the length of the
 * line (including the newline) of the line was ok.  This function does
 * not return if ERROR or EOF.
 */
int
get_line(char *buf, int siz, int maxcol)
{
    int length;			/* the length of the input line */

    /* flush terminal output */
    fflush(stdout);

    /* read the line */
    if (fgets(buf, siz+1, stdin) == NULL) {
	/* report the problem */
	check_io(stdin, "stdin", EOF_NOT_OK);
    }

    /* look for the newline */
    length = strlen(buf);
    if (buf[length-1] != '\n') {
	int eatchar;		/* the char being eaten */

	/* no newline found, line must be too long, eat the rest of the line */
	do {
	    eatchar = fgetc(stdin);
	} while (eatchar != EOF && eatchar != '\n');
	check_io(stdin, "stdin", EOF_NOT_OK);

	/* report the situation */
	return 0;
    }

    /* watch for long lines, if needed */
    if (maxcol > 0 && (length > maxcol || col_len(buf) > maxcol)) {
	/* report the situation */
	return 0;
    }

    /* return length */
    return length;
}

/*
 * output_till_dot - output a set of lines until '.' by itself is read
 *
 * usage:
 *	output		entry's output file stream
 *	oname		name of the output file
 *	leader		the lead text for the first line
 *
 * This routine will read a set of lines until (but not including)
 * a single line with '.' is read.  The format of the output is:
 *
 *	leader:\tfirst line
 *	\tnext line
 *	\tnext line
 *	   ...
 *
 * This routine will not return if I/O error or EOF.
 */
void
output_till_dot(FILE *output, char *oname, char *leader)
{
    char buf[BUFSIZ+1];		/* input buffer */
    int count;			/* lines read */
    int done=FALSE;		/* TRUE => finished reading input */

    /* instruct the user on how to input */
    printf("\nTo end input, enter a line with a single period.\n");

    /* read lines until '.' or EOF */
    count = 0;
    while (!done) {
	/* issue the prompt */
	printf("%s\t", (count>0) ? "" : leader);
	fflush(stdout);

	/* get the line */
	if (get_line(buf, BUFSIZ, MAX_COL-9) <= 0) {
	    printf("\nline too long, please re-enter:\n\t");
	    continue;
	}

	/* note if '.' was read */
	if (strcmp(buf, ".\n") == 0) {
	    done = TRUE;
	}

	/* write line if we read something */
	if (!done) {
	    fprintf(output, "%s\t%s", (count++>0) ? "" : leader, buf);
	    check_io(output, oname, EOF_NOT_OK);
	}
    }

    /* if no lines read, at least output something */
    if (count <= 0) {
	fprintf(output, "%s\t.\n", leader);
	check_io(output, oname, EOF_NOT_OK);
    }
    return;
}

/*
 * col_len - determine the highest that a string would reach
 *
 * usage:
 *	string		the string to examine
 *
 * Given a string, this routine returns that a string would reach
 * if the string were printed at column 1.  Tab stops are assumed
 * to start at 9, 17, 25, 33, ...
 */
int
col_len(char *string)
{
    int col;	/* current column */
    char *p;	/* current char */

    /* scan the string */
    for (col=0, p=string; *p != '\0' && *p != '\n'; ++p) {
	/* note the column shift */
	col = (*p=='\t') ? 1+((col+8)/8*8) : col+1;
    }
    if (*p == '\n') {
	--col;
    }

    /* return the highest column */
    return col;
}

/*
 * check_io - check for EOF or I/O error on a stream
 *
 * usage:
 *	stream		the stream to check
 *	name		the name of this stream
 *	eof_ok		EOF_OK or EOF_NOT_OK
 *
 * Does not return if EOF or I/O error.
 */
void
check_io(FILE *stream, char *name, int eof_ok)
{
    /* test for I/O error */
    if (ferror(stream)) {
	fprintf(stderr, "%s: error on %s: ", program, name);
	perror("");
	exit(1);

    /* test for EOF */
    } else if (eof_ok == EOF_NOT_OK && feof(stream)) {
	fprintf(stderr, "%s: EOF on %s\n", program, name);
	exit(1);
    }
    return;
}

/*
 * uuencode - uuencode a file
 *
 * usage:
 *	output		output file stream
 *	oname		output filename
 *	infile		input file stream
 *	iname		input filename
 *	umode		the mode to put on the uuencode file
 *	uname		name to put on the uuencode file
 *
 * Perform the uuencoding process identical to the process performed
 * by the uuencode(1) utility.
 *
 * This routine implements the algorithm described in the uuencode(5)
 * 4.3BSD Reno man page.
 */
void
uuencode(FILE *output, char *oname, FILE *infile,
	 char *iname, int umode, char *uname)
{
    char buf[UUENCODE_LEN+1];	/* the uuencode buffer */
    int read_len;		/* actual number of chars read */
    int val;			/* 6 bit chunk from buf */
    char filler='\0';		/* filler uuencode pad text */
    char *p;

    /*
     * output the initial uuencode header
     */
    fprintf(output, "begin %o %s\n", umode, uname);
    check_io(output, oname, EOF_NOT_OK);

    /*
     * clear out the input buffer
     */
    for (p=buf; p < &buf[sizeof(buf)/sizeof(buf[0])]; ++p) {
	*p = '\0';
    }

    /*
     * We will process UUENCODE_LEN chars at a time, forming
     * a single output line each time.
     */
    while ((read_len=fread(buf,sizeof(buf[0]),UUENCODE_LEN,infile)) > 0) {

	/*
	 * the first character is the length character
	 */
	fputc(UUENCODE(read_len), output);
	check_io(output, oname, EOF_NOT_OK);

	/*
	 * We will convert 24 bits at a time.  Thus we will convert
	 * 3 sets of 8 bits into 4 sets of uuencoded 6 bits.
	 */
	for (p=buf; read_len>0; read_len-=3, p+=3) {

	    /* bits 0 to 5 */
	    val = (p[0]>>2)&0x3f;
	    fputc(UUENCODE(val), output);
	    check_io(output, oname, EOF_NOT_OK);

	    /* bits 6 to 11 */
	    val = ((p[0]<<4)&0x30) | ((p[1]>>4)&0x0f);
	    fputc(UUENCODE(val), output);
	    check_io(output, oname, EOF_NOT_OK);

	    /* bits 12 to 17 */
	    val = ((p[1]<<2)&0x3c) | ((p[2]>>6)&0x03);
	    fputc(UUENCODE(val), output);
	    check_io(output, oname, EOF_NOT_OK);

	    /* bits 18 to 23 */
	    val = p[2]&0x3f;
	    fputc(UUENCODE(val), output);
	    check_io(output, oname, EOF_NOT_OK);
	}

	/* end of UUENCODE_LEN line */
	fputc('\n', output);
	check_io(output, oname, EOF_NOT_OK);

	/*
	 * clear out the input buffer  (don't depend on bzero() or memset())
	 */
	for (p=buf; p < &buf[sizeof(buf)/sizeof(buf[0])]; ++p) {
	    *p = '\0';
	}
    }

    /* check the last read on the input file */
    check_io(infile, iname, EOF_OK);

    /* write end of uuencode file */
    fprintf(output, "%c\nend\n", UUENCODE(filler));
    check_io(output, oname, EOF_NOT_OK);
}
