/*
 * COMMANDS.C
 *
 * This module contains all the command parsing stuff used to parse the
 * commands in the script file.
 *
 */

#ifndef LATTICE_50
#include "system.h"
#endif

#include "bbsindex.h"

/*
 *             The list of command names and associated functions
 */

void com_append(), com_close(), com_config(), com_database(), com_echo(),
        com_exec(), com_format(), com_list(), com_macro(), com_msg(),
        com_open(), com_reset(), com_scan(), com_trace(), com_ignore();

struct {
       char *name;
       void (*proc)();
} com[] = {

       {       "APPEND",               com_append              },
       {       "CHECKFILES",   com_checkfiles  },
       {       "CLOSE",                com_close               },
       {       "CONFIG",               com_config              },
       {       "DATABASE",             com_database    },
       {       "ECHO",                 com_echo                },
       {       "EXEC",                 com_exec                },
       {       "FOREIGN",              com_foreign             },
       {       "FORMAT",               com_format              },
       {       "IGNORE",               com_ignore              },
       {       "LIST",                 com_list                },
       {       "MACRO",                com_macro               },
       {       "MSG",                  com_msg                 },
       {       "NOREQUEST",    com_norequest   },
       {       "OPEN",                 com_open                },
       {       "RESET",                com_reset               },
       {       "SCAN",                 com_scan                },
       {       "SELECT",               com_select              },
       {       "SORT",                 com_sort                },
       {       "TRACE",                com_trace               },
       {       NULL,                   NULL                    } 
};

struct constval {
       struct constval *next;
       char *name;
       char *value;
};
typedef struct constval CONST;

CONST *firstconst = NULL;              /* Pointer to first constant on list    */

static char line[MAXCOM];      /* Temporary line buffer */

/*
 *             scripterror()
 *             -------------
 *             Prints an error message for the current script command; the filename
 *             and linenumber are automatically output, followed by the user
 *             specified message. The message should be terminated with a NL,
 *             unless the caller intends outputting any more info afterwards.
 */
void scripterror(s)
char *s;
{
       print3("===> ", scriptname, " (");
       print3(itoa(linenum), "): ",s);
}

/*
 *             addconst()
 *             --------
 *             Creates a new constant entry, and links it into the list of 
 *             existing constant definitions. The name of the constant is
 *             initialised appropriately. A pointer to the constant is returned.
 */
CONST *addconst(name)
char *name;
{
       CONST *tv;

       tv                      = mymalloc(sizeof(CONST));
       tv->next        = firstconst;
       firstconst      = tv;
       tv->name        = mymalloc(strlen(name)+1);
       strcpy(tv->name, name);
       return (tv);
}

/*
 *             findconst()
 *             ---------
 *             Searches constant table for specified constant, and returns a
 *             pointer to it if found, or NULL if not found.
 */
CONST *findconst(name)
char *name;
{
       CONST *tv;

       for (tv = firstconst; tv && stricmp(name, tv->name); tv = tv->next)
               ;
       return (tv);
}


/*
 *             readcommand()
 *             -------------
 *             This function reads a command from the script buffer into a
 *             specified command buffer. The following modifications are made
 *             to the original script text:
 *
 *             - Anything after a # to an end of line is ignored
 *             - Any line ending with \ is continued on the next line
 *             - Everything outside quotes is capitalised.
 *             - Any white space outside quotes gets reduced to a single space
 *             - White space surrounding command lines is removed.
 *             - Any commas on the line are removed, and replaced by spaces
 *
 *             The script command is terminated by either a semicolon or a newline.
 *             The command buffer is null-terminated on return. When the end of the
 *             script is reached, a 0 is returned. The maximum size of the command
 *             line is specified in max - if this is exceeded, then an error message
 *             is generated. Normally, the length of the command line read in is
 *             returned.
 *
 *             For the technically minded, a mini state machine is setup, to handle
 *             the different requirements.
 */

#define STATE_START            1       /* Skip over space at start of command                  */
#define STATE_SPACE            2       /* Replace multiple white space by single space */
#define STATE_COPY             3       /* Copy normal text, capitalising                               */
#define STATE_IGNORE   4       /* If next char is newline, then skip it                */
#define STATE_QUOTE            5       /* Copy text up until next quote                                */
#define STATE_IGQUOTE  6       /* Like STATE_IGNORE, but between quotes                */
#define STATE_COMMENT  7       /* Ignore everything until the next newline             */
#define STATE_CONST_ST 8       /* Starting to expand a constant $(..)                  */
#define STATE_CONST_CP  9      /* Copying constant name, and expanding it              */

/*
 *             Retrieve next character from script buffer, updating line counter
 *             and checking for end of buffer.
 */
#define nextchar(ch)                                                                   \
       (       (ch) = script[scriptpos++],                                             \
               ((ch) == CHAR_NL ? linenum++ : 0),                              \
               ((scriptpos > scriptsize) ? (loop = 0) : 0)     \
       )

/*
 *             Add character to command line, checking for end of buffer and
 *             aborting if the buffer is overrun.
 */
#define addchar(ch)                                                                            \
       (       buf[pos++] = (ch),                                                              \
               ((pos > max) ? (scripterror("line too long\n"), Cleanup(10)) : 0)       \
       )

int readcommand(buf,max)
char *buf;
int max;
{
       int pos = 0;
       int state = STATE_START;
       int laststate;
       int loop = 1;
       int ch;
       CONST *var;
       char varname[MAXCONST], *p;
       char openquote;
       int varpos;

       if (scriptpos >= scriptsize)
               return 0;

       nextchar(ch);
       while (loop) {
               switch (state) {

                       case STATE_START:
                               switch (ch) {

                                       case CHAR_SPACE:
                                       case CHAR_TAB:
                                       case CHAR_SEMI:
                                       case CHAR_NL:
                                       case CHAR_COMMA:
                                               nextchar(ch);
                                               break;

                                       case CHAR_HASH:
                                               state = STATE_COMMENT;
                                               break;

                                       default:
                                               state = STATE_COPY;
                                               break;
                               }
                               break;

                       case STATE_SPACE:
                               switch (ch) {

                                       case CHAR_SPACE:
                                       case CHAR_TAB:
                                       case CHAR_COMMA:
                                               nextchar(ch);
                                               break;

                                       case CHAR_NL:
                                               linenum--;
                                               /* Drop through */
                                       case CHAR_SEMI:
                                               scriptpos--;
                                               loop = 0;
                                               break;

                                       default:
                                               addchar(CHAR_SPACE);
                                               state = STATE_COPY;
                                               break;
                               }
                               break;

                       case STATE_COPY:
                               switch (ch) {

                                       /*
                                        * If we get a # after a command, then we immediately
                                        * stop, so that the comment will get eaten the NEXT
                                        * time we call readcommand(). If we went into
                                        * STATE_COMMENT, then the following command would end
                                        * up getting read into the buffer as well.
                                        *
                                        */
                                       case CHAR_HASH:
                                               scriptpos--;
                                               loop = 0;
                                               break;

                                       case CHAR_QUOTE:
                                       case CHAR_QUOTES:
                                               addchar(ch);
                                               openquote = ch;
                                               state = STATE_QUOTE;
                                               nextchar(ch);
                                               break;

                                       case CHAR_DOLLAR:
                                               laststate = state;      /* Save return state */
                                               state = STATE_CONST_ST;
                                               nextchar(ch);
                                               break;

                                       case CHAR_NL:
                                               linenum--;
                                               /* Drop through */

                                       case CHAR_SEMI:
                                               loop = 0;
                                               scriptpos--;
                                               break;

                                       case CHAR_ESC:
                                               nextchar(ch);
                                               state = STATE_IGNORE;
                                               break;

                                       case CHAR_SPACE:
                                       case CHAR_TAB:
                                       case CHAR_COMMA:
                                               state = STATE_SPACE;
                                               break;

                                       default:
                                               addchar(toupper(ch));
                                               nextchar(ch);
                                               break;
                               }
                               break;

                       case STATE_IGNORE:
                               if (ch == CHAR_NL)
                                       ch = CHAR_SPACE;
                               addchar(ch);
                               nextchar(ch);
                               state = STATE_COPY;
                               break;

                       case STATE_QUOTE:
                               switch (ch) {

                                       case CHAR_NL:
                                               scriptpos--;
                                               linenum--;
                                               loop = 0;
                                               break;

                                       case CHAR_ESC:
                                               state = STATE_IGQUOTE;
                                               nextchar(ch);
                                               break;

                                       case CHAR_DOLLAR:
                                               laststate = state;
                                               state = STATE_CONST_ST;
                                               nextchar(ch);
                                               break;

                                       case CHAR_QUOTE:
                                       case CHAR_QUOTES:
                                               if (openquote == ch)
                                                       state = STATE_COPY;
                                               /* Deliberate fall through to next switch */

                                       default:
                                               addchar(ch);
                                               nextchar(ch);
                                               break;
                               }
                               break;

                       case STATE_IGQUOTE:
                               if (ch != CHAR_NL) {
                                       addchar(CHAR_ESC);
                                       addchar(ch);
                               }
                               nextchar(ch);
                               state = STATE_QUOTE;
                               break;

                       case STATE_COMMENT:
                               if (ch == CHAR_NL)
                                       state = STATE_START;
                               nextchar(ch);
                               break;

                       case STATE_CONST_ST:
                               if (ch != '(') { /* If not a constant usage, copy unchanged */
                                       addchar(CHAR_DOLLAR);
                                       state = laststate;
                                       break;
                               }
                               varpos = 0;
                               nextchar(ch);
                               state = STATE_CONST_CP;
                               break;

                       case STATE_CONST_CP:
                               if (ch == ')') {
                                       /*
                                        *              Variable name has been fully entered, so now
                                        *              expand it.
                                        */
                                       varname[varpos] = CHAR_NULL;
                                       var = findconst(varname);
                                       if (!var) {
                                               scripterror("unknown constant ");
                                               print2(varname, ".\n");
                                               Cleanup(10);
                                       }
                                       for (p = var->value; *p; p++)
                                               addchar(*p);
                                       state = laststate;
                               } else {
                                       /*
                                        *              Else still gathering constant name, so keep
                                        *              copying into array.
                                        */
                                       if (varpos >= MAXCONST) {
                                               scripterror("constant name too long.\n");
                                               Cleanup(10);
                                       }
                                       varname[varpos++] = ch;
                               }
                               nextchar(ch);
                               break;
               }
       }
       addchar(CHAR_NULL);     /* Null terminate command string */
       compos = 0;
       comlen = strlen(buf);
       return (comlen);
}

/*
 *             findmacro()
 *             -----------
 *             Searches the macro table for the specified macro. If found, returns
 *             pointer to the macro definition, else returns -1.
 */
int findmacro(name)
char *name;
{
       int i;

       for (i = 0; i < nummacros; i++)
               if (!strcmp(macros[i]->name, name))
                       return (i);
       return (-1);
}

/*
 *             getstring()
 *             -----------
 *             This function scans the command buffer starting at position
 *             'compos', and returns a pointer to the next identifier/string in
 *             the buffer. The string is null-terminated, and if it was enclosed
 *             in quotes, these are removed. compos is left pointing to just after
 *             the string.
 */
char *getstring()
{
       char *s = combuf + compos;
       char *p, openquote;

       if (compos > comlen) {
               scripterror("missing parameter\n");
               Cleanup(10);
       }
       if (*s == CHAR_SPACE)
               s++;

       if (*s == CHAR_QUOTE || *s == CHAR_QUOTES) {
               openquote = *s++;
               for (p = s; *p && *p != openquote; p++) {
                       if (*p == CHAR_ESC)
                               p++;    /* Skip over possible escaped quotes */
               }
               *p = CHAR_NULL;
               compos = (p - combuf) + 1;
               return (s);
       }

       for (p = s; *p && *p != CHAR_SPACE; p++)
               ;
       *p = CHAR_NULL;
       compos = (p - combuf) + 1;
       return (s);
}


/*
 *             execline()
 *             ----------
 *             This function executes the current line in the command buffer,
 *             handling macro expansion as necessary.
 */
void execline()
{
       char *cmd;
       char *p, *s;
       char *equals;
       MACRO *curmac;
       CONST *var;
       int macronum;
       char *ps[10];
       PARAM *par;
       int length;
       int i;

       chkabort();
       if (tracemode) {
               if (nestlevel > 0) {
                       int i;
                       for (i = 0; i < nestlevel; i++)
                               print("    ");
                       print2(combuf, "\n");
               } else {
                       print3(itoa(linenum), ":", scriptname);
                       print3(": ", combuf, "\n");
               }
       }
       cmd = getstring();
       for (i = 0; com[i].name && strcmp(cmd, com[i].name); i++)
               ;
       if (com[i].name) {
               /*
                *              A valid command was found, so execute it
                */
               com[i].proc();
       } else if ((macronum = findmacro(cmd)) == -1) {
               /*
                *              Not a macro -- is it a constant definition?
                */
               if (compos < comlen) {
                       /*
                        *              There's a parameter after it; check is it an equals sign
                        */
                       equals = getstring();
                       if (equals[0] != CHAR_EQUALS || equals[1] != CHAR_NULL) {
                               /*
                                *              Wasn't an equals, so probably just a wrong command
                                *              with some superfluous parameters.
                                */
                               scripterror("unknown command ");
                               print2(cmd, "\n");
                               Cleanup(10);
                       }
                       /*
                        *              Okay, it's a constant definition. If it's already been
                        *              defined, print a warning but carry on anyway.
                        */
                       var = findconst(cmd);
                       if (var) {
                               scripterror("constant ");
                               print2(cmd, " redefined.\n");
                       } else
                               var = addconst(cmd);
                       /*
                        *              Now see was a value specified for the constant.
                        *              If it was, copy it into constant, else just 
                        *              setup a null definition.
                        */
                       if (compos < comlen) {
                               /*
                                *              Copy user's definition
                                */
                               cmd = getstring();
                               if (strlen(cmd) >= MAXCONST) {
                                       scripterror("constant name too long.\n");
                                       Cleanup(10);
                               }
                               var->value = mymalloc(strlen(cmd)+1);
                               strcpy(var->value, cmd);
                       } else {
                               /*
                                *              Setup null constant definition
                                */
                               var->value = mymalloc(1);
                               strcpy(var->value, "");
                       }
               } else {
                       /*
                        *              Not a command, not a macro, not a constant definition.
                        *              Therefore, must be an error.
                        */
                       scripterror("unknown command ");
                       print2(cmd, "\n");
                       Cleanup(10);
               }
       } else {
               /*
                *              It's a macro. Save a copy of the macro parameters into
                *              $0 to $9, and then execute each line of the macro definition,
                *              expanding parameter usages as necessary.
                */
               curmac = macros[macronum];
               length = 0;
               if (nestlevel >= MAXNEST) {
                       scripterror("macros can only be nested ");
                       print2(MAXNEST, " deep\n");
                       Cleanup(10);
               }
               par = SafeAllocMem(PARAMSIZE + comlen + 1);
               params[nestlevel++] = par;
               par->size = comlen + 1;

               /*
                *              Now initialise parameters $0 to $9
                */
               ps[0] = par->params;
               for (i = 1; i < 10; i++) {
                       if (compos < comlen)
                               /* Save parameter for later */
                               ps[i] = par->params + (getstring() - combuf);
                       else
                               ps[i] = NULL;
               }
               memcpy(par->params, combuf, comlen+1);

               /*
                *              Now recursively call execline() with command buffer setup
                *              to be the current macro buffer line.
                */
               length = 0;
               s = curmac->text;
               for (length = 0; length < curmac->size;
                                               length += strlen(s) + 1, s = curmac->text + length) {
                       /* Copy macro line into buffer, doing expansion */
                       comlen = 0;
                       for (p = s; *p; p++) {
                               if (*p == '$') {
                                       p++;
                                       if (!*p) {
                                               scripterror("'$' must be followed by something\n");
                                               Cleanup(10);
                                       }
                                       if (isdigit(*p)) {
                                               int num = *p - '0';

                                               if (ps[num]) {
                                                       /* Copy macro definition */
                                                       if ((strlen(ps[num]) + comlen) >= MAXCOM - 10)
                                                               goto endcommand;        /* Eek! my first C Goto! */
                                                       strcpy(combuf+comlen, ps[num]);
                                                       comlen += strlen(ps[num]);
                                               }
                                               continue;

                                       }
                               }
                               combuf[comlen++] = *p;
                       }
endcommand:
                       combuf[comlen] = CHAR_NULL;
                       compos = 0;
                       execline();
               }
               /*
                *              Now free memory allocated for macro parameters
                */
               FreeMem(par, PARAMSIZE + par->size);
               nestlevel--;
       }
}


/*
 *             execscript()
 *             ------------
 *             This function goes through all the commands in the script file,
 *             executing them one by one. Each command is parsed, and then
 *             the appropriate function called. Any errors that occur cause the
 *             script to be aborted, so if this call returns, the script was
 *             successfully executed.
 */
void execscript()
{
       while (readcommand(combuf, MAXCOM))
               execline();
}

/*
 *             ====> Now the actual commands follow <====
 */

/*
 *             com_append()
 *             ------------
 *             This file opens the specified file for appending. If there was
 *             already a file open, it is closed.
 */

void com_append()
{
       char *filename = getstring();
       BPTR lock;

       com_close();

       if (lock = Lock(filename, ACCESS_READ)) {
               UnLock(lock);
               outfile = Open(filename, MODE_OLDFILE);
       } else
               outfile = Open(filename, MODE_NEWFILE);

       if (!outfile) {
               scripterror("error appending to file ");
               print2(filename, "\n");
               Cleanup(10);
       }
       Seek(outfile, 0, OFFSET_END);
       toscreen = IsInteractive(outfile);
}

/*
 *             com_open()
 *             ----------
 *             This command opens a file for output. The output of the ECHO,
 *             FOREIGN and LIST commands goes to this file. If an output file
 *             was already open, it is closed first.
 */
void com_open()
{
       char *filename = getstring();

       com_close();
       outfile = Open(filename, MODE_NEWFILE);
       if (!outfile) {
               scripterror("error opening file ");
               print2(filename, "\n");
               Cleanup(10);
       }
       toscreen = IsInteractive(outfile);
}

/*
 *             com_close()
 *             -----------
 *             Closes the current output file, if any. All future output goes to
 *             stdout.
 */
void com_close()
{
       if (outfile != Output()) {
               flushout();
               Close(outfile);
               outfile = Output();
               toscreen = IsInteractive(outfile);
       }
}

/*
 *             com_config()
 *             ------------
 *             This command sets the name of the configuration file used by
 *             CHECKFILES to get the names of the directories to scan from.
 */
void com_config()
{
       strcpy(configname, getstring());
}

/*
 *             com_database()
 *             --------------
 *             This command sets the name of the database file read in
 *             which contains the details of all the file headers.
 */
void com_database()
{
       strcpy(databasename, getstring());
}

/*
 *             com_norequest()
 *             ---------------
 *             This command stops AmigaDos from putting up requesters when an
 *             error occurs, or a volume isn't mounted. 
 */
void com_norequest()
{
       struct Process *me = (struct Process *)FindTask(0L);
       me->pr_WindowPtr = (APTR)-1L;
}

/*
 *             com_echo()
 *             ----------
 *             This command echoes the specified string to the output file.
 *             A number of meta characters may be present in the string to be
 *             output. See the documentation for more info. If any other parameter
 *             is present on the line after the string, no newline is added
 *             to the output, otherwise a newline is added automatically.
 *
 */
void com_echo()
{
       strcpy(line, getstring());
       if (compos >= comlen)   /* Add NL if no second parameter */
               strcat(line, "\n");
       putstring(echoformat(out, MAXOUT, line));
}

/*
 *             com_exec()
 *             ----------
 *             This command executes the indicated AmigaDOS command. The output
 *             from the command goes into the current output file, if any.
 */
void com_exec()
{
       flushout();
       Execute(getstring(), NULL, outfile);
}

/*
 *             com_format()
 *             ------------
 *             This command sets up the format string used by LIST and FOREIGN.
 *             For details of what the format may contain, see the documentation.
 */
void com_format()
{
       strcpy(formatstring, getstring());
       if (compos >= comlen)                           /* No second parameter */
               strcat(formatstring, "\n");
}

/*
 *             com_list()
 *             ----------
 *             This command is the biggy! It scans through the entire file
 *             database, selecting records as specified with SELECT, and printing
 *             them using the order specified with FORMAT. The order of printing
 *             is as specified with SORT.
 */
void com_list()
{
       int i;

       CHECKDATABASE();
       curbytes = 0;
       curfiles = 0;
    for (i = 0; i < numrecs; i++) {
        if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
            format(out, MAXOUT, formatstring, ptrblock[i], checkfiles);
                       putstring(out);
                       curfiles++;
                       curbytes += ptrblock[i]->length;
               }
               chkabort();
       }
       totalbytes += curbytes;
       totalfiles += curfiles;
}

/*
 *             com_msg()
 *             ---------
 *             This command is identical to the ECHO command, except that
 *             the output goes to the screen (i.e. stderr) instead of the
 *             current output file. It is intended for printing informational
 *             messages to let the user know of the program's progress, while
 *             processing a large script.
 */
void com_msg()
{
       strcpy(line, getstring());
       if (compos >= comlen)   /* Add NL if no second parameter */
               strcat(line, "\n");
       print(echoformat(out, MAXOUT, line));
}

/*
 *             com_reset()
 *             -----------
 *             This command resets the running totals which are maintained, 
 *             which give the total number of bytes and total number of files
 *             output so far.
 */
void com_reset()
{
       totalfiles = 0;
       totalbytes = 0;
}

/*
 *             com_scan()
 *             ----------
 *             This command is identical to com_list(), except that no output is
 *             produced. It is provided to allow counters to be updated, without
 *             producing output, so that for example, the total number of files
 *             in a listing can be output before the files themselves.
 */

void com_scan()
{
       int i;

       CHECKDATABASE();
       curbytes = 0;
       curfiles = 0;
    for (i = 0; i < numrecs; i++) {
        if (ptrblock[i]->type == 0 && match(ptrblock[i], tree)) {
                       curfiles++;
                       curbytes += ptrblock[i]->length;
               }
               chkabort();
       }
       totalbytes += curbytes;
       totalfiles += curfiles;
}

/*
 *             com_macro()
 *             -----------
 *             This command lets you define a macro. The syntax is:
 *
 *                     MACRO name
 *                               ..
 *                             commands
 *                               ..
 *                     ENDM
 *
 *             The macro can contain $1 to $9, which will be substituted at
 *             run time by the appropriate parameters that were passed when
 *             the macro was invoked.
 */
void com_macro()
{
       static char macroname[MACROLEN];
       int curline, curpos;    /* Saves current line # and position in script */
       int length;                             /* Length of macro script                                          */
       int macronum;
       MACRO *curmac;
       char *buf, *p;

       if (nummacros >= MAXMACRO) {
               scripterror("maximum of ");
               print2(itoa(MAXMACRO), " macros allowed\n");
               Cleanup(10);
       }

       curline = linenum;
       curpos = scriptpos;
       
       p = getstring();
       strncpy(macroname, p, MACROLEN-1);
       macroname[MACROLEN-1] = CHAR_NULL;

       /*
        *              First of all, find out how much space the macro occupies
        */
       length = 0;
       while (readcommand(combuf, MAXCOM) && strcmp(combuf, "ENDM"))
               length += comlen + 1;   /* The extra 1 is for the terminating \0 */

       /*
        *              Now, restore linenumber and script position so we can read
        *              in macro for real the second time.
        */
       linenum = curline;
       if (scriptpos >= scriptsize) {
               scripterror("missing ENDM in macro definition\n");
               Cleanup(10);
       }

       scriptpos = curpos;

       if ((macronum = findmacro(macroname)) == -1)            /* New macro */
               macronum = nummacros++;
       else
               /* Macro redefinition, so release earlier definition */
               FreeMem(macros[macronum], macros[macronum]->size + MACROSIZE);

       curmac = SafeAllocMem(MACROSIZE + length);
       macros[macronum] = curmac;
       strcpy(curmac->name, macroname);
       curmac->size = length;
       buf = curmac->text;

       /*
        *              Initialised macro definition, now copy macro text from script
        *              file into macro buffer.
        */
       while (readcommand(combuf, MAXCOM) && strcmp("ENDM", combuf)) {
               strcpy(buf, combuf);
               buf += comlen + 1;
       }
}

/*
 *             com_trace()
 *             -----------
 *             Turns on or off trace mode. When trace mode is on, every line that
 *             is executed is displayed first. This is handy when executing
 *             macros, to see exactly what is happening. Note that the -t option
 *             on the command line will enable tracing for the whole file,
 *             but if TRACE ON or OFF is encountered, this overrides -t.
 */
void com_trace()
{
       char *opt = getstring();

       if (!strcmp(opt, "ON"))
               tracemode = TRUE;
       else if (!strcmp(opt, "OFF"))
               tracemode = FALSE;
       else {
               scripterror("TRACE ON or TRACE OFF expected\n");
               Cleanup(10);
       }
}

/*
 *             com_ignore()
 *             ------------
 *             This command takes a list of filenames. Each filename listed here
 *             is remembered, and when CHECKFILES is done, any filename listed
 *             here will be marked as valid, regardless of whether it really IS
 *             valid or not. This is useful if you have some files online which
 *             are updated by external programs (such as, for example, the output
 *             from BBSINFO), and hence have "wandering" file sizes.
 */
void com_ignore()
{
       IGNORE *ig;
       char *p;

       while (compos < comlen) {
               ig = mymalloc(sizeof(IGNORE));
               p = getstring();
               if (strlen(p) >= CAT_LEN) {
                       scripterror("filename too long.\n");
                       Cleanup(10);
               }
               strcpy(ig->name, p);
               ig->next = firstignore;
               firstignore = ig;
       }
}
