/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1991-1994, 1998-2002, 2004, 2006 Peter Miller;
 *	All rights reserved.
 *
 *	Derived from a work
 *	Copyright (C) 1990 David I. Bell.
 *
 *	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, USA.
 *
 * MANIFEST: operating system entry point, and parse arguments
 *
 * Program to perform source control operations for modules.
 */

#include <ac/ctype.h>
#include <ac/sys/types.h>
#include <ac/stdio.h>
#include <ac/errno.h>
#include <setjmp.h>
#include <ac/dirent.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/unistd.h>
#include <ac/sys/stat.h> /* for mkdir */

#include <arglex.h>
#include <breaks.h>
#include <cmalloc.h>
#include <compare.h>
#include <diff.h>
#include <error_intl.h>
#include <extract.h>
#include <fhist.h>
#include <help.h>
#include <isdir.h>
#include <list.h>
#include <modlin.h>
#include <name.h>
#include <progname.h>
#include <prune.h>
#include <quit.h>
#include <str.h>
#include <subroutine.h>
#include <trace.h>
#include <update.h>
#include <version.h>


/*
 * Actions to be performed for modules
 */
#define A_CREATE	0x0001 /* want to create */
#define A_UPDATE	0x0002 /* want to update */
#define A_EXTRACT	0x0004 /* want to extract */
#define A_LIST		0x0008 /* want to list changes */
#define A_DIFF		0x0010 /* want to list differences */
#define A_TYPE		0x0020 /* want to type output */
#define A_NAME		0x0080 /* want to set edit name */
#define A_CLEAN		0x0100 /* clean away up to date copy of module */
#define A_CONDUPDATE	0x0200 /* do update if there are differences */
#define A_CHECK		0x0400 /* check to see if a file is up to date */
#define A_PRUNE		0x0800 /* prune old edits from edithistory */


/*
 * Definitions of which actions accept various extra arguments
 */
#define REMARK_ACTIONS	(A_CREATE | A_UPDATE | A_CONDUPDATE)
#define NAME_ACTIONS	(REMARK_ACTIONS | A_NAME)
#define INPUT_ACTIONS	(REMARK_ACTIONS | A_DIFF | A_CHECK | A_CLEAN)
#define OUTPUT_ACTIONS	(A_EXTRACT | A_LIST | A_DIFF)


SC_DATA		sc;		/* storage for sc program */


enum
{
    arglex_token_all,
    arglex_token_binary,
    arglex_token_check,
    arglex_token_clean,
    arglex_token_conditional_update,
    arglex_token_create,
    arglex_token_debug,
    arglex_token_difference,
    arglex_token_difference_update,
    arglex_token_extract,
    arglex_token_force_writing,
    arglex_token_forced_update,
    arglex_token_input,
    arglex_token_list,
    arglex_token_make_path,
    arglex_token_modified,
    arglex_token_nname,
    arglex_token_no_write,
    arglex_token_output,
    arglex_token_path,
    arglex_token_prune,
    arglex_token_quick,
    arglex_token_remark,
    arglex_token_remark_string,
    arglex_token_terminal,
    arglex_token_trace,
    arglex_token_update,
    arglex_token_verbose,
    arglex_token_what,
    arglex_token_keywords_not,
};

static arglex_table_ty argtab[] =
{
    {"-All", arglex_token_all, },
    {"-BINary", arglex_token_binary, },
    {"-CLean", arglex_token_clean, },
    {"-CReate", arglex_token_create, },
    {"-Check", arglex_token_check, },
    {"-Conditional_Update", arglex_token_conditional_update, },
    {"-DEbug", arglex_token_debug, },
    {"-Difference", arglex_token_difference, },
    {"-Difference_Update", arglex_token_difference_update, },
    {"-Extract", arglex_token_extract, },
    {"-Force_Writing", arglex_token_force_writing, },
    {"-Forced_Update", arglex_token_forced_update, },
    {"-Input", arglex_token_input, },
    {"-List", arglex_token_list, },
    {"-MaKe_Path", arglex_token_make_path, },
    {"-Modify", arglex_token_modified, },
    {"-Name", arglex_token_nname, },
    {"-No_Keywords", arglex_token_keywords_not, },
    {"-No_Write", arglex_token_no_write, },
    {"-Output", arglex_token_output, },
    {"-PRune", arglex_token_prune, },
    {"-Path", arglex_token_path, },
    {"-Quick", arglex_token_quick, },
    {"-Remark", arglex_token_remark, },
    {"-Remark_String", arglex_token_remark_string, },
    {"-Terminal", arglex_token_terminal, },
    {"-TRace", arglex_token_trace, },
    {"-Update", arglex_token_update, },
    {"-Verbose", arglex_token_verbose, },
    {"-What", arglex_token_what, },
    {0, 0, }, /* end marker */
};


static void
usage(void)
{
    char	    *progname;

    progname = progname_get();
    fprintf(stderr, "usage: %s <filename> <option>...\n", progname);
    fprintf(stderr, "       %s -Help\n", progname);
    fprintf(stderr, "       %s -VERSion\n", progname);
    quit(1);
}


static void
main_help(void)
{
    help(NULL, usage);
}


/*
 * Routine called after errors to restart the program for the next file.
 * This reenables breaks, closes all files, frees all allocated memory,
 * resets static variables, and then possibly longjmps back to the top
 * level code.
 */

static void
restart(int jumpflag)
{
    int		    i;

    breakson();
    cm_reset();
    fcompreset();
    editreset();
    sc.modifybuffer = NULL;
    sc.firstedit = 0;
    sc.lastedit = 0;
    sc.tablepos = 0;
    for (i = 3; i < 10; i++)
	close(i);
}


/*
 * Perform the specified action for a particular module.
 * If the wildcard flag is 1, then any input or output name is a
 * directory which needs to have the module name appended to it.
 */

static void
domodule(int action, char *modulename, char *inputname, char *outputname,
    char *editname, char *editstring, char *editstring2, int wildcard)
{
    int		    module_max;
    char	    fullinputname[1024]; /* full input name if a dir */
    char	    fulloutputname[1024]; /* full output name if a dir */

    trace(("domodule(name = \"%s\")\n{\n", modulename));
    restart(0);
    if (strchr(modulename, PATH_STR[0]))
    {
	sub_context_ty	*scp;

	scp = sub_context_new();
	sub_var_set_charstar(scp, "Module", modulename);
	fatal_intl
	(
	    scp,
	    i18n("module name \"$module\" cannot contain path separators")
	);
	/* NOTREACHED */
	sub_context_delete(scp);
    }
    module_max = pathconf_name_max(sc.dirname) - 2;
    if (strlen(modulename) > module_max)
    {
	sub_context_ty	*scp;

	scp = sub_context_new();
	sub_var_set_charstar(scp, "Module", modulename);
	fatal_intl(scp, i18n("module name \"$module\" is too long"));
	/* NOTREACHED */
	sub_context_delete(scp);
    }
    if (inputname && wildcard)
    {
	strcpy(fullinputname, inputname);
	strcat(fullinputname, PATH_STR);
	strcat(fullinputname, modulename);
	inputname = fullinputname;
    }
    if (outputname && wildcard)
    {
	strcpy(fulloutputname, outputname);
	strcat(fulloutputname, PATH_STR);
	strcat(fulloutputname, modulename);
	outputname = fulloutputname;
    }
    sc.modulename = modulename;
    strcpy(sc.basename, sc.dirname);
    strcat(sc.basename, PATH_STR);
    strcat(sc.basename, modulename);
    trace_string(sc.basename);
    strcpy(sc.sourcename, sc.basename);
    strcat(sc.sourcename, EXT_SOURCE);
    trace_string(sc.sourcename);
    strcpy(sc.historyname, sc.basename);
    strcat(sc.historyname, EXT_HISTORY);
    trace_string(sc.historyname);

    switch (action)
    {
    case A_EXTRACT:
	trace(("extract\n"));
	if (outputname == NULL)
	    outputname = modulename;
	extracthistory(editstring, outputname, (fc.verbosity != 0));
	break;

    case A_CREATE:
	trace(("create\n"));
	if (inputname == NULL)
	    inputname = modulename;
	createhistory(inputname, editname);
	break;

    case A_DIFF:
	if (inputname && editstring2)
	{
	    fatal_intl(0, i18n("conflicting options for differences"));
	}
	if (inputname == NULL)
	    inputname = modulename;
	diffhistory(inputname, outputname, editstring, editstring2, 1);
	break;

    case A_DIFF | A_UPDATE:
	if (inputname == NULL)
	    inputname = modulename;
	diffhistory(inputname, outputname, editstring, editstring2, 0);
	if ((fc.inserts == 0) && (fc.deletes == 0))
	{
	    if (fc.verbosity)
	    {
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_var_set_charstar(scp, "Module", modulename);
		error_intl
		(
		    scp,
		    i18n("module \"$module\" does not need updating")
		);
		sub_context_delete(scp);
	    }
	    break;
	}
	printf("\n");
	updatehistory(inputname, editname);
	break;

    case A_CREATE | A_CONDUPDATE:
	trace(("creaye | condupdate\n"));
	if (inputname == NULL)
	    inputname = modulename;
	if (!history_file_exists())
	{
	    createhistory(inputname, editname);
	    break;
	}
	/* fall through... */

    case A_CONDUPDATE:
	trace(("condupdate\n"));
	if (inputname == NULL)
	    inputname = modulename;
	comphistory(inputname);
	if ((fc.inserts == 0) && (fc.deletes == 0))
	{
	    if (fc.verbosity)
	    {
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_var_set_charstar(scp, "Module", modulename);
		error_intl
		(
		    scp,
		    i18n("module \"$module\" does not need updating")
		);
		sub_context_delete(scp);
	    }
	    break;
	}
	updatehistory(inputname, editname);
	break;

    case A_CREATE | A_UPDATE:
	trace(("create | update\n"));
	if (inputname == NULL)
	    inputname = modulename;
	if (!history_file_exists())
	{
	    createhistory(inputname, editname);
	    break;
	}
	/* fall through... */

    case A_UPDATE:
	trace(("update\n"));
	if (inputname == NULL)
	    inputname = modulename;
	updatehistory(inputname, editname);
	break;

    case A_LIST:
	trace(("list\n"));
	listhistory(editstring, editstring2, outputname);
	break;

    case A_NAME:
	trace(("name\n"));
	namehistory(editname);
	break;

    case A_TYPE:
	trace(("type\n"));
	extracthistory(editstring, outputname, VERBOSE_NONE);
	break;

    case A_CLEAN:
	trace(("clean\n"));
	if (inputname == NULL)
	    inputname = modulename;
	cleanhistory(inputname);
	break;

    case A_CHECK:
	trace(("check\n"));
	if (inputname == NULL)
	    inputname = modulename;
	checkhistory(inputname);
	break;

    case A_PRUNE:
	trace(("prune\n"));
	prunehistory(editstring);
	break;
    }
    trace(("}\n"));
}


/*
 * Perform the specified action for all modules in the edit history directory.
 */

static void
doallmodules(int action, char *inputname, char *outputname, char *editstring,
    char *editstring2, char *editname)
{
    char	    *modulename;	/* current file name */
    DIR		    *dirp;		/* directory handle */
    struct dirent   *dp;		/* current directory entry */
    int		    len;		/* length of filename */
    char	    *buffer;		/* buffer for name storage */
    unsigned long   bufsize;		/* current size of buffer */
    long	    bufindex;		/* index into buffer */

    bufindex = 0;
    bufsize = 1000;
    buffer = r_alloc_and_check(bufsize);
    dirp = opendir(sc.dirname);
    if (dirp == NULL)
    {
	sub_context_ty	*scp;

	scp = sub_context_new();
	sub_errno_set(scp);
	sub_var_set_charstar(scp, "File_Name", sc.dirname);
	fatal_intl(scp, i18n("opendir $filename: $errno"));
	/* NOTREACHED */
	sub_context_delete(scp);
    }
    while ((dp = readdir(dirp)) != 0)
    {
	modulename = dp->d_name;
	len = strlen(modulename) - 2;
	if (len <= 0)
	    continue;
	if ((modulename[len] != '.') || (modulename[len + 1] != 'e'))
	    continue;
	modulename[len] = '\0';
	if ((bufindex + len + 2) > bufsize)
	{
	    bufsize += 1000;
	    buffer = r_realloc_and_check(buffer, bufsize);
	}
	strcpy(&buffer[bufindex], modulename);
	bufindex += (len + 1);
    }
    closedir(dirp);
    buffer[bufindex] = '\0';

    modulename = buffer;
    while (*modulename)
    {
	domodule
	(
	    action,
	    modulename,
	    inputname,
	    outputname,
	    editname,
	    editstring,
	    editstring2,
	    1
	);
	modulename += (strlen(modulename) + 1);
    }
}


/*
 * Routine to read an optional edit number string from the command line.
 * Returns the string converted to lower case, or NULL if one is not there.
 */

static char *
geteditstring(void)
{
    char	    *str;
    char	    *cp;

    switch (arglex_token)
    {
    default:
	str = 0;
	break;

    case arglex_token_number:
	str = arglex_value.alv_string;
	arglex();
	break;

    case arglex_token_string:
	str = arglex_value.alv_string;
	for (cp = str; *cp; ++cp)
	    if (isupper((unsigned char)*cp))
		*cp = tolower((unsigned char)*cp);
	    arglex();
	    break;
	}
    return str;
}


/*
 * Routine to read an optional non-negative number from the command line.
 * Returns the specified default value if no number is present.
 */

static long
getvalue(long value, arglex_token_ty name)
{
    long	    result;

    if (arglex_token != arglex_token_number)
	result = value;
    else
    {
	result = arglex_value.alv_number;
	arglex();
	if (result < 0)
	{
	    sub_context_ty  *scp;

	    scp = sub_context_new();
	    sub_var_set_charstar(scp, "Name", arglex_token_name(name));
	    sub_var_set_long(scp, "Number", result);
	    sub_var_optional(scp, "Number");
	    fatal_intl
	    (
		scp,
		i18n("the $name option needs a non-negative argument")
	    );
	    /* NOTREACHED */
	    sub_context_delete(scp);
	}
    }
    return result;
}


/*
 * Expand a global variable name.
 * Returns original argument if string is not a global variable,
 * or returns copied value of global variable.
 */

static char *
expand(char *cp)
{
    char	    *var;	/* variable value */

    if ((cp == NULL) || (*cp != '.'))
	return cp;
    var = getenv(cp);
    if ((var == NULL) || (*var == '\0'))
	return cp;
    cp = r_alloc_and_check(strlen(var) + 1);
    strcpy(cp, var);
    return cp;
}


static void
mkdir_minus_p(char *path)
{
    char	    *slash;

    slash = strrchr(path, '/');
    if (slash && slash > path)
    {
	string_ty	*tmp;

	tmp = str_n_from_c(path, slash - path);
	mkdir_minus_p(tmp->str_text);
	str_free(tmp);
    }

    /*
     * Ignore any error codes coming back.  (The commonest will be
     * EEXISTS, which we won't be complaining about anyway.)  When we
     * try to access the directory contents, some other fatal error
     * will occur, so the user foinds out anyway.
     */
    mkdir(path, 0777);
}


int
main(int argc, char **argv)
{
    char	    *outputname;	/* output name for extraction */
    char	    *inputname;		/* input name for update */
    char	    *editname;		/* name for this edit */
    char	    *editstring;	/* edit number string */
    char	    *editstring2;	/* second edit number string */
    int		    modulecount;	/* number of modules */
    int		    curmod;		/* current module number */
    int		    action;		/* action to be performed */
    int		    allflag;		/* 1 if want to do all files */
    char	    *modules[MAXMODULES]; /* list of modules */
    int		    make_path;

    arglex_init(argc, argv, argtab);
    switch (arglex())
    {
    case arglex_token_help:
	main_help();
	quit(0);

    case arglex_token_version:
	version();
	quit(0);

    default:
	break;
    }
    modifyline_register(modifyline);
    fc.debugflag = 0;
    fc.quickflag = 0;
    fc.verbosity = VERBOSE_DEFAULT;
    fc.binary = 0;
    sc.nowriteflag = 0;
    sc.forcewriteflag = 0;
    sc.forceupdateflag = 0;
    sc.modifylines = MODIFY_LINES;
    sc.firstedit = 0;
    sc.lastedit = 0;
    sc.tablepos = 0;
    sc.dirname = DEFAULTPATH;
    sc.modulename = NULL;
    make_path = 0;
    allflag = 0;
    modulecount = 0;
    action = 0;
    outputname = NULL;
    inputname = NULL;
    editname = NULL;
    editstring = NULL;
    editstring2 = NULL;
    sc.remarkname = NULL;
    sc.remark_string = NULL;

    while (arglex_token != arglex_token_eoln)
    {
	switch (arglex_token)
	{
	default:
	    bad_argument(usage);
	    /* NOTREACHED */

	case arglex_token_number:
	    /*
	     * Modules names can look like numbers.
	     * Fall through...
	     */

	case arglex_token_string:
	    if (modulecount >= MAXMODULES)
	    {
		fatal_intl(0, i18n("too many modules specified"));
		/* NOTREACHED */
	    }
	    modules[modulecount++] = arglex_value.alv_string;
	    break;

	case arglex_token_difference:
	    /* generate differences */
	    action |= A_DIFF;
	    arglex();
	    editstring = geteditstring();
	    editstring2 = geteditstring();
	    continue;

	case arglex_token_extract:
	    /* extract edit */
	    action |= A_EXTRACT;
	    arglex();
	    editstring = geteditstring();
	    continue;

	case arglex_token_input:
	    /* input file */
	    if (arglex() != arglex_token_string)
	    {
		error_intl(0, i18n("no input file supplied"));
		usage();
	    }
	    inputname = expand(arglex_value.alv_string);
	    break;

	case arglex_token_list:
	    /* list edit */
	    action |= A_LIST;
	    arglex();
	    editstring = geteditstring();
	    editstring2 = geteditstring();
	    continue;

	case arglex_token_modified:
	    /* set number of modified lines */
	    arglex();
	    sc.modifylines =
		getvalue((long)MODIFY_LINES, arglex_token_modified);
	    continue;

	case arglex_token_output:
	    /* output file */
	    if (arglex() != arglex_token_string)
	    {
		error_intl(0, i18n("no output file supplied"));
		usage();
	    }
	    outputname = expand(arglex_value.alv_string);
	    break;

	case arglex_token_make_path:
	    make_path = 1;
	    break;

	case arglex_token_path:
	    /* directory path for edit histories */
	    if (arglex() != arglex_token_string)
	    {
		error_intl(0, i18n("no directory path supplied"));
		usage();
	    }
	    sc.dirname = expand(arglex_value.alv_string);
	    break;

	case arglex_token_quick:
	    /* be quick on comparison or list output */
	    fc.quickflag = 1;
	    break;

	case arglex_token_remark:
	    /* remark file */
	    if (arglex() != arglex_token_string)
	    {
		sc.remarkname = "";
		continue;
	    }
	    sc.remarkname = expand(arglex_value.alv_string);
	    break;

	case arglex_token_remark_string:
	    /* remark string */
	    if (arglex() != arglex_token_string)
	    {
		sc.remark_string = NULL;
		continue;
	    }
	    sc.remark_string = arglex_value.alv_string;
	    break;

	case arglex_token_terminal:
	    /* type edit to terminal */
	    action |= A_TYPE;
	    arglex();
	    editstring = geteditstring();
	    continue;

	case arglex_token_update:
	    /* update history file */
	    action |= A_UPDATE;
	    break;

	case arglex_token_verbose:
	    /* be verbose */
	    arglex();
	    fc.verbosity = getvalue((long)VERBOSE_FULL, arglex_token_verbose);
	    continue;

	case arglex_token_what:
	    /* descripe what changes are */
	    fc.whatflag = 1;
	    break;

	case arglex_token_debug:
	    /* debugging enabled */
	    fc.debugflag = 1;
	    break;

	case arglex_token_create:
	    /* create new module */
	    action |= A_CREATE;
	    break;

	case arglex_token_difference_update:
	    /* do difference and update */
	    action |= A_DIFF | A_UPDATE;
	    break;

	case arglex_token_conditional_update:
	    /* update if differences */
	    action |= A_CONDUPDATE;
	    break;

	case arglex_token_clean:
	    /* clean up to date file */
	    action |= A_CLEAN;
	    break;

	case arglex_token_check:
	    /* see if file is up to date */
	    action |= A_CHECK;
	    break;

	case arglex_token_forced_update:
	    /* force update */
	    sc.forceupdateflag = 1;
	    break;

	case arglex_token_force_writing:
	    /* force writing */
	    sc.forcewriteflag = 1;
	    break;

	case arglex_token_all:
	    /* do all files */
	    allflag = 1;
	    break;

	case arglex_token_nname:
	    /* set edit name */
	    if (arglex() != arglex_token_string)
	    {
		error_intl(0, i18n("no edit name supplied"));
		usage();
	    }
	    editname = arglex_value.alv_string;
	    checkeditname(editname);
	    break;

	case arglex_token_no_write:
	    /* don't overwrite */
	    sc.nowriteflag = 1;
	    break;

	case arglex_token_prune:
	    /* prune old edits */
	    action |= A_PRUNE;
	    arglex();
	    editstring = geteditstring();
	    continue;

	case arglex_token_binary:
	    fc.binary = 1;
	    break;

	case arglex_token_keywords_not:
	    sc.modifylines = 0;
	    break;

	case arglex_token_trace:
	    for (;;)
	    {
#ifdef DEBUG
		trace_enable(arglex_value.alv_string);
#endif
		if (arglex() != arglex_token_string)
		    break;
	    }
#ifndef DEBUG
	    error_intl(0, i18n("-TRace needs DEBUG"));
#endif
	    continue;
	}
	arglex();
    }
    if (action == 0)
	action = (editname ? A_NAME : A_EXTRACT);
    if
    (
	action != (A_CREATE | A_CONDUPDATE)
    &&
	action != (A_CREATE | A_UPDATE)
    &&
	action != (A_DIFF | A_UPDATE)
    &&
	action != (action & -action)
    )
	fatal_intl(0, i18n("multiple actions specified"));
    if (sc.remarkname && ((action & REMARK_ACTIONS) == 0))
	fatal_intl(0, i18n("cannot use -Remark with specified action"));
    if (sc.remark_string && ((action & REMARK_ACTIONS) == 0))
	fatal_intl(0, i18n("cannot use -Remark_String with specified action"));
    if (inputname && ((action & INPUT_ACTIONS) == 0))
	fatal_intl(0, i18n("cannot use -Input with specified action"));
    if (outputname && ((action & OUTPUT_ACTIONS) == 0))
	fatal_intl(0, i18n("cannot use -Output with specified action"));
    if (editname && ((action & NAME_ACTIONS) == 0))
	fatal_intl(0, i18n("cannot use -Name with specified action"));
    if (sc.forcewriteflag && sc.nowriteflag)
	fatal_intl(0, i18n("cannot specify both -Force_Write and -No_Write"));
    if (fc.quickflag && fc.whatflag)
	fatal_intl(0, i18n("cannot specify both -What and -Quick"));
    if ((modulecount <= 0) && (action == A_CHECK))
	allflag = 1;
    if ((modulecount <= 0) && !allflag)
	fatal_intl(0, i18n("no module name specified"));
    if (allflag)
    {
	if (modulecount)
	    fatal_intl(0, i18n("cannot specify module names with -ALL"));
	if (inputname && !isdir(inputname))
	    fatal_intl(0, i18n("input name must be a directory for -ALL"));
	if (outputname && !isdir(outputname))
	    fatal_intl(0, i18n("output name must be a directory for -ALL"));
    }
    if (modulecount > 1)
    {
	if (inputname && !isdir(inputname))
	{
	    fatal_intl
	    (
		0,
		i18n("input name must be a directory for multiple modules")
	    );
	}
	if (outputname && !isdir(outputname))
	{
	    fatal_intl
	    (
		0,
		i18n("output name must be a directory for multiple modules")
	    );
	}
    }

    if (make_path)
	mkdir_minus_p(sc.dirname);
    if (access(sc.dirname, R_OK) < 0)
    {
	sub_context_ty	*scp;
	int		what_happened;

	what_happened = errno;
	scp = sub_context_new();
	sub_var_set_charstar(scp, "File_Name", sc.dirname);
	if (what_happened == ENOENT)
	{
	    fatal_intl
	    (
		scp,
		i18n("directory \"$filename\" needs to be created")
	    );
	}
	else
	{
	    fatal_intl(scp, i18n("opendir $filename: $errno"));
	}
	/* NOTREACHED */
	sub_context_delete(scp);
    }

    if (allflag)
    {
	doallmodules
	(
	    action,
	    inputname,
	    outputname,
	    editstring,
	    editstring2,
	    editname
	);
	quit(0);
    }
    for (curmod = 0; curmod < modulecount; curmod++)
    {
	domodule
	(
	    action,
	    modules[curmod],
	    inputname,
	    outputname,
	    editname,
	    editstring,
	    editstring2,
	    (modulecount > 1)
	);
    }
    quit(0);
    return 0;
}
