applied 050_ck_patch.patch
[ckermit.git] / ckucmd.c
1 #include "ckcsym.h"
2
3 char *cmdv = "Command package 8.0.157, 11 May 2003";
4
5 /*  C K U C M D  --  Interactive command package for Unix  */
6
7 /*
8   Author: Frank da Cruz (fdc@columbia.edu),
9   Columbia University Academic Information Systems, New York City.
10
11   Copyright (C) 1985, 2004,
12     Trustees of Columbia University in the City of New York.
13     All rights reserved.  See the C-Kermit COPYING.TXT file or the
14     copyright text in the ckcmai.c module for disclaimer and permissions.
15 */
16
17 #define TOKPRECHECK
18
19 #define DOCHKVAR
20
21 /* Command-terminal-to-C-Kermit character mask */
22
23 #ifdef OS2                              /* K95 */
24 int cmdmsk = 255;                       /* (always was 255) */
25 #else                                   /* All others... */
26 int cmdmsk = 255;                       /* 31 Dec 2000 (was 127) */
27 #endif /* OS2 */
28
29 #ifdef BS_DIRSEP                        /* Directory separator is backslash */
30 #undef BS_DIRSEP
31 #endif /* BS_DIRSEP */
32
33 #ifdef OS2
34 #define BS_DIRSEP
35 #endif /* BS_DIRSEP */
36
37 #define CKUCMD_C
38
39 #include "ckcdeb.h"                     /* Formats for debug(), etc. */
40 #include "ckcker.h"                     /* Needed for BIGBUFOK definition */
41 #include "ckcnet.h"                     /* Needed for server-side Telnet */
42 #include "ckucmd.h"                     /* Needed for everything */
43 #include "ckuusr.h"                     /* Needed for prompt length */
44
45 #ifndef NOARROWKEYS
46 #ifndef NOESCSEQ
47 #ifdef VMSORUNIX
48 #define USE_ARROWKEYS                   /* Use arrow keys for command recall */
49 #endif /* VMSORUNIX */
50 #endif /* NOESCSEQ */
51 #endif /* NOARROWKEYS */
52
53 #undef CKUCMD_C
54
55 _PROTOTYP( int unhex, (char) );
56 _PROTOTYP( static VOID cmdclrscn, (void) );
57
58 #ifdef CKLEARN
59 _PROTOTYP( VOID learncmd, (char *) );
60 #endif /* CKLEARN */
61
62 static char *moname[] = {
63     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
64     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
65 };
66
67 struct keytab cmonths[] = {
68   { "april",     4, 0 },
69   { "august",    8, 0 },
70   { "december", 12, 0 },
71   { "february",  2, 0 },
72   { "january",   1, 0 },
73   { "july",      7, 0 },
74   { "june",      6, 0 },
75   { "march",     3, 0 },
76   { "may",       5, 0 },
77   { "november", 11, 0 },
78   { "october",  10, 0 },
79   { "september", 9, 0 }
80 };
81
82 #ifndef NOICP     /* The rest only if interactive command parsing selected */
83
84 #ifndef NOSPL
85 _PROTOTYP( int chkvar, (char *) );
86 extern int askflag;
87 #endif /* NOSPL */
88
89 #ifdef CKROOT
90 extern int ckrooterr;
91 #endif /* CKROOT */
92
93 #ifdef IKSD
94 extern int inserver;
95 #endif /* IKSD */
96
97 int cmfldflgs = 0;                      /* Flags for cmfld() */
98 int cmkwflgs = 0;                       /* Flags from last keyword parse */
99 static int blocklvl = 0;                /* Block nesting level */
100 static int linebegin = 0;               /* Flag for at start of a line */
101 static int quoting = 1;                 /* Quoting is allowed */
102 static int swarg = 0;                   /* Parsing a switch argument */
103 static int xcmfdb = 0;                  /* Flag for parsing chained fdbs... */
104 static int chsrc = 0;                   /* Source of character, 1 = tty */
105 static int newcmd = 0;                  /* See addcmd() */
106
107 #ifdef BS_DIRSEP
108 static int dirnamflg = 0;
109 #endif /* BS_DIRSEP */
110
111 /*
112 Modeled after the DECSYSTEM-20 command parser (the COMND JSYS), RIP. Features:
113
114  . parses and verifies keywords, filenames, text strings, numbers, other data
115  . displays appropriate menu or help message when user types "?"
116  . does keyword and filename completion when user types ESC or TAB
117  . does partial keyword and filename completion
118  . accepts any unique abbreviation for a keyword
119  . allows keywords to have attributes, like "invisible" and "abbreviation"
120  . can supply defaults for fields omitted by user
121  . provides command retry and recall
122  . provides character, word, and line deletion (but only from the end)
123  . accepts input from keyboard, command files, macros, or redirected stdin
124  . allows for full or half duplex operation, character or line input
125  . allows \-escapes for special characters
126  . allows specification of a user exit to expand variables, etc.
127  . settable prompt, protected from deletion, dynamically re-evaluated each time
128  . allows chained parse functions.
129
130 Functions:
131  cmsetp - Set prompt (cmprom is prompt string)
132  cmsavp - Save current prompt
133  cmgetp = Get current prompt
134  prompt - Issue prompt
135  cmini  - Clear the command buffer (before parsing a new command)
136  cmres  - Reset command buffer pointers (before reparsing)
137  cmkey  - Parse a keyword or token (also cmkey2)
138  cmswi  - Parse a switch
139  cmnum  - Parse a number
140  cmifi  - Parse an input file name
141  cmofi  - Parse an output file name (also cmifip, cmifi2, ...)
142  cmdir  - Parse a directory name (also cmdirp)
143  cmfld  - Parse an arbitrary field
144  cmtxt  - Parse a text string
145  cmdate - Parse a date-time string
146  cmcfm  - Parse command confirmation (end of line)
147  cmfdb  - Parse any of a list of the foregoing (chained parse functions)
148
149 Return codes:
150  -9: like -2 except this module already printed the error message
151  -3: no input provided when required
152  -2: input was invalid (e.g. not a number when a number was required)
153  -1: reparse required (user deleted into a preceding field)
154   0 or greater: success
155 See individual functions for greater detail.
156
157 Before using these routines, the caller should #include "ckucmd.h" and set the
158 program's prompt by calling cmsetp().  If the file parsing functions cmifi,
159 cmofi, or cmdir are to be used, this module must be linked with a ck?fio file
160 system support module for the appropriate system, e.g. ckufio for Unix.  If
161 the caller puts the terminal in character wakeup ("cbreak") mode with no echo,
162 then these functions will provide line editing -- character, word, and line
163 deletion, as well as keyword and filename completion upon ESC and help
164 strings, keyword, or file menus upon '?'.  If the caller puts the terminal
165 into character wakeup/noecho mode, care should be taken to restore it before
166 exit from or interruption of the program.  If the character wakeup mode is not
167 set, the system's own line editor may be used.
168
169 NOTE: Contrary to expectations, many #ifdef's have been added to this module.
170 Any operation requiring an #ifdef (like clear screen, get character from
171 keyboard, erase character from screen, etc) should eventually be turned into a
172 call to a function that is defined in ck?tio.c, but then all the ck?tio.c
173 modules would have to be changed...
174 */
175
176 /* Includes */
177
178 #include "ckcker.h"
179 #include "ckcasc.h"                     /* ASCII character symbols */
180 #include "ckucmd.h"                     /* Command parsing definitions */
181
182 #ifdef OSF13
183 #ifdef CK_ANSIC
184 #ifdef _NO_PROTO
185 #undef _NO_PROTO
186 #endif /* _NO_PROTO */
187 #endif /* CK_ANSIC */
188 #endif /* OSF13 */
189
190 #include <errno.h>                      /* Error number symbols */
191
192 #ifdef OS2
193 #ifndef NT
194 #define INCL_NOPM
195 #define INCL_VIO                        /* Needed for ckocon.h */
196 #include <os2.h>
197 #undef COMMENT
198 #else
199 #define APIRET ULONG
200 #include <windows.h>
201 #endif /* NT */
202 #include "ckocon.h"
203 #include <io.h>
204 #endif /* OS2 */
205
206 #ifdef NT
207 #define stricmp _stricmp
208 #endif /* NT */
209
210 #ifdef OSK
211 #define cc ccount                       /* OS-9/68K compiler bug */
212 #endif /* OSK */
213
214 #ifdef GEMDOS                           /* Atari ST */
215 #ifdef putchar
216 #undef putchar
217 #endif /* putchar */
218 #define putchar(x) conoc(x)
219 #endif /* GEMDOS */
220
221 #ifdef CK_AUTODL
222 extern int cmdadl, justone;
223 #endif /* CK_AUTODL */
224
225 extern int timelimit, nzxopts, nopush, nolocal, xcmdsrc, keepallchars;
226
227 #ifdef CKSYSLOG
228 #ifdef UNIX
229 #ifdef CKXPRINTF                        /* Our printf macro conflicts with */
230 #undef printf                           /* use of "printf" in syslog.h */
231 #endif /* CKXPRINTF */
232 #ifdef RTAIX
233 #include <sys/syslog.h>
234 #else  /* RTAIX */
235 #include <syslog.h>
236 #endif /* RTAIX */
237 #ifdef CKXPRINTF
238 #define printf ckxprintf
239 #endif /* CKXPRINTF */
240 #endif /* UNIX */
241 #endif /* CKSYSLOG */
242
243 /* Local variables */
244
245 static
246 int psetf = 0,                          /* Flag that prompt has been set */
247     cc = 0,                             /* Character count */
248     dpx = 0,                            /* Duplex (0 = full) */
249     inword = 0;                         /* In the middle of getting a word */
250
251 char *dfprom = "Command? ";             /* Default prompt */
252
253 int cmflgs;                             /* Command flags */
254 int cmfsav;                             /* A saved version of them */
255
256 static char pushc = NUL;
257 static char brkchar = NUL;
258
259 #define CMDEFAULT 1023
260 static char cmdefault[CMDEFAULT+1];
261
262 #ifdef DCMDBUF
263 char *cmdbuf = NULL;                    /* Command buffer */
264 char *savbuf = NULL;                    /* Buffer to save copy of command */
265 char *atmbuf = NULL;                    /* Atom buffer - for current field */
266 char *atxbuf = NULL;                    /* For expanding the atom buffer */
267 static char *atybuf = NULL;             /* For copying atom buffer */
268 static char *filbuf = NULL;             /* File name buffer */
269 static char *cmprom = NULL;             /* Program's prompt */
270 static char *cmprxx = NULL;             /* Program's prompt, unevaluated */
271
272 #ifdef CK_RECALL
273 /*
274   Command recall is available only if we can make profligate use of malloc().
275 */
276 #define R_MAX 10                        /* How many commands to save */
277 int cm_recall = R_MAX;                  /* Size of command recall buffer */
278 int on_recall = 1;                      /* Recall feature is ON */
279 static int no_recall = 0;               /* Recall OFF for this cmd only */
280 static int force_add = 0;               /* Force cmd into recall buffer */
281 static int last_recall = -1;            /* Last recall-related action */
282 /*
283   -1 = none
284    0 = CR (a command was entered)
285    1 = Up
286    2 = Down
287 */
288 int in_recall = 0;                      /* Recall buffers are init'd */
289 static int
290   current = -1,                         /* Pointer to current command */
291   rlast = -1;                           /* Index of last command in buffer */
292 static char **recall = NULL;            /* Array of recall buffer pointers */
293 #endif /* CK_RECALL */
294 #else  /* !DCMDBUF */
295 char cmdbuf[CMDBL+4];                   /* Command buffer */
296 char savbuf[CMDBL+4];                   /* Buffer to save copy of command */
297 char atmbuf[ATMBL+4];                   /* Atom buffer */
298 char atxbuf[CMDBL+4];                   /* For expanding the atom buffer */
299 static char atybuf[ATMBL+4];            /* For copying atom buffer */
300 static char filbuf[ATMBL+4];            /* File name buffer */
301 static char cmprom[PROMPTL+1];          /* Program's prompt */
302 static char cmprxx[PROMPTL+1];          /* Program's prompt, unevaluated */
303 #endif /* DCMDBUF */
304
305 /* Command buffer pointers */
306
307 #define PPVLEN 24
308 char ppvnambuf[PPVLEN+1] = { NUL, NUL };
309
310 char * cmbptr = NULL;                   /* Current position (for export) */
311
312 static char *bp,                        /* Current command buffer position */
313     *pp,                                /* Start of current field */
314     *np;                                /* Start of next field */
315
316 static int ungw,                        /* For ungetting words */
317     atxn;                               /* Expansion buffer (atxbuf) length */
318
319 #ifdef OS2
320 extern int wideresult;
321 #endif /* OS2 */
322
323 extern int cmd_cols, cmd_rows, local, quiet;
324
325 #ifdef TNCODE
326 #ifdef IAC
327 #undef IAC
328 #endif /* IAC */
329 #define IAC 255
330 #endif /* TNCODE */
331
332 _PROTOTYP( static int gtword, (int) );
333 _PROTOTYP( static int addbuf, (char *) );
334 _PROTOTYP( static int setatm, (char *, int) );
335 _PROTOTYP( static VOID cmdnewl, (char) );
336 _PROTOTYP( static VOID cmdchardel, (void) );
337 _PROTOTYP( static VOID cmdecho, (char, int) );
338 _PROTOTYP( static int test, (int, int) );
339 #ifdef GEMDOS
340 _PROTOTYP( extern char *strchr, (char *, int) );
341 #endif /* GEMDOS */
342
343 extern char * dftty;
344
345 /* The following are for use with chained FDB's */
346
347 static int crflag = 0;                  /* Carriage return was typed */
348 static int qmflag = 0;                  /* Question mark was typed */
349 static int esflag = 0;                  /* Escape was typed */
350
351 /* Directory separator */
352
353 #ifdef GEMDOS
354 static char dirsep = '\\';
355 #else
356 #ifdef datageneral
357 static char dirsep = ':';
358 #else
359 #ifdef MAC
360 static char dirsep = ':';
361 #else
362 #ifdef VMS
363 static char dirsep = '.';
364 #else
365 #ifdef STRATUS
366 static char dirsep = '>';
367 #else
368 static char dirsep = '/';               /* UNIX, OS/2, OS-9, Amiga, etc. */
369 #endif /* STRATUS */
370 #endif /* VMS */
371 #endif /* MAC */
372 #endif /* datageneral */
373 #endif /* GEMDOS */
374
375 /*  H A S N O P A T H  */
376
377 /*  Returns 0 if filespec s includes any path segments; 1 if it doesn't. */
378
379 int
380 hasnopath(s) char * s; {
381     char * p = NULL;
382     if (!s) return(0);
383     if (!*s) return(0);
384     zstrip(s,&p);
385     return(ckstrcmp(s,p,CKMAXPATH,filecase) == 0 ? 1 : 0);
386 }
387
388 /*  C K S P R E A D  --  Print string double-spaced  */
389
390 static char * sprptr = NULL;
391
392 static char *
393 ckspread(s) char * s; {
394     int n = 0;
395     char * p;
396     n = strlen(s);
397     if (sprptr)
398       free(sprptr);
399     sprptr = malloc(n + n + 3);
400     if (sprptr) {
401         p = sprptr;
402         while (*s) {
403             *p++ = *s++;
404             *p++ = SP;
405         }
406         *p = NUL;
407     }
408     return(sprptr ? sprptr : "");
409 }
410
411 /*  T E S T  --  Bit test  */
412
413 static int
414 test(x,m) int x, m; { /*  Returns 1 if any bits from m are on in x, else 0  */
415     return((x & m) ? 1 : 0);
416 }
417
418 /*  K W D H E L P  --  Given a keyword table, print keywords in columns.  */
419 /*
420   Call with:
421     s     - keyword table
422     n     - number of entries
423     pat   - pattern (left substring) that must match for each keyword
424     pre   - prefix to add to each keyword
425     post  - suffix to add to each keyword
426     off   - offset on first screenful, allowing room for introductory text
427     xhlp  - 1 to print any CM_INV keywords that are not also abbreviations.
428             2 to print CM_INV keywords if CM_HLP also set
429             4 if it's a switch table (to show ':' if CM_ARG)
430
431   Arranges keywords in columns with width based on longest keyword.
432   Does "more?" prompting at end of screen.
433   Uses global cmd_rows and cmd_cols for screen size.
434 */
435 VOID
436 kwdhelp(s,n,pat,pre,post,off,xhlp)
437     struct keytab s[]; int n, off, xhlp; char *pat, *pre, *post;
438 /* kwdhelp */ {
439
440     int width = 0;
441     int cc;
442     int cols, height, i, j, k, lc, n2 = 0;
443     char *b = NULL, *p, *q;
444     char *pa, *px;
445     char **s2 = NULL;
446     char *tmpbuf = NULL;
447
448     cc = strlen(pat);
449
450     if (!s) return;                     /* Nothing to do */
451     if (n < 1) return;                  /* Ditto */
452     if (off < 0) off = 0;               /* Offset for first page */
453     if (!pre) pre = "";                 /* Handle null string pointers */
454     if (!post) post = "";
455     lc = off;                           /* Screen-line counter */
456
457     if (xhlp & 4)                       /* For switches */
458       tmpbuf = (char *)malloc(TMPBUFSIZ+1);
459
460     if ((s2 = (char **) malloc(n * sizeof(char *)))) {
461         for (i = 0; i < n; i++) {       /* Find longest keyword */
462             s2[i] = NULL;
463             if (ckstrcmp(s[i].kwd,pat,cc,0))
464               continue;
465
466             if (s[i].flgs & CM_PSH      /* NOPUSH or nopush screening */
467 #ifndef NOPUSH
468                 && nopush
469 #endif /* NOPUSH */
470                 )
471               continue;
472             if (s[i].flgs & CM_LOC      /* NOLOCAL or nolocal screening */
473 #ifndef NOLOCAL
474                 && nolocal
475 #endif /* NOLOCAL */
476                 )
477               continue;
478
479             if (s[i].flgs & CM_INV) {
480 #ifdef COMMENT
481 /* This code does not show invisible keywords at all except for "help ?" */
482 /* and then only help topics (CM_HLP) in the top-level keyword list. */
483
484                 if ((xhlp & 2) == 0)
485                   continue;
486                 else if ((s[i].flgs & CM_HLP) == 0)
487                   continue;
488 #else
489 /* This code shows invisible keywords that are not also abbreviations when */
490 /* ? was typed AFTER the beginning of the field so the user can find out */
491 /* what they are and (for example) why completion doesn't work at this point */
492
493                 if (s[i].flgs & CM_ABR)
494                   continue;
495                 else if ((xhlp & 3) == 0)
496                   continue;
497                 else if ((xhlp & 2) && ((s[i].flgs & CM_HLP) == 0))
498                   continue;
499 #endif /* COMMENT */
500             }
501             j = strlen(s[i].kwd);
502             if (!(xhlp & 4) || !tmpbuf) { /* Regular keyword table */
503                 s2[n2++] = s[i].kwd;    /* Copy pointers to visible ones */
504             } else {                    /* Switches */
505                 ckmakmsg(tmpbuf,        /* Make a copy that shows ":" if */
506                          TMPBUFSIZ,     /* the switch takes an argument. */
507                          s[i].kwd,
508                          (s[i].flgs & CM_ARG) ? ":" : "",
509                          NULL,
510                          NULL
511                          );
512                 makestr(&(s2[n2]),tmpbuf);
513                 if (s[i].flgs & CM_ARG) j++;
514                 n2++;
515             }
516             if (j > width)
517               width = j;
518         }
519         /* Column width */
520         n = n2;
521     }
522     if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer   */
523         char * bx;
524         bx = b + cmd_cols;
525         width += (int)strlen(pre) + (int)strlen(post) + 2;
526         cols = cmd_cols / width;        /* How many columns? */
527         if (cols < 1) cols = 1;
528         height = n / cols;              /* How long is each column? */
529         if (n % cols) height++;         /* Add one for remainder, if any */
530
531         for (i = 0; i < height; i++) {      /* Loop for each row */
532             for (j = 0; j < cmd_cols; j++)  /* First fill row with blanks */
533               b[j] = SP;
534             for (j = 0; j < cols; j++) {    /* Loop for each column in row */
535                 k = i + (j * height);       /* Index of next keyword */
536                 if (k < n) {                /* In range? */
537                     pa = pre;
538                     px = post;
539                     p = s2[k];              /* Point to verb name */
540                     q = b + (j * width) + 1; /* Where to copy it to */
541                     while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
542                     q--;                                 /* Back up over NUL */
543                     while ((q < bx) && (*q++ = *p++)) ;  /* Copy filename */
544                     q--;                                 /* Back up over NUL */
545                     while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
546                     if (j < cols - 1) {
547                         q--;
548                         *q = SP;        /* Replace the space */
549                     }
550                 }
551             }
552             p = b + cmd_cols - 1;       /* Last char in line */
553             while (*p-- == SP) ;        /* Trim */
554             *(p+2) = NUL;
555             printf("%s\n",b);           /* Print the line */
556             if (++lc > (cmd_rows - 2)) { /* Screen full? */
557                 if (!askmore())         /* Do more-prompting... */
558                   goto xkwdhelp;
559                 else
560                   lc = 0;
561             }
562         }
563         /* printf("\n"); */             /* Blank line at end of report */
564     } else {                            /* Malloc failure, no columns */
565         for (i = 0; i < n; i++) {
566             if (s[i].flgs & CM_INV)     /* Use original keyword table */
567               continue;                 /* skipping invisible entries */
568             printf("%s%s%s\n",pre,s[i].kwd,post);
569             if (++lc > (cmd_rows - 2)) { /* Screen full? */
570                 if (!askmore())         /* Do more-prompting... */
571                   goto xkwdhelp;
572                 else
573                   lc = 0;
574             }
575         }
576     }
577   xkwdhelp:
578     if (xhlp & 4) {
579         if (tmpbuf) free((char *)tmpbuf);
580         for (i = 0; i < n; i++)
581           if (s2[i]) free(s2[i]);
582     }
583     if (s2) free(s2);                   /* Free array copy */
584     if (b) free(b);                     /* Free line buffer */
585     return;
586 }
587
588 /*  F I L H E L P  --  Given a file list, print names in columns.  */
589 /*
590   Call with:
591     n     - number of entries
592     pre   - prefix to add to each filename
593     post  - suffix to add to each filename
594     off   - offset on first screenful, allowing room for introductory text
595     cmdirflg - 1 if only directory names should be listed, 0 to list all files
596
597   Arranges filenames in columns with width based on longest filename.
598   Does "more?" prompting at end of screen.
599   Uses global cmd_rows and cmd_cols for screen size.
600 */
601
602 int
603 filhelp(n,pre,post,off,cmdirflg) int n, off; char *pre, *post; int cmdirflg; {
604     char filbuf[CKMAXPATH + 1];         /* Temp buffer for one filename */
605     int width = 0;
606     int cols, height, i, j, k, lc, n2 = 0, rc = 0, itsadir = 0;
607     char *b = NULL, *p, *q;
608     char *pa, *px;
609     char **s2 = NULL;
610 #ifdef VMS
611     char * cdp = zgtdir();
612 #endif /* VMS */
613
614     if (n < 1) return(0);
615     if (off < 0) off = 0;               /* Offset for first page */
616     if (!pre) pre = "";                 /* Handle null string pointers */
617     if (!post) post = "";
618
619     lc = off;                           /* Screen-line counter */
620
621     if ((s2 = (char **) malloc(n * sizeof(char *)))) {
622         for (i = 0; i < n; i++) {       /* Loop through filenames */
623             itsadir = 0;
624             s2[i] = NULL;               /* Initialize each pointer to NULL */
625             znext(filbuf);              /* Get next filename */
626             if (!filbuf[0])             /* Shouldn't happen */
627               break;
628 #ifdef COMMENT
629             itsadir = isdir(filbuf);    /* Is it a directory? */
630             if (cmdirflg && !itsadir)   /* No, listing directories only? */
631               continue;                 /* So skip this one. */
632 #endif /* COMMENT */
633 #ifdef VMS
634             ckstrncpy(filbuf,zrelname(filbuf,cdp),CKMAXPATH);
635 #endif /* VMS */
636             j = strlen(filbuf);
637 #ifndef VMS
638             if (itsadir && j < CKMAXPATH - 1 && j > 0) {
639                 if (filbuf[j-1] != dirsep) {
640                     filbuf[j++] = dirsep;
641                     filbuf[j] = NUL;
642                 }
643             }
644 #endif /* VMS */
645             if (!(s2[n2] = malloc(j+1))) {
646                 printf("?Memory allocation failure\n");
647                 rc = -9;
648                 goto xfilhelp;
649             }
650             if (j <= CKMAXPATH) {
651                 strcpy(s2[n2],filbuf);
652                 n2++;
653             } else {
654                 printf("?Name too long - %s\n", filbuf);
655                 rc = -9;
656                 goto xfilhelp;
657             }
658             if (j > width)              /* Get width of widest one */
659               width = j;
660         }
661         n = n2;                         /* How many we actually got */
662     }
663     sh_sort(s2,NULL,n,0,0,filecase);    /* Alphabetize the list */
664
665     rc = 1;
666     if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
667         char * bx;
668         bx = b + cmd_cols;
669         width += (int)strlen(pre) + (int)strlen(post) + 2;
670         cols = cmd_cols / width;        /* How many columns? */
671         if (cols < 1) cols = 1;
672         height = n / cols;              /* How long is each column? */
673         if (n % cols) height++;         /* Add one for remainder, if any */
674
675         for (i = 0; i < height; i++) {      /* Loop for each row */
676             for (j = 0; j < cmd_cols; j++)  /* First fill row with blanks */
677               b[j] = SP;
678             for (j = 0; j < cols; j++) {    /* Loop for each column in row */
679                 k = i + (j * height);       /* Index of next filename */
680                 if (k < n) {                /* In range? */
681                     pa = pre;
682                     px = post;
683                     p = s2[k];                         /* Point to filename */
684                     q = b + (j * width) + 1;             /* and destination */
685                     while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
686                     q--;                                 /* Back up over NUL */
687                     while ((q < bx) && (*q++ = *p++)) ;  /* Copy filename */
688                     q--;                                 /* Back up over NUL */
689                     while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
690                     if (j < cols - 1) {
691                         q--;
692                         *q = SP;        /* Replace the space */
693                     }
694                 }
695             }
696             p = b + cmd_cols - 1;       /* Last char in line */
697             while (*p-- == SP) ;        /* Trim */
698             *(p+2) = NUL;
699             printf("%s\n",b);           /* Print the line */
700             if (++lc > (cmd_rows - 2)) { /* Screen full? */
701                 if (!askmore()) {       /* Do more-prompting... */
702                     rc = 0;
703                     goto xfilhelp;
704                 } else
705                   lc = 0;
706             }
707         }
708         printf("\n");                   /* Blank line at end of report */
709         goto xfilhelp;
710     } else {                            /* Malloc failure, no columns */
711         for (i = 0; i < n; i++) {
712             znext(filbuf);
713             if (!filbuf[0]) break;
714             printf("%s%s%s\n",pre,filbuf,post);
715             if (++lc > (cmd_rows - 2)) { /* Screen full? */
716                 if (!askmore()) {        /* Do more-prompting... */
717                     rc = 0;
718                     goto xfilhelp;
719                 } else lc = 0;
720             }
721         }
722 xfilhelp:
723         if (b) free(b);
724         for (i = 0; i < n2; i++)
725           if (s2[i]) free(s2[i]);
726         if (s2) free((char *)s2);
727         return(rc);
728     }
729 }
730
731 /*  C M S E T U P  --  Set up command buffers  */
732
733 #ifdef DCMDBUF
734 int
735 cmsetup() {
736     if (!(cmdbuf = malloc(CMDBL + 4))) return(-1);
737     if (!(savbuf = malloc(CMDBL + 4))) return(-1);
738     savbuf[0] = '\0';
739     if (!(atmbuf = malloc(ATMBL + 4))) return(-1);
740     if (!(atxbuf = malloc(CMDBL + 4))) return(-1);
741     if (!(atybuf = malloc(ATMBL + 4))) return(-1);
742     if (!(filbuf = malloc(ATMBL + 4))) return(-1);
743     if (!(cmprom = malloc(PROMPTL + 4))) return(-1);
744     if (!(cmprxx = malloc(PROMPTL + 4))) return(-1);
745 #ifdef CK_RECALL
746     cmrini(cm_recall);
747 #endif /* CK_RECALL */
748     return(0);
749 }
750 #endif /* DCMDBUF */
751
752 /*  C M S E T P  --  Set the program prompt.  */
753
754 VOID
755 cmsetp(s) char *s; {
756     if (!s) s = "";
757     ckstrncpy(cmprxx,s,PROMPTL);
758     psetf = 1;                          /* Flag that prompt has been set. */
759 }
760
761 /*  C M S A V P  --  Save a copy of the current prompt.  */
762
763 VOID
764 #ifdef CK_ANSIC
765 cmsavp(char s[], int n)
766 #else
767 cmsavp(s,n) char s[]; int n;
768 #endif /* CK_ANSIC */
769 /* cmsavp */ {
770     if (psetf)                          /* But not if no prompt is set. */
771       ckstrncpy(s,cmprxx,n);
772 }
773
774 char *
775 cmgetp() {
776     return(cmprxx);
777 }
778
779 int
780 cmgbrk() {
781     return(brkchar);
782 }
783
784 int
785 cmgkwflgs() {
786     return(cmkwflgs);
787 }
788
789 /*  P R O M P T  --  Issue the program prompt.  */
790
791 VOID
792 prompt(f) xx_strp f; {
793     char *sx, *sy; int n;
794 #ifdef CK_SSL
795     extern int ssl_active_flag, tls_active_flag;
796 #endif /* CK_SSL */
797 #ifdef OS2
798     extern int display_demo;
799
800     /* If there is a demo screen to be displayed, display it */
801     if (display_demo && xcmdsrc == 0) {
802         demoscrn(VCMD);
803         display_demo = 0;
804     }
805 #endif /* OS2 */
806
807     if (psetf == 0)                     /* If no prompt set, set default. */
808       cmsetp(dfprom);
809
810     sx = cmprxx;                        /* Unevaluated copy */
811     if (f) {                            /* If conversion function given */
812         sy = cmprom;                    /* Evaluate it */
813         debug(F101,"prompt sx","",sx);
814         debug(F101,"prompt sy","",sy);
815         n = PROMPTL;
816         if ((*f)(sx,&sy,&n) < 0)        /* If evaluation failed */
817           sx = cmprxx;                  /* revert to unevaluated copy */
818         else if (!*cmprom)              /* ditto if it came up empty */
819           sx = cmprxx;
820         else
821           sx = cmprom;
822     } else
823       ckstrncpy(cmprom,sx,PROMPTL);
824     cmprom[PROMPTL-1] = NUL;
825     if (!*sx)                           /* Don't print if empty */
826       return;
827
828 #ifdef OSK
829     fputs(sx, stdout);
830 #else
831 #ifdef MAC
832     printf("%s", sx);
833 #else
834 #ifdef IKSD
835     if (inserver) {                     /* Print the prompt. */
836         ttoc(CR);                       /* If TELNET Server */
837         ttoc(NUL);                      /* must folloW CR by NUL */
838         printf("%s",sx);
839     } else
840 #endif /* IKSD */
841       printf("\r%s",sx);
842 #ifdef CK_SSL
843     if (!(ssl_active_flag || tls_active_flag))
844 #endif /* CK_SSL */
845       fflush(stdout);                   /* Now! */
846 #endif /* MAC */
847 #endif /* OSK */
848 }
849
850 #ifndef NOSPL
851 VOID
852 pushcmd(s) char * s; {                  /* For use with IF command. */
853     if (!s) s = np;
854     ckstrncpy(savbuf,s,CMDBL);          /* Save the dependent clause,  */
855     cmres();                            /* and clear the command buffer. */
856     debug(F110, "pushcmd savbuf", savbuf, 0);
857 }
858
859 VOID
860 pushqcmd(s) char * s; {                 /* For use with ELSE command. */
861     char c, * p = savbuf;               /* Dest */
862     if (!s) s = np;                     /* Source */
863     while (*s) {                        /* Get first nonwhitespace char */
864         if (*s != SP)
865           break;
866         else
867           s++;
868     }
869     if (*s != '{') {                    /* If it's not "{" */
870         pushcmd(s);                     /* do regular pushcmd */
871         return;
872     }
873     while ((c = *s++)) {                /* Otherwise insert quotes */
874         if (c == CMDQ)
875           *p++ = CMDQ;
876         *p++ = c;
877     }
878     cmres();                            /* and clear the command buffer. */
879     debug(F110, "pushqcmd savbuf", savbuf, 0);
880 }
881 #endif /* NOSPL */
882
883 #ifdef COMMENT
884 /* no longer used... */
885 VOID
886 popcmd() {
887     ckstrncpy(cmdbuf,savbuf,CMDBL);     /* Put back the saved material */
888     *savbuf = '\0';                     /* and clear the save buffer */
889     cmres();
890 }
891 #endif /* COMMENT */
892
893 /*  C M R E S  --  Reset pointers to beginning of command buffer.  */
894
895 VOID
896 cmres() {
897     inword = 0;                         /* We're not in a word */
898     cc = 0;                             /* Character count is zero */
899
900 /* Initialize pointers */
901
902     pp = cmdbuf;                        /* Beginning of current field */
903     bp = cmdbuf;                        /* Current position within buffer */
904     np = cmdbuf;                        /* Where to start next field */
905
906     cmfldflgs = 0;
907     cmflgs = -5;                        /* Parse not yet started. */
908     ungw = 0;                           /* Don't need to unget a word. */
909 }
910
911 /*  C M I N I  --  Clear the command and atom buffers, reset pointers.  */
912
913 /*
914 The argument specifies who is to echo the user's typein --
915   1 means the cmd package echoes
916   0 somebody else (system, front end, terminal) echoes
917 */
918 VOID
919 cmini(d) int d; {
920 #ifdef DCMDBUF
921     if (!atmbuf)
922       if (cmsetup()<0)
923         fatal("fatal error: unable to allocate command buffers");
924 #endif /* DCMDBUF */
925 #ifdef USE_MEMCPY
926     memset(cmdbuf,0,CMDBL);
927     memset(atmbuf,0,ATMBL);
928 #else
929     for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
930     for (bp = atmbuf; bp < atmbuf+ATMBL; bp++) *bp = NUL;
931 #endif /* USE_MEMCPY */
932
933     *atmbuf = *savbuf = *atxbuf = *atybuf = *filbuf = NUL;
934     blocklvl = 0;                       /* Block level is 0 */
935     linebegin = 1;                      /* At the beginning of a line */
936     dpx = d;                            /* Global copy of the echo flag */
937     debug(F101,"cmini dpx","",dpx);
938     crflag = 0;                         /* Reset flags */
939     qmflag = 0;
940     esflag = 0;
941 #ifdef CK_RECALL
942     no_recall = 0;                      /* Start out with recall enabled */
943 #endif /* CK_RECALL */
944     cmres();                            /* Sets bp etc */
945     newcmd = 1;                         /* See addcmd() */
946 }
947
948 #ifndef NOSPL
949 /*
950   The following bits are to allow the command package to call itself
951   in the middle of a parse.  To do this, begin by calling cmpush, and
952   end by calling cmpop.  As you can see, this is rather expensive.
953 */
954 #ifdef DCMDBUF
955 struct cmp {
956     int i[5];                           /* stack for integers */
957     char *c[3];                         /* stack for pointers */
958     char *b[8];                         /* stack for buffer contents */
959 };
960 struct cmp *cmp = 0;
961 #else
962 int cmp_i[CMDDEP+1][5];                 /* Stack for integers */
963 char *cmp_c[CMDDEP+1][5];               /* for misc pointers */
964 char *cmp_b[CMDDEP+1][7];               /* for buffer contents pointers */
965 #endif /* DCMDBUF */
966
967 int cmddep = -1;                        /* Current stack depth */
968
969 int
970 cmpush() {                              /* Save the command environment */
971     char *cp;                           /* Character pointer */
972
973     if (cmddep >= CMDDEP)               /* Enter a new command depth */
974       return(-1);
975     cmddep++;
976     debug(F101,"&cmpush to depth","",cmddep);
977
978 #ifdef DCMDBUF
979     /* allocate memory for cmp if not already done */
980     if (!cmp && !(cmp = (struct cmp *) malloc(sizeof(struct cmp)*(CMDDEP+1))))
981       fatal("cmpush: no memory for cmp");
982     cmp[cmddep].i[0] = cmflgs;          /* First do the global ints */
983     cmp[cmddep].i[1] = cmfsav;
984     cmp[cmddep].i[2] = atxn;
985     cmp[cmddep].i[3] = ungw;
986
987     cmp[cmddep].c[0] = bp;              /* Then the global pointers */
988     cmp[cmddep].c[1] = pp;
989     cmp[cmddep].c[2] = np;
990 #else
991     cmp_i[cmddep][0] = cmflgs;          /* First do the global ints */
992     cmp_i[cmddep][1] = cmfsav;
993     cmp_i[cmddep][2] = atxn;
994     cmp_i[cmddep][3] = ungw;
995
996     cmp_c[cmddep][0] = bp;              /* Then the global pointers */
997     cmp_c[cmddep][1] = pp;
998     cmp_c[cmddep][2] = np;
999 #endif /* DCMDBUF */
1000
1001     /* Now the buffers themselves.  A lot of repititious code... */
1002
1003 #ifdef DCMDBUF
1004     cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1005     if (cp) strcpy(cp,cmdbuf);
1006     cmp[cmddep].b[0] = cp;
1007     if (cp == NULL) return(-1);
1008
1009     cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1010     if (cp) strcpy(cp,savbuf);
1011     cmp[cmddep].b[1] = cp;
1012     if (cp == NULL) return(-1);
1013
1014     cmp[cmddep].b[2] = NULL;
1015
1016     cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1017     if (cp) strcpy(cp,atmbuf);
1018     cmp[cmddep].b[3] = cp;
1019     if (cp == NULL) return(-1);
1020
1021     cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1022     if (cp) strcpy(cp,atxbuf);
1023     cmp[cmddep].b[4] = cp;
1024     if (cp == NULL) return(-1);
1025
1026     cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1027     if (cp) strcpy(cp,atybuf);
1028     cmp[cmddep].b[5] = cp;
1029     if (cp == NULL) return(-1);
1030
1031     cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1032     if (cp) strcpy(cp,filbuf);
1033     cmp[cmddep].b[6] = cp;
1034     if (cp == NULL) return(-1);
1035 #else
1036     cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1037     if (cp) strcpy(cp,cmdbuf);
1038     cmp_b[cmddep][0] = cp;
1039     if (cp == NULL) return(-1);
1040
1041     cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1042     if (cp) strcpy(cp,savbuf);
1043     cmp_b[cmddep][1] = cp;
1044     if (cp == NULL) return(-1);
1045
1046     cmp_b[cmddep][2] = NULL;
1047
1048     cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1049     if (cp) strcpy(cp,atmbuf);
1050     cmp_b[cmddep][3] = cp;
1051     if (cp == NULL) return(-1);
1052
1053     cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1054     if (cp) strcpy(cp,atxbuf);
1055     cmp_b[cmddep][4] = cp;
1056     if (cp == NULL) return(-1);
1057
1058     cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1059     if (cp) strcpy(cp,atybuf);
1060     cmp_b[cmddep][5] = cp;
1061     if (cp == NULL) return(-1);
1062
1063     cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1064     if (cp) strcpy(cp,filbuf);
1065     cmp_b[cmddep][6] = cp;
1066     if (cp == NULL) return(-1);
1067 #endif /* DCMDBUF */
1068
1069     cmini(dpx);                         /* Initize the command parser */
1070     return(0);
1071 }
1072
1073 int
1074 cmpop() {                               /* Restore the command environment */
1075     if (cmddep < 0) {
1076         debug(F100,"&cmpop called from top level","",0);
1077         return(-1);                     /* Don't pop too much! */
1078     }
1079 #ifdef DCMDBUF
1080     cmflgs = cmp[cmddep].i[0];          /* First do the global ints */
1081     cmfsav = cmp[cmddep].i[1];
1082     atxn = cmp[cmddep].i[2];
1083     ungw = cmp[cmddep].i[3];
1084
1085     bp = cmp[cmddep].c[0];              /* Then the global pointers */
1086     pp = cmp[cmddep].c[1];
1087     np = cmp[cmddep].c[2];
1088 #else
1089     cmflgs = cmp_i[cmddep][0];          /* First do the global ints */
1090     cmfsav = cmp_i[cmddep][1];
1091     atxn = cmp_i[cmddep][2];
1092     ungw = cmp_i[cmddep][3];
1093
1094     bp = cmp_c[cmddep][0];              /* Then the global pointers */
1095     pp = cmp_c[cmddep][1];
1096     np = cmp_c[cmddep][2];
1097 #endif /* DCMDBUF */
1098
1099     /* Now the buffers themselves. */
1100     /* Note: strncpy(), not ckstrncpy() -- Here we WANT the NUL padding... */
1101
1102 #ifdef DCMDBUF
1103     if (cmp[cmddep].b[0]) {
1104
1105         strncpy(cmdbuf,cmp[cmddep].b[0],CMDBL); /* 0: Command buffer */
1106         free(cmp[cmddep].b[0]);
1107         cmp[cmddep].b[0] = NULL;
1108     }
1109     if (cmp[cmddep].b[1]) {
1110         strncpy(savbuf,cmp[cmddep].b[1],CMDBL); /* 1: Save buffer */
1111         free(cmp[cmddep].b[1]);
1112         cmp[cmddep].b[1] = NULL;
1113     }
1114     if (cmp[cmddep].b[3]) {
1115         strncpy(atmbuf,cmp[cmddep].b[3],ATMBL); /* 3: Atomic buffer! */
1116         free(cmp[cmddep].b[3]);
1117         cmp[cmddep].b[3] = NULL;
1118     }
1119     if (cmp[cmddep].b[4]) {
1120         strncpy(atxbuf,cmp[cmddep].b[4],ATMBL); /* 4: eXpansion buffer */
1121         free(cmp[cmddep].b[4]);
1122         cmp[cmddep].b[4] = NULL;
1123     }
1124     if (cmp[cmddep].b[5]) {
1125         strncpy(atybuf,cmp[cmddep].b[5],ATMBL); /* 5: Atom buffer copY */
1126         free(cmp[cmddep].b[5]);
1127         cmp[cmddep].b[5] = NULL;
1128     }
1129     if (cmp[cmddep].b[6]) {
1130         strncpy(filbuf,cmp[cmddep].b[6],ATMBL); /* 6: Filename buffer */
1131         free(cmp[cmddep].b[6]);
1132         cmp[cmddep].b[6] = NULL;
1133     }
1134 #else
1135     if (cmp_b[cmddep][0]) {
1136         strncpy(cmdbuf,cmp_b[cmddep][0],CMDBL); /* 0: Command buffer */
1137         free(cmp_b[cmddep][0]);
1138         cmp_b[cmddep][0] = NULL;
1139     }
1140     if (cmp_b[cmddep][1]) {
1141         strncpy(savbuf,cmp_b[cmddep][1],CMDBL); /* 1: Save buffer */
1142         free(cmp_b[cmddep][1]);
1143         cmp_b[cmddep][1] = NULL;
1144     }
1145     if (cmp_b[cmddep][3]) {
1146         strncpy(atmbuf,cmp_b[cmddep][3],ATMBL); /* 3: Atomic buffer! */
1147         free(cmp_b[cmddep][3]);
1148         cmp_b[cmddep][3] = NULL;
1149     }
1150     if (cmp_b[cmddep][4]) {
1151         strncpy(atxbuf,cmp_b[cmddep][4],ATMBL); /* 4: eXpansion buffer */
1152         free(cmp_b[cmddep][4]);
1153         cmp_b[cmddep][4] = NULL;
1154     }
1155     if (cmp_b[cmddep][5]) {
1156         strncpy(atybuf,cmp_b[cmddep][5],ATMBL); /* 5: Atom buffer copY */
1157         free(cmp_b[cmddep][5]);
1158         cmp_b[cmddep][5] = NULL;
1159     }
1160     if (cmp_b[cmddep][6]) {
1161         strncpy(filbuf,cmp_b[cmddep][6],ATMBL); /* 6: Filename buffer */
1162         free(cmp_b[cmddep][6]);
1163         cmp_b[cmddep][6] = NULL;
1164     }
1165 #endif /* DCMDBUF */
1166
1167     cmddep--;                           /* Rise, rise */
1168     debug(F101,"&cmpop to depth","",cmddep);
1169     return(cmddep);
1170 }
1171 #endif /* NOSPL */
1172
1173 #ifdef COMMENT
1174 VOID                                    /* Not used */
1175 stripq(s) char *s; {                    /* Function to strip '\' quotes */
1176     char *t;
1177     while (*s) {
1178         if (*s == CMDQ) {
1179             for (t = s; *t != '\0'; t++) *t = *(t+1);
1180         }
1181         s++;
1182     }
1183 }
1184 #endif /* COMMENT */
1185
1186 /* Convert tabs to spaces, one for one */
1187 VOID
1188 untab(s) char *s; {
1189     while (*s) {
1190         if (*s == HT) *s = SP;
1191         s++;
1192     }
1193 }
1194
1195 /*  C M N U M  --  Parse a number in the indicated radix  */
1196
1197 /*
1198  The radix is specified in the arg list.
1199  Parses unquoted numeric strings in the given radix.
1200  Parses backslash-quoted numbers in the radix indicated by the quote:
1201    \nnn = \dnnn = decimal, \onnn = octal, \xnn = Hexadecimal.
1202  If these fail, then if a preprocessing function is supplied, that is applied
1203  and then a second attempt is made to parse an unquoted decimal string.
1204  And if that fails, the preprocessed string is passed to an arithmetic
1205  expression evaluator.
1206
1207  Returns:
1208    -3 if no input present when required,
1209    -2 if user typed an illegal number,
1210    -1 if reparse needed,
1211     0 otherwise, with argument n set to the number that was parsed
1212 */
1213 int
1214 cmnum(xhlp,xdef,radix,n,f) char *xhlp, *xdef; int radix, *n; xx_strp f; {
1215     int x; char *s, *zp, *zq;
1216 #ifdef COMMENT
1217     char lbrace, rbrace;
1218 #endif /* COMMENT */
1219
1220     if (!xhlp) xhlp = "";
1221     if (!xdef) xdef = "";
1222
1223 #ifdef COMMENT
1224     if (cmfldflgs & 1) {
1225         lbrace = '(';
1226         rbrace = ')';
1227     } else {
1228         lbrace = '{';
1229         rbrace = '}';
1230     }
1231 #endif /* COMMENT */
1232
1233     if (radix != 10 && radix != 8) {    /* Just do bases 8 and 10 */
1234         printf("cmnum: illegal radix - %d\n",radix);
1235         return(-2);
1236     } /* Easy to add others but there has never been a need for it. */
1237     x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1238     debug(F101,"cmnum: cmfld","",x);
1239     if (x < 0) return(x);               /* Parse a field */
1240     zp = atmbuf;
1241 /*
1242   Edit 192 - Allow any number field to be braced.  This lets us include
1243   spaces in expressions, but perhaps more important lets us have user-defined
1244   functions in numeric fields.
1245 */
1246     zp = brstrip(zp);                   /* Strip braces */
1247     if (cmfldflgs & 1 && *zp == '(') {  /* Parens too.. */
1248         x = (int) strlen(atmbuf);
1249         if (x > 0) {
1250             if (*(atmbuf+x-1) == ')') {
1251                 *(atmbuf+x-1) = NUL;
1252                 zp++;
1253             }
1254         }
1255     }
1256     if (chknum(zp)) {                   /* Check for number */
1257         if (radix == 8) {               /* If it's supposed to be octal */
1258             zp = ckradix(zp,8,10);      /* convert to decimal */
1259             if (!zp) return(-2);
1260             if (!strcmp(zp,"-1")) return(-2);
1261         }
1262         errno = 0;                      /* Got one, we're done. */
1263         *n = atoi(zp);
1264         if (errno) {
1265             perror(zp);
1266             return(-9);
1267         }
1268         debug(F101,"cmnum 1st chknum ok","",*n);
1269         return(0);
1270     } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1271
1272 #ifndef OS2
1273         *n = x;
1274 #else
1275         *n = wideresult;
1276 #endif /* OS2 */
1277
1278         debug(F101,"cmnum xxesc ok","",*n);
1279         return(*zp ? -2 : 0);
1280     } else if (f) {                     /* If conversion function given */
1281         zq = atxbuf;                    /* Try that */
1282         atxn = CMDBL;
1283         if ((*f)(zp,&zq,&atxn) < 0)     /* Convert */
1284           return(-2);
1285         zp = atxbuf;
1286     }
1287     debug(F110,"cmnum zp 1",zp,0);
1288     if (!*zp) zp = xdef;                /* Result empty, substitute default */
1289     debug(F110,"cmnum zp 2",zp,0);
1290     if (chknum(zp)) {                   /* Check again for decimal number */
1291         if (radix == 8) {               /* If it's supposed to be octal */
1292             zp = ckradix(zp,8,10);      /* convert to decimal */
1293             if (!zp) return(-2);
1294             if (!strcmp(zp,"-1")) return(-2);
1295         }
1296         errno = 0;
1297         *n = atoi(zp);
1298         if (errno) {
1299             perror(zp);
1300             return(-9);
1301         }
1302         debug(F101,"cmnum 2nd chknum ok","",*n);
1303         return(0);
1304 #ifndef NOSPL
1305     }  else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1306 #ifndef OS2
1307         *n = x;
1308 #else
1309         *n = wideresult;
1310 #endif /* OS2 */
1311         debug(F101,"cmnum xxesc 2 ok","",*n);
1312         return(*zp ? -2 : 0);
1313     } else if (f) {                     /* Not numeric, maybe an expression */
1314         char * p;
1315         p = evala(zp);
1316         if (chknum(p)) {
1317             if (radix == 8) {           /* If it's supposed to be octal */
1318                 zp = ckradix(zp,8,10);  /* convert to decimal */
1319                 if (!zp) return(-2);
1320                 if (!strcmp(zp,"-1")) return(-2);
1321             }
1322             errno = 0;
1323             *n = atoi(p);
1324             if (errno) {
1325                 perror(p);
1326                 return(-9);
1327             }
1328             debug(F101,"cmnum exp eval ok","",*n);
1329             return(0);
1330         } else return(-2);
1331 #endif /* NOSPL */
1332     } else {                            /* Not numeric */
1333         return(-2);
1334     }
1335 }
1336
1337 #ifdef CKCHANNELIO
1338 extern int z_error;
1339 #endif /* CKCHANNELIO */
1340
1341 /*  C M O F I  --  Parse the name of an output file  */
1342
1343 /*
1344  Depends on the external function zchko(); if zchko() not available, use
1345  cmfld() to parse output file names.
1346
1347  Returns:
1348    -9 like -2, except message already printed,
1349    -3 if no input present when required,
1350    -2 if permission would be denied to create the file,
1351    -1 if reparse needed,
1352     0 or 1 if file can be created, with xp pointing to name.
1353     2 if given the name of an existing directory.
1354 */
1355 int
1356 cmofi(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1357     int x; char *s, *zq;
1358 #ifdef DOCHKVAR
1359     int tries;
1360 #endif /* DOCHKVAR */
1361 #ifdef DTILDE
1362     char *dirp;
1363 #endif /* DTILDE */
1364
1365     cmfldflgs = 0;
1366
1367     if (!xhlp) xhlp = "";
1368     if (!xdef) xdef = "";
1369
1370     if (*xhlp == NUL) xhlp = "Output file";
1371     *xp = "";
1372
1373     debug(F110,"cmofi xdef",xdef,0);
1374     x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1375     debug(F111,"cmofi cmfld returns",s,x);
1376     if (x < 0)
1377       return(x);
1378
1379     s = brstrip(s);                     /* Strip enclosing braces */
1380     debug(F110,"cmofi 1.5",s,0);
1381
1382 #ifdef DOCHKVAR
1383     tries = 0;
1384     {
1385         char *p = s;
1386     /*
1387       This is really ugly.  If we skip conversion the first time through,
1388       then variable names like \%a will be used as filenames (e.g. creating
1389       a file called %A in the root directory).  If we DON'T skip conversion
1390       the first time through, then single backslashes used as directory
1391       separators in filenames will be misinterpreted as variable lead-ins.
1392       So we prescan to see if it has any variable references.  But this
1393       module is not supposed to know anything about variables, functions,
1394       etc, so this code does not really belong here, but rather it should
1395       be at the same level as zzstring().
1396     */
1397 /*
1398   Hmmm, this looks a lot like chkvar() except it that includes \nnn number
1399   escapes.  But why?  This makes commands like "mkdir c:\123" impossible.
1400   And in fact, "mkdir c:\123" creates a directory called "c:{".  What's worse,
1401   rmdir(), which *does* call chkvar(), won't let us remove it.  So let's at
1402   least try making cmofi() symmetrical with cmifi()...
1403 */
1404 #ifdef COMMENT
1405         char * q;
1406         while ( (tries == 0) && (p = strchr(p,CMDQ)) ) {
1407             q = *(p+1);                 /* Char after backslash */
1408             if (!q)                     /* None, quit */
1409               break;
1410             if (isupper(q))             /* If letter, convert to lowercase */
1411               q = tolower(q);
1412             if (isdigit(q)) {           /* If it's a digit, */
1413                 tries = 1;              /* assume it's a backslash code  */
1414                 break;
1415             }
1416             switch (q) {
1417               case CMDQ:                /* Double backslash */
1418                 tries = 1;              /* so call the conversion function */
1419                 break;
1420               case '%':                 /* Variable or array reference */
1421               case '&':                 /* must be followed by letter */
1422                 if (isalpha(*(p+2)) || (*(p+2) >= '0' && *(p+2) <= '9'))
1423                   tries = 1;
1424                 break;
1425               case 'm': case 'v': case '$': /* \m(), \v(), \$() */
1426                 if (*(p+2) == '(')
1427                   if (strchr(p+2,')'))
1428                     tries = 1;
1429                 break;
1430               case 'f':                 /* \Fname() */
1431                 if (strchr(p+2,'('))
1432                   if (strchr(p+2,')'))
1433                       tries = 1;
1434                 break;
1435               case '{':                 /* \{...} */
1436                 if (strchr(p+2,'}'))
1437                   tries = 1;
1438                 break;
1439               case 'd': case 'o':       /* Decimal or Octal number */
1440                 if (isdigit(*(p+2)))
1441                   tries = 1;
1442                 break;
1443               case 'x':                 /* Hex number */
1444                 if (isdigit(*(p+2)) ||
1445                     ((*(p+2) >= 'a' && *(p+2) <= 'f') ||
1446                      ((*(p+2) >= 'A' && *(p+2) <= 'F'))))
1447                   tries = 1;
1448               default:
1449                 break;
1450             }
1451             p++;
1452         }
1453 #else
1454 #ifndef NOSPL
1455         if (f) {                        /* If a conversion function is given */
1456             char *s = p;                /* See if there are any variables in */
1457             while (*s) {                /* the string and if so, expand them */
1458                 if (chkvar(s)) {
1459                     tries = 1;
1460                     break;
1461                 }
1462                 s++;
1463             }
1464         }
1465 #endif /* NOSPL */
1466 #endif /* COMMENT */
1467     }
1468 #ifdef OS2
1469 o_again:
1470 #endif /* OS2 */
1471     if (tries == 1)
1472 #endif /* DOCHKVAR */
1473     if (f) {                            /* If a conversion function is given */
1474         zq = atxbuf;                    /* do the conversion. */
1475         atxn = CMDBL;
1476         if ((x = (*f)(s,&zq,&atxn)) < 0)
1477           return(-2);
1478         s = atxbuf;
1479         if (!*s)                        /* Result empty, substitute default */
1480           s = xdef;
1481     }
1482     debug(F111,"cmofi 2",s,x);
1483
1484 #ifdef DTILDE
1485     dirp = tilde_expand(s);             /* Expand tilde, if any, */
1486     if (*dirp != '\0') {                /* right in the atom buffer. */
1487         if (setatm(dirp,1) < 0) {
1488             printf("?Name too long\n");
1489             return(-9);
1490         }
1491     }
1492     s = atmbuf;
1493     debug(F110,"cmofi 3",s,0);
1494 #endif /* DTILDE */
1495
1496     if (iswild(s)) {
1497         printf("?Wildcards not allowed - %s\n",s);
1498         return(-2);
1499     }
1500     debug(F110,"cmofi 4",s,0);
1501
1502 #ifdef CK_TMPDIR
1503     /* isdir() function required for this! */
1504     if (isdir(s)) {
1505         debug(F110,"cmofi 5: is directory",s,0);
1506         *xp = s;
1507         return(2);
1508     }
1509 #endif /* CK_TMPDIR */
1510
1511     if (strcmp(s,CTTNAM) && (zchko(s) < 0)) { /* OK to write to console */
1512 #ifdef COMMENT
1513 #ifdef OS2
1514 /*
1515   We don't try again because we already prescanned the string to see if
1516   if it contained anything that could be used by zzstring().
1517 */
1518         if (tries++ < 1)
1519           goto o_again;
1520 #endif /* OS2 */
1521 #endif /* COMMENT */
1522 /*
1523   Note: there are certain circumstances where zchko() can give a false
1524   positive, so don't rely on it to catch every conceivable situation in
1525   which the given output file can't be created.  In other words, we print
1526   a message and fail here if we KNOW the file can't be created.  If we
1527   succeed but the file can't be opened, the code that tries to open the file
1528   has to print a message.
1529 */
1530         debug(F110,"cmofi 6: failure",s,0);
1531 #ifdef CKROOT
1532         if (ckrooterr)
1533           printf("?Off Limits: %s\n",s);
1534         else
1535 #endif /* CKROOT */
1536           printf("?Write permission denied - %s\n",s);
1537 #ifdef CKCHANNELIO
1538         z_error = FX_ACC;
1539 #endif /* CKCHANNELIO */
1540         return(-9);
1541     } else {
1542         debug(F110,"cmofi 7: ok",s,0);
1543         *xp = s;
1544         return(x);
1545     }
1546 }
1547
1548 /*  C M I F I  --  Parse the name of an existing file  */
1549
1550 /*
1551  This function depends on the external functions:
1552    zchki()  - Check if input file exists and is readable.
1553    zxpand() - Expand a wild file specification into a list.
1554    znext()  - Return next file name from list.
1555  If these functions aren't available, then use cmfld() to parse filenames.
1556 */
1557 /*
1558  Returns
1559    -4 EOF
1560    -3 if no input present when required,
1561    -2 if file does not exist or is not readable,
1562    -1 if reparse needed,
1563     0 or 1 otherwise, with:
1564         xp pointing to name,
1565         wild = 1 if name contains '*' or '?', 0 otherwise.
1566 */
1567
1568 /*
1569    C M I O F I  --  Parse an input file OR the name of a nonexistent file.
1570
1571    Use this when an existing file is wanted (so we get help, completion, etc),
1572    but if a file of the given name does not exist, the name of a new file is
1573    accepted.  For example, with the EDIT command (edit an existing file, or
1574    create a new file).  Returns -9 if file does not exist.  It is up to the
1575    caller to check creatability.
1576 */
1577 static int nomsg = 0;
1578 int
1579 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1580     int msgsave, x;
1581     msgsave = nomsg;
1582     nomsg = 1;
1583     x = cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0);
1584     nomsg = msgsave;
1585     return(x);
1586 }
1587
1588 int
1589 cmifi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1590     return(cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0));
1591 }
1592 /*
1593   cmifip() is called when we want to supply a path or path list to search
1594   in case the filename that the user gives is (a) not absolute, and (b) can't
1595   be found as given.  The path string can be the name of a single directory,
1596   or a list of directories separated by the PATHSEP character, defined in
1597   ckucmd.h.  Look in ckuusr.c and ckuus3.c for examples of usage.
1598 */
1599 int
1600 cmifip(xhlp,xdef,xp,wild,d,path,f)
1601     char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; {
1602     return(cmifi2(xhlp,xdef,xp,wild,0,path,f,0));
1603 }
1604
1605 /*  C M D I R  --  Parse a directory name  */
1606
1607 /*
1608  This function depends on the external functions:
1609    isdir(s)  - Check if string s is the name of a directory
1610    zchki(s)  - Check if input file s exists and what type it is.
1611  If these functions aren't available, then use cmfld() to parse dir names.
1612
1613  Returns
1614    -9 For all sorts of reasons, after printing appropriate error message.
1615    -4 EOF
1616    -3 if no input present when required,
1617    -2 if out of space or other internal error,
1618    -1 if reparse needed,
1619     0 or 1, with xp pointing to name, if directory specified,
1620 */
1621 int
1622 cmdir(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1623     int wild;
1624     return(cmifi2(xhlp,xdef,xp,&wild,0,NULL,f,1));
1625 }
1626
1627 /* Like CMDIR but includes PATH search */
1628
1629 int
1630 cmdirp(xhlp,xdef,xp,path,f) char *xhlp, *xdef, **xp; char * path; xx_strp f; {
1631     int wild;
1632     return(cmifi2(xhlp,xdef,xp,&wild,0,path,f,1));
1633 }
1634
1635 /*
1636   cmifi2() is the base filename parser called by cmifi, cmifip, cmdir, etc.
1637   Use it directly when you also want to parse a directory or device
1638   name as an input file, as in the DIRECTORY command.  Call with:
1639     xhlp  -- help message on ?
1640     xdef  -- default response
1641     xp    -- pointer to result (in our space, must be copied from here)
1642     wild  -- flag set upon return to indicate if filespec was wild
1643     d     -- 0 to parse files, 1 to parse files or directories
1644              Add 2 to inhibit following of symlinks.
1645     path  -- search path for files
1646     f     -- pointer to string processing function (e.g. to evaluate variables)
1647     dirflg -- 1 to parse *only* directories, 0 otherwise
1648 */
1649 int
1650 cmifi2(xhlp,xdef,xp,wild,d,path,f,dirflg)
1651     char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; int dirflg; {
1652     extern int recursive, diractive, cdactive, dblquo;
1653     int i, x, itsadir, xc, expanded = 0, nfiles = 0, children = -1;
1654     int qflag = 0;
1655     long y;
1656     char *sp = NULL, *zq, *np = NULL;
1657     char *sv = NULL, *p = NULL;
1658 #ifdef DTILDE
1659     char *dirp;
1660 #endif /* DTILDE */
1661
1662 #ifndef NOPARTIAL
1663 #ifndef OS2
1664 #ifdef OSK
1665     /* This large array is dynamic for OS-9 -- should do for others too... */
1666     extern char **mtchs;
1667 #else
1668 #ifdef UNIX
1669     /* OK, for UNIX too */
1670     extern char **mtchs;
1671 #else
1672 #ifdef VMS
1673     extern char **mtchs;
1674 #else
1675     extern char *mtchs[];
1676 #endif /* VMS */
1677 #endif /* UNIX */
1678 #endif /* OSK */
1679 #endif /* OS2 */
1680 #endif /* NOPARTIAL */
1681
1682     if (!xhlp) xhlp = "";
1683     if (!xdef) xdef = "";
1684
1685     nzxopts = 0;                        /* zxpand() options */
1686     debug(F101,"cmifi d","",d);
1687     if (d & 2) {                        /* d & 2 means don't follow symlinks */
1688         d ^= 2;
1689         nzxopts = ZX_NOLINKS;
1690     }
1691     debug(F101,"cmifi nzxopts","",nzxopts);
1692     cmfldflgs = 0;
1693     if (path)
1694       if (!*path)
1695         path = NULL;
1696     if (path) {                         /* Make a copy we can poke */
1697         x = strlen(path);
1698         np = (char *) malloc(x + 1);
1699         if (np) {
1700             strcpy(np, path);
1701             path = sp = np;
1702         }
1703     }
1704     debug(F110,"cmifi2 path",path,0);
1705
1706     ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
1707     xdef = cmdefault;
1708
1709     inword = 0;                         /* Initialize counts & pointers */
1710     cc = 0;
1711     xc = 0;
1712     *xp = "";                           /* Pointer to result string */
1713     if ((x = cmflgs) != 1) {            /* Already confirmed? */
1714 #ifdef BS_DIRSEP
1715         dirnamflg = 1;
1716         x = gtword(0);                  /* No, get a word */
1717         dirnamflg = 0;
1718 #else
1719         x = gtword(0);                  /* No, get a word */
1720 #endif /* BS_DIRSEP */
1721     } else {                            /* If so, use default, if any. */
1722         if (setatm(xdef,1) < 0) {
1723             printf("?Default name too long\n");
1724             if (np) free(np);
1725             return(-9);
1726         }
1727     }
1728   i_path:
1729     *xp = atmbuf;                       /* Point to result. */
1730
1731     while (1) {
1732         xc += cc;                       /* Count this character. */
1733         debug(F111,"cmifi gtword",atmbuf,xc);
1734         debug(F101,"cmifi switch x","",x);
1735         switch (x) {                    /* x = gtword() return code */
1736           case -10:
1737             if (gtimer() > timelimit) {
1738 #ifdef IKSD
1739                 if (inserver) {
1740                     printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
1741                     doexit(GOOD_EXIT,0);
1742                 }
1743 #endif /* IKSD */
1744                 /* if (!quiet) printf("?Timed out\n"); */
1745                 return(-10);
1746             } else {
1747                 x = gtword(0);
1748                 continue;
1749             }
1750           case -9:
1751             printf("Command or field too long\n");
1752           case -4:                      /* EOF */
1753           case -2:                      /* Out of space. */
1754           case -1:                      /* Reparse needed */
1755             if (np) free(np);
1756             return(x);
1757           case 1:                       /* CR */
1758           case 0:                       /* SP */
1759             if (xc == 0)                /* If no input... */
1760               *xp = xdef;               /* substitute the default */
1761             *xp = brstrip(*xp);         /* Strip braces */
1762             if (**xp == NUL) {          /* 12 mar 2001 */
1763                 if (np) free(np);
1764                 return(-3);
1765             }
1766             debug(F110,"cmifi brstrip",*xp,0);
1767 #ifndef NOSPL
1768             if (f) {                    /* If a conversion function is given */
1769 #ifdef DOCHKVAR
1770                 char *s = *xp;          /* See if there are any variables in */
1771                 int x;
1772                 while (*s) {            /* the string and if so, expand them */
1773                     x = chkvar(s);
1774                     /* debug(F111,"cmifi chkvar",*xp,x); */
1775                     if (x) {
1776 #endif /* DOCHKVAR */
1777                         zq = atxbuf;
1778                         atxn = CMDBL;
1779                         if ((*f)(*xp,&zq,&atxn) < 0) {
1780                             if (np) free(np);
1781                             return(-2);
1782                         }
1783                         *xp = atxbuf;
1784                         if (!atxbuf[0])
1785                           *xp = xdef;
1786 #ifdef DOCHKVAR
1787                         break;
1788                     }
1789                     s++;
1790                 }
1791 #endif /* DOCHKVAR */
1792             }
1793 #endif /* NOSPL */
1794             if (**xp == NUL) {          /* 12 mar 2001 */
1795                 if (np) free(np);
1796                 return(-3);
1797             }
1798 #ifdef DTILDE
1799             if (dirflg) {
1800                 dirp = tilde_expand(*xp); /* Expand tilde, if any, */
1801                 if (*dirp != '\0') {    /* in the atom buffer. */
1802                     if (setatm(dirp,1) < 0) {
1803                         printf("Expanded name too long\n");
1804                         if (np) free(np);
1805                         return(-9);
1806                     }
1807                 }
1808                 *xp = atmbuf;
1809                 debug(F110,"cmifi tilde_expand",*xp,0);
1810             }
1811 #endif /* DTILDE */
1812             if (!sv) {                  /* Only do this once */
1813                 sv = malloc((int)strlen(*xp)+1); /* Make a safe copy */
1814                 if (!sv) {
1815                     printf("?cmifi: malloc error\n");
1816                     if (np) free(np);
1817                     return(-9);
1818                 }
1819                 strcpy(sv,*xp);
1820                 debug(F110,"cmifi sv",sv,0);
1821             }
1822
1823 /* This is to get around "cd /" failing because "too many directories match" */
1824
1825             expanded = 0;               /* Didn't call zxpand */
1826 #ifdef datageneral
1827             debug(F110,"cmifi isdir 1",*xp,0);
1828             {
1829                 int y; char *s;
1830                 s = *xp;
1831                 y = strlen(s);
1832                 if (y > 1 &&
1833                     (s[y-1] == ':' ||
1834                      s[y-1] == '^' ||
1835                      s[y-1] == '=')
1836                     )
1837                   s[y-1] = NUL;
1838             }
1839             debug(F110,"cmifi isdir 2",*xp,0);
1840 #endif /*  datageneral */
1841
1842 #ifdef VMS
1843             if (dirflg) {
1844                 if (!strcmp(*xp,"..")) { /* For UNIXers... */
1845                     setatm("-",0);
1846                     *xp = atmbuf;
1847                 } else if (!strcmp(*xp,".")) {
1848                     setatm("[]",0);
1849                     *xp = atmbuf;
1850                 }
1851             }
1852 #endif /* VMS */
1853             itsadir = isdir(*xp);       /* Is it a directory? */
1854             debug(F111,"cmifi itsadir",*xp,itsadir);
1855 #ifdef VMS
1856             /* If they said "blah" where "blah.dir" is a directory... */
1857             /* change it to [.blah]. */
1858             if (!itsadir) {
1859                 char tmpbuf[600];
1860                 int flag = 0; char c, * p;
1861                 p = *xp;
1862                 while ((c = *p++) && !flag)
1863                   if (ckstrchr(".[]:*?<>",c))
1864                     flag = 1;
1865                 debug(F111,"cmifi VMS dirname flag",*xp,flag);
1866                 if (!flag) {
1867                     ckmakmsg(tmpbuf,TMPBUFSIZ,"[.",*xp,"]",NULL);
1868                     itsadir = isdir(tmpbuf);
1869                     if (itsadir) {
1870                         setatm(tmpbuf,0);
1871                         *xp = atmbuf;
1872                     }
1873                     debug(F111,"cmifi VMS dirname flag itsadir",*xp,itsadir);
1874                 }
1875             } else if (itsadir == 1 && *(xp[0]) == '.' && *(xp[1])) {
1876                 char *p;
1877                 if (p = malloc(cc + 4)) {
1878                     ckmakmsg(p,cc+4,"[",*xp,"]",NULL);
1879                     setatm(p,0);
1880                     *xp = atmbuf;
1881                     debug(F110,"cmdir .foo",*xp,0);
1882                     free(p);
1883                 }
1884             } else if (itsadir == 2 && !diractive) {
1885                 int x;                  /* [FOO]BAR.DIR instead of [FOO.BAR] */
1886                 char *p;
1887                 p = malloc(cc + 4);
1888                 if (p) {
1889                     x = cvtdir(*xp,p,ATMBL); /* Convert to [FOO.BAR] */
1890                     if (x > 0) {
1891                         setatm(p,0);
1892                         *xp = atmbuf;
1893                         debug(F110,"cmdir cvtdir",*xp,0);
1894                     }
1895                     free(p);
1896                 }
1897             }
1898 #endif /* VMS */
1899
1900             debug(F101,"cmifi dirflg","",dirflg);
1901             if (dirflg) {               /* Parsing a directory name? */
1902                 /* Yes, does it contain wildcards? */
1903                 if (iswild(*xp) ||
1904                     (diractive && (!strcmp(*xp,".")  || !strcmp(*xp,"..")))
1905                     ) {
1906                     nzxopts |= ZX_DIRONLY; /* Match only directory names */
1907                     if (matchdot)  nzxopts |= ZX_MATCHDOT;
1908                     if (recursive) nzxopts |= ZX_RECURSE;
1909                     debug(F111,"cmifi nzxopts 2",*xp,nzxopts);
1910                     y = nzxpand(*xp,nzxopts);
1911                     debug(F111,"cmifi nzxpand 2",*xp,y);
1912                     nfiles = y;
1913                     expanded = 1;
1914                 } else {
1915 #ifdef VMS
1916 /*
1917   This is to allow (e.g.) "cd foo", where FOO.DIR;1 is in the
1918   current directory.
1919 */
1920                     debug(F111,"cmdir itsadir",*xp,itsadir);
1921                     if (!itsadir) {
1922                         char *s;
1923                         int n;
1924                         s = *xp;
1925                         n = strlen(s);
1926                         if (n > 0 &&
1927 #ifdef COMMENT
1928                             *s != '[' && s[n-1] != ']' &&
1929                             *s != '<' && s[n-1] != '>' &&
1930 #else
1931                             ckindex("[",s,0,0,1) == 0 &&
1932                             ckindex("<",s,0,0,1) == 0 &&
1933 #endif /* COMMENT */
1934                             s[n-1] != ':') {
1935                             char * dirbuf = NULL;
1936                             dirbuf = (char *)malloc(n+4);
1937                             if (dirbuf) {
1938                                 if (*s == '.')
1939                                   ckmakmsg(dirbuf,n+4,"[",s,"]",NULL);
1940                                 else
1941                                   ckmakmsg(dirbuf,n+4,"[.",s,"]",NULL);
1942                                 itsadir = isdir(dirbuf);
1943                                 debug(F111,"cmdir dirbuf",dirbuf,itsadir);
1944                                 if (itsadir) {
1945                                     setatm(dirbuf,0);
1946                                     *xp = atmbuf;
1947                                     debug(F110,"cmdir new *xp",*xp,0);
1948                                 }
1949                                 free(dirbuf);
1950                             }
1951
1952 /* This is to allow CDPATH to work in VMS... */
1953
1954                         } else if (n > 0) {
1955                             char * p; int i, j, k, d;
1956                             char rb[2] = "]";
1957                             if (p = malloc(x + 8)) {
1958                                 ckstrncpy(p,*xp,x+8);
1959                                 i = ckindex(".",p,-1,1,1);
1960                                 d = ckindex(".dir",p,0,0,0);
1961                                 j = ckindex("]",p,-1,1,1);
1962                                 if (j == 0) {
1963                                     j = ckindex(">",p,-1,1,1);
1964                                     rb[0] = '>';
1965                                 }
1966                                 k = ckindex(":",p,-1,1,1);
1967                                 if (i < j || i < k) i = 0;
1968                                 if (d < j || d < k) d = 0;
1969                                 /* Change [FOO]BAR or [FOO]BAR.DIR */
1970                                 /* to [FOO.BAR] */
1971                                 if (j > 0 && j < n) {
1972                                     p[j-1] = '.';
1973                                     if (d > 0) p[d-1] = NUL;
1974                                     ckstrncat(p,rb,x+8);
1975                                     debug(F110,"cmdir xxx",p,0);
1976                                 }
1977                                 itsadir = isdir(p);
1978                                 debug(F111,"cmdir p",p,itsadir);
1979                                 if (itsadir) {
1980                                     setatm(p,0);
1981                                     *xp = atmbuf;
1982                                     debug(F110,"cmdir new *xp",*xp,0);
1983                                 }
1984                                 free(p);
1985                             }
1986                         }
1987                     }
1988 #endif /* VMS */
1989                     y = (!itsadir) ? 0 : 1;
1990                     debug(F111,"cmifi y itsadir",*xp,y);
1991                 }
1992             } else {                    /* Parsing a filename. */
1993                 debug(F110,"cmifi *xp pre-zxpand",*xp,0);
1994 #ifndef COMMENT
1995                 nzxopts |= (d == 0) ? ZX_FILONLY : 0; /* So always expand. */
1996                 if (matchdot)  nzxopts |= ZX_MATCHDOT;
1997                 if (recursive) nzxopts |= ZX_RECURSE;
1998                 y = nzxpand(*xp,nzxopts);
1999 #else
2000 /* Here we're trying to fix a problem in which a directory name is accepted */
2001 /* as a filename, but this breaks too many other things. */
2002                 /* nzxopts = 0; */
2003                 if (!d) {
2004                     if (itsadir & !iswild(*xp)) {
2005                         debug(F100,"cmifi dir when filonly","",0);
2006                         printf("?Not a regular file: \"%s\"\n",*xp);
2007                         if (sv) free(sv);
2008                         if (np) free(np);
2009                         return(-9);
2010                     } else {
2011                         nzxopts |= ZX_FILONLY;
2012                         if (matchdot)  nzxopts |= ZX_MATCHDOT;
2013                         if (recursive) nzxopts |= ZX_RECURSE;
2014                         y = nzxpand(*xp,nzxopts);
2015                     }
2016                 }
2017 #endif /* COMMENT */
2018                 nfiles = y;
2019                 debug(F111,"cmifi y nzxpand",*xp,y);
2020                 debug(F111,"cmifi y atmbuf",atmbuf,itsadir);
2021                 expanded = 1;
2022             }
2023             /* domydir() calls zxrewind() so we MUST call nzxpand() here */
2024             if (!expanded && diractive) {
2025                 debug(F110,"cmifi diractive catch-all zxpand",*xp,0);
2026                 nzxopts |= (d == 0) ? ZX_FILONLY : (dirflg ? ZX_DIRONLY : 0);
2027                 if (matchdot)  nzxopts |= ZX_MATCHDOT;
2028                 if (recursive) nzxopts |= ZX_RECURSE;
2029                 y = nzxpand(*xp,nzxopts);
2030                 debug(F111,"cmifi diractive nzxpand",*xp,y);
2031                 nfiles = y;
2032                 expanded = 1;
2033             }
2034             *wild = (iswild(sv) || (y > 1)) && (itsadir == 0);
2035
2036 #ifdef RECURSIVE
2037             if (!*wild) *wild = recursive;
2038 #endif /* RECURSIVE */
2039
2040             debug(F111,"cmifi sv wild",sv,*wild);
2041             debug(F101,"cmifi y","",y);
2042             if (dirflg && *wild && cdactive) {
2043                 if (y > 1) {
2044                     printf("?Wildcard matches more than one directory\n");
2045                     if (sv) free(sv);
2046                     if (np) free(np);
2047                     return(-9);
2048                 } else {
2049                     znext(*xp);
2050                 }
2051             }
2052             if (itsadir && d && !dirflg) { /* It's a directory and not wild */
2053                 if (sv) free(sv);       /* and it's ok to parse directories */
2054                 if (np) free(np);
2055                 return(x);
2056             }
2057             if (y == 0) {               /* File was not found */
2058                 int dosearch = 0;
2059                 dosearch = (path != NULL); /* A search path was given */
2060                 if (dosearch) {
2061                     dosearch = hasnopath(sv); /* Filename includes no path */
2062                     debug(F111,"cmifip hasnopath",sv,dosearch);
2063                 }
2064                 if (dosearch) {         /* Search the path... */
2065                     char * ptr = path;
2066                     char c;
2067                     while (1) {
2068                         c = *ptr;
2069                         if (c == PATHSEP || c == NUL) {
2070                             if (!*path) {
2071                                 path = NULL;
2072                                 break;
2073                             }
2074                             *ptr = NUL;
2075 #ifdef UNIX
2076 /* By definition of CDPATH, an empty member denotes the current directory */
2077                             if (!*path)
2078                               ckstrncpy(atmbuf,".",ATMBL);
2079                             else
2080 #endif /* UNIX */
2081                               ckstrncpy(atmbuf,path,ATMBL);
2082 #ifdef VMS
2083                             atmbuf[ATMBL] = NUL;
2084 /* If we have a logical name, evaluate it recursively */
2085                             if (*(ptr-1) == ':') { /* Logical name ends in : */
2086                                 char *p; int n;
2087                                 while (((n = strlen(atmbuf))  > 0) &&
2088                                        atmbuf[n-1] == ':') {
2089                                     atmbuf[n-1] = NUL;
2090                                     for (p = atmbuf; *p; p++)
2091                                       if (islower(*p)) *p = toupper(*p);
2092                                     debug(F111,"cmdir CDPATH LN 1",atmbuf,n);
2093                                     p = getenv(atmbuf);
2094                                     debug(F110,"cmdir CDPATH LN 2",p,0);
2095                                     if (!p)
2096                                       break;
2097                                     strncpy(atmbuf,p,ATMBL);
2098                                     atmbuf[ATMBL] = NUL;
2099                                 }
2100                             }
2101 #else
2102 #ifdef OS2
2103                             if (*(ptr-1) != '\\' && *(ptr-1) != '/')
2104                               ckstrncat(atmbuf,"\\",ATMBL);
2105 #else
2106 #ifdef UNIX
2107                             if (*(ptr-1) != '/')
2108                               ckstrncat(atmbuf,"/",ATMBL);
2109 #else
2110 #ifdef datageneral
2111                             if (*(ptr-1) != ':')
2112                               ckstrncat(atmbuf,":",ATMBL);
2113 #endif /* datageneral */
2114 #endif /* UNIX */
2115 #endif /* OS2 */
2116 #endif /* VMS */
2117                             ckstrncat(atmbuf,sv,ATMBL);
2118                             debug(F110,"cmifip add path",atmbuf,0);
2119                             if (c == PATHSEP) ptr++;
2120                             path = ptr;
2121                             break;
2122                         }
2123                         ptr++;
2124                     }
2125                     x = 1;
2126                     inword = 0;
2127                     cc = 0;
2128                     xc = (int) strlen(atmbuf);
2129                     *xp = "";
2130                     goto i_path;
2131                 }
2132                 if (d) {
2133                     if (sv) free(sv);
2134                     if (np) free(np);
2135                     return(-2);
2136                 } else {
2137                     if (!nomsg) {
2138 #ifdef CKROOT
2139                         if (ckrooterr)
2140                           printf("?Off Limits: %s\n",sv);
2141                         else
2142 #endif /* CKROOT */
2143                           printf("?No %s match - %s\n",
2144                                  dirflg ? "directories" : "files", sv);
2145                     }
2146                     if (sv) free(sv);
2147                     if (np) free(np);
2148                     return(-9);
2149                 }
2150             } else if (y < 0) {
2151 #ifdef CKROOT
2152                 if (ckrooterr)
2153                   printf("?Off Limits: %s\n",sv);
2154                 else
2155 #endif /* CKROOT */
2156                   printf("?Too many %s match - %s\n",
2157                          dirflg ? "directories" : "files", sv);
2158                 if (sv) free(sv);
2159                 if (np) free(np);
2160                 return(-9);
2161             } else if (*wild || y > 1) {
2162                 if (sv) free(sv);
2163                 if (np) free(np);
2164                 return(x);
2165             }
2166
2167             /* If not wild, see if it exists and is readable. */
2168
2169             debug(F111,"cmifi sv not wild",sv,*wild);
2170             if (expanded)
2171               znext(*xp);               /* Get first (only?) matching file */
2172             if (dirflg)                 /* Maybe wild and expanded */
2173               itsadir = isdir(*xp);     /* so do this again. */
2174             y = dirflg ? itsadir : zchki(*xp); /* Now check accessibility */
2175             if (expanded) {
2176 #ifdef ZXREWIND
2177                 nfiles = zxrewind();    /* Rewind so next znext() gets 1st */
2178 #else
2179
2180                 nzxopts |= dirflg ? ZX_DIRONLY : 0;
2181                 if (matchdot)  nzxopts |= ZX_MATCHDOT;
2182                 if (recursive) nzxopts |= ZX_RECURSE;
2183                 nfiles = nzxpand(*xp,nzxopts);
2184 #endif /* ZXREWIND */
2185             }
2186             debug(F111,"cmifi nfiles",*xp,nfiles);
2187             free(sv);                   /* done with this */
2188             sv = NULL;
2189             if (dirflg && y == 0) {
2190                 printf("?Not a directory - %s\n",*xp);
2191 #ifdef CKCHANNELIO
2192                 z_error = FX_ACC;
2193 #endif /* CKCHANNELIO */
2194                 return(-9);
2195             } else if (y == -3) {
2196                 if (!xcmfdb) {
2197                     if (diractive)
2198                       /* Don't show filename if we're not allowed to see it */
2199                       printf("?Read permission denied\n");
2200                     else
2201                       printf("?Read permission denied - %s\n",*xp);
2202                 }
2203                 if (np) free(np);
2204 #ifdef CKCHANNELIO
2205                 z_error = FX_ACC;
2206 #endif /* CKCHANNELIO */
2207                 return(xcmfdb ? -6 : -9);
2208             } else if (y == -2) {
2209                 if (!recursive) {
2210                     if (np) free(np);
2211                     if (d) return(0);
2212                     if (!xcmfdb)
2213                       printf("?File not readable - %s\n",*xp);
2214 #ifdef CKCHANNELIO
2215                     z_error = FX_ACC;
2216 #endif /* CKCHANNELIO */
2217                     return(xcmfdb ? -6 : -9);
2218                 }
2219             } else if (y < 0) {
2220                 if (np) free(np);
2221                 if (!nomsg && !xcmfdb)
2222                   printf("?File not found - %s\n",*xp);
2223 #ifdef CKCHANNELIO
2224                 z_error = FX_FNF;
2225 #endif /* CKCHANNELIO */
2226                 return(xcmfdb ? -6 : -9);
2227             }
2228             if (np) free(np);
2229             return(x);
2230
2231 #ifndef MAC
2232           case 2:                       /* ESC */
2233             debug(F101,"cmifi esc, xc","",xc);
2234             if (xc == 0) {
2235                 if (*xdef) {
2236                     printf("%s ",xdef); /* If at beginning of field */
2237 #ifdef GEMDOS
2238                     fflush(stdout);
2239 #endif /* GEMDOS */
2240                     inword = cmflgs = 0;
2241                     addbuf(xdef);       /* Supply default. */
2242                     if (setatm(xdef,0) < 0) {
2243                         printf("Default name too long\n");
2244                         if (np) free(np);
2245                         return(-9);
2246                     }
2247                 } else {                /* No default */
2248                     bleep(BP_WARN);
2249                 }
2250                 break;
2251             }
2252             if (**xp == '{') {          /* Did user type opening brace... */
2253                 *xp = *xp + 1;
2254                 xc--;
2255                 cc--;
2256                 qflag = '}';
2257             } else if (dblquo && **xp == '"') { /* or doublequote? */
2258                 *xp = *xp + 1;          /* If so ignore it and space past it */
2259                 xc--;
2260                 cc--;
2261                 qflag = '"';
2262             }
2263 #ifndef NOSPL
2264             if (f) {                    /* If a conversion function is given */
2265 #ifdef DOCHKVAR
2266                 char *s = *xp;          /* See if there are any variables in */
2267                 while (*s) {            /* the string and if so, expand it.  */
2268                     if (chkvar(s)) {
2269 #endif /* DOCHKVAR */
2270                         zq = atxbuf;
2271                         atxn = CMDBL;
2272                         if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2273                             if (np) free(np);
2274                             return(-2);
2275                         }
2276 #ifdef DOCHKVAR
2277                     /* reduce cc by number of \\ consumed by conversion */
2278                     /* function (needed for OS/2, where \ is path separator) */
2279                         cc -= (strlen(*xp) - strlen(atxbuf));
2280 #endif /* DOCHKVAR */
2281                         *xp = atxbuf;
2282                         if (!atxbuf[0]) { /* Result empty, use default */
2283                             *xp = xdef;
2284                             cc = strlen(xdef);
2285                         }
2286 #ifdef DOCHKVAR
2287                         break;
2288                     }
2289                     s++;
2290                 }
2291 #endif /* DOCHKVAR */
2292             }
2293 #endif /* NOSPL */
2294
2295 #ifdef DTILDE
2296             if (dirflg && *(*xp) == '~') {
2297                 debug(F111,"cmifi tilde_expand A",*xp,cc);
2298                 dirp = tilde_expand(*xp); /* Expand tilde, if any... */
2299                 if (!dirp) dirp = "";
2300                 if (*dirp) {
2301                     int i, xx;
2302                     char * sp;
2303                     xc = cc;            /* Length of ~thing */
2304                     xx = setatm(dirp,0); /* Copy expansion to atom buffer */
2305                     debug(F111,"cmifi tilde_expand B",atmbuf,cc);
2306                     if (xx < 0) {
2307                         printf("Expanded name too long\n");
2308                         if (np) free(np);
2309                         return(-9);
2310                     }
2311                     debug(F111,"cmifi tilde_expand xc","",xc);
2312                     for (i = 0; i < xc; i++) {
2313                         cmdchardel();   /* Back up over ~thing */
2314                         bp--;
2315                     }
2316                     xc = cc;            /* How many new ones we just got */
2317                     sp = atmbuf;
2318                     printf("%s",sp);    /* Print them */
2319                     while ((*bp++ = *sp++)) ;   /* Copy to command buffer */
2320                     bp--;                       /* Back up over NUL */
2321                 }
2322                 *xp = atmbuf;
2323             }
2324 #endif /* DTILDE */
2325
2326             sp = *xp + cc;
2327
2328 #ifdef UNIXOROSK
2329             if (!strcmp(atmbuf,"..")) {
2330                 printf(" ");
2331                 ckstrncat(cmdbuf," ",CMDBL);
2332                 cc++;
2333                 bp++;
2334                 *wild = 0;
2335                 *xp = atmbuf;
2336                 break;
2337             } else if (!strcmp(atmbuf,".")) {
2338                 bleep(BP_WARN);
2339                 if (np) free(np);
2340                 return(-1);
2341             } else {
2342                 /* This patches a glitch when user types "./foo<ESC>" */
2343                 /* in which the next two chars are omitted from the */
2344                 /* expansion.  There should be a better fix, however, */
2345                 /* since there is no problem with "../foo<ESC>". */
2346                 char *p = *xp;
2347                 if (*p == '.' && *(p+1) == '/')
2348                   cc -= 2;
2349             }
2350 #endif /* UNIXOROSK */
2351
2352 #ifdef datageneral
2353             *sp++ = '+';                /* Data General AOS wildcard */
2354 #else
2355             *sp++ = '*';                /* Others */
2356 #endif /* datageneral */
2357             *sp-- = '\0';
2358 #ifdef GEMDOS
2359             if (!strchr(*xp, '.'))      /* abde.e -> abcde.e* */
2360               strcat(*xp, ".*");        /* abc -> abc*.* */
2361 #endif /* GEMDOS */
2362             /* Add wildcard and expand list. */
2363 #ifdef COMMENT
2364             /* This kills partial completion when ESC given in path segment */
2365             nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2366 #else
2367             /* nzxopts = 0; */
2368 #endif /* COMMENT */
2369             if (matchdot)  nzxopts |= ZX_MATCHDOT;
2370             if (recursive) nzxopts |= ZX_RECURSE;
2371             y = nzxpand(*xp,nzxopts);
2372             nfiles = y;
2373             debug(F111,"cmifi nzxpand",*xp,y);
2374             if (y > 0) {
2375 #ifdef OS2
2376                 znext(filbuf);          /* Get first */
2377 #ifdef ZXREWIND
2378                 zxrewind();             /* Must "rewind" */
2379 #else
2380                 nzxpand(*xp,nxzopts);
2381 #endif /* ZXREWIND */
2382 #else  /* Not OS2 */
2383                 ckstrncpy(filbuf,mtchs[0],CKMAXPATH);
2384 #endif /* OS2 */
2385             } else
2386               *filbuf = '\0';
2387             filbuf[CKMAXPATH] = NUL;
2388             *sp = '\0';                 /* Remove wildcard. */
2389             debug(F111,"cmifi filbuf",filbuf,y);
2390             debug(F111,"cmifi *xp",*xp,cc);
2391
2392             *wild = (y > 1);
2393             if (y == 0) {
2394                 if (!nomsg) {
2395 #ifdef CKROOT
2396                     if (ckrooterr)
2397                       printf("?Off Limits: %s\n",atmbuf);
2398                     else
2399 #endif /* CKROOT */
2400                       printf("?No %s match - %s\n",
2401                            dirflg ? "directories" : "files", atmbuf);
2402                     if (np) free(np);
2403                     return(-9);
2404                 } else {
2405                     bleep(BP_WARN);
2406                     if (np) free(np);
2407                     return(-1);
2408                 }
2409             } else if (y < 0) {
2410 #ifdef CKROOT
2411                 if (ckrooterr)
2412                   printf("?Off Limits: %s\n",atmbuf);
2413                 else
2414 #endif /* CKROOT */
2415                   printf("?Too many %s match - %s\n",
2416                          dirflg ? "directories" : "files", atmbuf);
2417                 if (np) free(np);
2418                 return(-9);
2419             } else if (y > 1            /* Not unique */
2420 #ifndef VMS
2421                        || (y == 1 && isdir(filbuf)) /* Unique directory */
2422 #endif /* VMS */
2423                        ) {
2424 #ifndef NOPARTIAL
2425 /* Partial filename completion */
2426                 int j, k; char c;
2427                 k = 0;
2428                 debug(F111,"cmifi partial",filbuf,cc);
2429 #ifdef OS2
2430                 {
2431                     int cur = 0,
2432                     len = 0,
2433                     len2 = 0,
2434                     min = strlen(filbuf),
2435                     found = 0;
2436                     char localfn[CKMAXPATH+1];
2437
2438                     len = min;
2439                     for (j = 1; j <= y; j++) {
2440                         znext(localfn);
2441                         if (dirflg && !isdir(localfn))
2442                           continue;
2443                         found = 1;
2444                         len2 = strlen(localfn);
2445                         for (cur = cc;
2446                              cur < len && cur < len2 && cur <= min;
2447                              cur++
2448                              ) {
2449                             /* OS/2 or Windows, case doesn't matter */
2450                             if (tolower(filbuf[cur]) != tolower(localfn[cur]))
2451                               break;
2452                         }
2453                         if (cur < min)
2454                           min = cur;
2455                     }
2456                     if (!found)
2457                       min = cc;
2458                     filbuf[min] = NUL;
2459                     if (min > cc)
2460                       k++;
2461                 }
2462 #else /* OS2 */
2463                 for (i = cc; (c = filbuf[i]); i++) {
2464                     for (j = 1; j < y; j++)
2465                       if (mtchs[j][i] != c) break;
2466                     if (j == y) k++;
2467                     else filbuf[i] = filbuf[i+1] = NUL;
2468                 }
2469 #endif /* OS2 */
2470
2471
2472 #ifndef VMS
2473                 /* isdir() function required for this! */
2474                 if (y == 1 && isdir(filbuf)) { /* Dont we already know this? */
2475                     int len;
2476                     len = strlen(filbuf);
2477                     if (len > 0 && len < ATMBL - 1) {
2478                         if (filbuf[len-1] != dirsep) {
2479                             filbuf[len] = dirsep;
2480                             filbuf[len+1] = NUL;
2481                         }
2482                     }
2483 /*
2484   At this point, before just doing partial completion, we should look first to
2485   see if the given directory does indeed have any subdirectories (dirflg) or
2486   files (!dirflg); if it doesn't we should do full completion.  Otherwise, the
2487   result looks funny to the user and "?" blows up the command for no good
2488   reason.
2489 */
2490                     {
2491                         int flags = 0;
2492                         filbuf[len+1] = '*';
2493                         filbuf[len+2] = NUL;
2494                         if (dirflg) flags = ZX_DIRONLY;
2495                         children = nzxpand(filbuf,flags);
2496                         debug(F111,"cmifi children",filbuf,children);
2497                         filbuf[len+1] = NUL;
2498                         nzxpand(filbuf,flags); /* Restore previous list */
2499                         if (children == 0)
2500                           goto NOSUBDIRS;
2501                     }
2502                     if (len + 1 > cc)
2503                       k++;
2504                 }
2505                 /* Add doublequotes if there are spaces in the name */
2506                 {
2507                     int x;
2508                     if (qflag) {
2509                         x = (qflag == '}'); /* (or braces) */
2510                     } else {
2511                         x = !dblquo;
2512                     }
2513                     if (filbuf[0] != '"' && filbuf[0] != '{')
2514                       k = dquote(filbuf,ATMBL,x);
2515                 }
2516 #endif /* VMS */
2517                 debug(F111,"cmifi REPAINT filbuf",filbuf,k);
2518                 if (k > 0) {            /* Got more characters */
2519                     debug(F101,"cmifi REPAINT cc","",cc);
2520                     debug(F101,"cmifi REPAINT xc","",xc);
2521                     debug(F110,"cmifi REPAINT bp-cc",bp-cc,0);
2522                     debug(F110,"cmifi REPAINT bp-xc",bp-xc,0);
2523                     sp = filbuf + cc;   /* Point to new ones */
2524                     if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2525                         int x;
2526                         x = cc;
2527                         if (qflag) x++;
2528                         for (i = 0; i < x; i++) {
2529                             cmdchardel(); /* Back up over old partial spec */
2530                             bp--;
2531                         }
2532                         sp = filbuf;    /* Point to new word start */
2533                         debug(F110,"cmifi erase ok",sp,0);
2534                     }
2535                     cc = k;             /* How many new ones we just got */
2536                     printf("%s",sp);    /* Print them */
2537                     while ((*bp++ = *sp++)) ;   /* Copy to command buffer */
2538                     bp--;                       /* Back up over NUL */
2539                     debug(F110,"cmifi partial cmdbuf",cmdbuf,0);
2540                     if (setatm(filbuf,0) < 0) {
2541                         printf("?Partial name too long\n");
2542                         if (np) free(np);
2543                         return(-9);
2544                     }
2545                     debug(F111,"cmifi partial atmbuf",atmbuf,cc);
2546                     *xp = atmbuf;
2547                 }
2548 #endif /* NOPARTIAL */
2549                 bleep(BP_WARN);
2550             } else {                    /* Unique, complete it.  */
2551 #ifndef VMS
2552 #ifdef CK_TMPDIR
2553                 /* isdir() function required for this! */
2554               NOSUBDIRS:
2555                 debug(F111,"cmifi unique",filbuf,children);
2556                 if (isdir(filbuf) && children > 0) {
2557                     int len;
2558                     len = strlen(filbuf);
2559                     if (len > 0 && len < ATMBL - 1) {
2560                         if (filbuf[len-1] != dirsep) {
2561                             filbuf[len] = dirsep;
2562                             filbuf[len+1] = NUL;
2563                         }
2564                     }
2565                     sp = filbuf + cc;
2566                     bleep(BP_WARN);
2567                     printf("%s",sp);
2568                     cc++;
2569                     while ((*bp++ = *sp++)) ;
2570                     bp--;
2571                     if (setatm(filbuf,0) < 0) {
2572                         printf("?Directory name too long\n");
2573                         if (np) free(np);
2574                         return(-9);
2575                     }
2576                     debug(F111,"cmifi directory atmbuf",atmbuf,cc);
2577                     *xp = atmbuf;
2578                 } else {                /* Not a directory or dirflg */
2579 #endif /* CK_TMPDIR */
2580 #endif /* VMS */
2581 #ifndef VMS                             /* VMS dir names are special */
2582 #ifndef datageneral                     /* VS dirnames must not end in ":" */
2583                     if (dirflg) {
2584                         int len;
2585                         len = strlen(filbuf);
2586                         if (len > 0 && len < ATMBL - 1) {
2587                             if (filbuf[len-1] != dirsep) {
2588                                 filbuf[len] = dirsep;
2589                                 filbuf[len+1] = NUL;
2590                             }
2591                         }
2592                     }
2593 #endif /* datageneral */
2594 #endif /* VMS */
2595                     sp = filbuf + cc;   /* Point past what user typed. */
2596                     {
2597                         int x;
2598                         if (qflag) {
2599                             x = (qflag == '}');
2600                         } else {
2601                             x = !dblquo;
2602                         }
2603                         if (filbuf[0] != '"' && filbuf[0] != '{')
2604                           dquote(filbuf,ATMBL,x);
2605                     }
2606                     if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2607                         int x;
2608                         x = cc;
2609                         if (qflag) x++;
2610                         for (i = 0; i < x; i++) {
2611                             cmdchardel(); /* Back up over old partial spec */
2612                             bp--;
2613                         }
2614                         sp = filbuf;    /* Point to new word start */
2615                         debug(F111,"cmifi after erase sp=",sp,cc);
2616                     }
2617                     printf("%s ",sp);   /* Print the completed name. */
2618 #ifdef GEMDOS
2619                     fflush(stdout);
2620 #endif /* GEMDOS */
2621                     addbuf(sp);         /* Add the characters to cmdbuf. */
2622                     if (setatm(filbuf,0) < 0) { /* And to atmbuf. */
2623                         printf("?Completed name too long\n");
2624                         if (np) free(np);
2625                         return(-9);
2626                     }
2627                     inword = cmflgs = 0;
2628                     *xp = brstrip(atmbuf); /* Return pointer to atmbuf. */
2629                     if (dirflg && !isdir(*xp)) {
2630                         printf("?Not a directory - %s\n", filbuf);
2631                         if (np) free(np);
2632                         return(-9);
2633                     }
2634                     if (np) free(np);
2635                     return(0);
2636 #ifndef VMS
2637 #ifdef CK_TMPDIR
2638                 }
2639 #endif /* CK_TMPDIR */
2640 #endif /* VMS */
2641             }
2642             break;
2643
2644           case 3:                       /* Question mark - file menu wanted */
2645             if (*xhlp == NUL)
2646               printf(dirflg ? " Directory name" : " Input file specification");
2647             else
2648               printf(" %s",xhlp);
2649 #ifdef GEMDOS
2650             fflush(stdout);
2651 #endif /* GEMDOS */
2652             /* If user typed an opening quote or brace, just skip past it */
2653
2654             if (**xp == '"' || **xp == '{') {
2655                 *xp = *xp + 1;
2656                 xc--;
2657                 cc--;
2658             }
2659 #ifndef NOSPL
2660             if (f) {                    /* If a conversion function is given */
2661 #ifdef DOCHKVAR
2662                 char *s = *xp;          /* See if there are any variables in */
2663                 while (*s) {            /* the string and if so, expand them */
2664                     if (chkvar(s)) {
2665 #endif /* DOCHKVAR */
2666                         zq = atxbuf;
2667                         atxn = CMDBL;
2668                         if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2669                             if (np) free(np);
2670                             return(-2);
2671                         }
2672 #ifdef DOCHKVAR
2673                     /* reduce cc by number of \\ consumed by conversion */
2674                     /* function (needed for OS/2, where \ is path separator) */
2675                         cc -= (strlen(*xp) - strlen(atxbuf));
2676 #endif /* DOCHKVAR */
2677                         *xp = atxbuf;
2678 #ifdef DOCHKVAR
2679                         break;
2680                     }
2681                     s++;
2682                 }
2683 #endif /* DOCHKVAR */
2684             }
2685 #endif /* NOSPL */
2686             debug(F111,"cmifi ? *xp, cc",*xp,cc);
2687             sp = *xp + cc;              /* Insert "*" at end */
2688 #ifdef datageneral
2689             *sp++ = '+';                /* Insert +, the DG wild card */
2690 #else
2691             *sp++ = '*';
2692 #endif /* datageneral */
2693             *sp-- = '\0';
2694 #ifdef GEMDOS
2695             if (! strchr(*xp, '.'))     /* abde.e -> abcde.e* */
2696               strcat(*xp, ".*");        /* abc -> abc*.* */
2697 #endif /* GEMDOS */
2698             debug(F110,"cmifi ? wild",*xp,0);
2699
2700             nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2701
2702             debug(F101,"cmifi matchdot","",matchdot);
2703             if (matchdot)  nzxopts |= ZX_MATCHDOT;
2704             if (recursive) nzxopts |= ZX_RECURSE;
2705             y = nzxpand(*xp,nzxopts);
2706             nfiles = y;
2707             *sp = '\0';
2708             if (y == 0) {
2709                 if (nomsg) {
2710                     printf(": %s\n",atmbuf);
2711                     printf("%s%s",cmprom,cmdbuf);
2712                     fflush(stdout);
2713                     if (np) free(np);
2714                     return(-1);
2715                 } else {
2716 #ifdef CKROOT
2717                     if (ckrooterr)
2718                       printf("?Off Limits: %s\n",atmbuf);
2719                     else
2720 #endif /* CKROOT */
2721                       printf("?No %s match - %s\n",
2722                              dirflg ? "directories" : "files", atmbuf);
2723                     if (np) free(np);
2724                     return(-9);
2725                 }
2726             } else if (y < 0) {
2727 #ifdef CKROOT
2728                 if (ckrooterr)
2729                   printf("?Off Limits: %s\n",atmbuf);
2730                 else
2731 #endif /* CKROOT */
2732                   printf("?Too many %s match - %s\n",
2733                          dirflg ? "directories" : "files", atmbuf);
2734                 if (np) free(np);
2735                 return(-9);
2736             } else {
2737                 printf(", one of the following:\n");
2738                 if (filhelp((int)y,"","",1,dirflg) < 0) {
2739                     if (np) free(np);
2740                     return(-9);
2741                 }
2742             }
2743             printf("%s%s",cmprom,cmdbuf);
2744             fflush(stdout);
2745             break;
2746 #endif /* MAC */
2747         }
2748 #ifdef BS_DIRSEP
2749         dirnamflg = 1;
2750         x = gtword(0);                  /* No, get a word */
2751         dirnamflg = 0;
2752 #else
2753         x = gtword(0);                  /* No, get a word */
2754 #endif /* BS_DIRSEP */
2755     *xp = atmbuf;
2756     }
2757 }
2758
2759 /*  C M F L D  --  Parse an arbitrary field  */
2760 /*
2761   Returns:
2762     -3 if no input present when required,
2763     -2 if field too big for buffer,
2764     -1 if reparse needed,
2765      0 otherwise, xp pointing to string result.
2766
2767   NOTE: Global flag keepallchars says whether this routine should break on CR
2768   or LF: needed for MINPUT targets and DECLARE initializers, where we want to
2769   keep control characters if the user specifies them (March 2003).  It might
2770   have been better to change the calling sequence but that was not practical.
2771 */
2772 int
2773 cmfld(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
2774     int x, xc;
2775     char *zq;
2776
2777     inword = 0;                         /* Initialize counts & pointers */
2778     cc = 0;
2779     xc = 0;
2780     *xp = "";
2781
2782     debug(F110,"cmfld xdef 1",xdef,0);
2783
2784     if (!xhlp) xhlp = "";
2785     if (!xdef) xdef = "";
2786     ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2787     xdef = cmdefault;
2788
2789     debug(F111,"cmfld xdef 2",xdef,cmflgs);
2790     debug(F111,"cmfld atmbuf 1",atmbuf,xc);
2791
2792     if ((x = cmflgs) != 1) {            /* Already confirmed? */
2793         x = gtword(0);                  /* No, get a word */
2794     } else {
2795         if (setatm(xdef,0) < 0) {       /* If so, use default, if any. */
2796             printf("?Default too long\n");
2797             return(-9);
2798         }
2799     }
2800     *xp = atmbuf;                       /* Point to result. */
2801     debug(F111,"cmfld atmbuf 2",atmbuf,cmflgs);
2802
2803     while (1) {
2804         xc += cc;                       /* Count the characters. */
2805         debug(F111,"cmfld gtword",atmbuf,xc);
2806         debug(F101,"cmfld x","",x);
2807         switch (x) {
2808           case -9:
2809             printf("Command or field too long\n");
2810           case -4:                      /* EOF */
2811           case -3:                      /* Empty. */
2812           case -2:                      /* Out of space. */
2813           case -1:                      /* Reparse needed */
2814             return(x);
2815           case 1:                       /* CR */
2816           case 0:                       /* SP */
2817             debug(F111,"cmfld 1",atmbuf,xc);
2818             if (xc == 0) {              /* If no input, return default. */
2819                 if (setatm(xdef,0) < 0) {
2820                     printf("?Default too long\n");
2821                     return(-9);
2822                 }
2823             }
2824             *xp = atmbuf;               /* Point to what we got. */
2825             debug(F111,"cmfld 2",atmbuf,(f) ? 1 : 0);
2826             if (f) {                    /* If a conversion function is given */
2827                 zq = atxbuf;            /* employ it now. */
2828                 atxn = CMDBL;
2829                 if ((*f)(*xp,&zq,&atxn) < 0)
2830                   return(-2);
2831                 debug(F111,"cmfld 3",atxbuf,xc);
2832                 /* Replace by new value -- for MINPUT only keep all chars */
2833                 if (setatm(atxbuf,keepallchars ? 3:1) < 0) { /* 16 Mar 2003 */
2834                     printf("Value too long\n");
2835                     return(-9);
2836                 }
2837                 *xp = atmbuf;
2838             }
2839             debug(F111,"cmfld 4",atmbuf,xc);
2840             if (**xp == NUL) {          /* If variable evaluates to null */
2841                 if (setatm(xdef,0) < 0) {
2842                     printf("?Default too long\n");
2843                     return(-9);
2844                 }
2845                 if (**xp == NUL) x = -3; /* If still empty, return -3. */
2846             }
2847             debug(F111,"cmfld returns",*xp,x);
2848             return(x);
2849           case 2:                       /* ESC */
2850             if (xc == 0 && *xdef) {
2851                 printf("%s ",xdef); /* If at beginning of field, */
2852 #ifdef GEMDOS
2853                 fflush(stdout);
2854 #endif /* GEMDOS */
2855                 addbuf(xdef);           /* Supply default. */
2856                 inword = cmflgs = 0;
2857                 if (setatm(xdef,0) < 0) {
2858                     printf("?Default too long\n");
2859                     return(-9);
2860                 } else                  /* Return as if whole field */
2861                   return(0);            /* typed, followed by space. */
2862             } else {
2863                 bleep(BP_WARN);
2864             }
2865             break;
2866           case 3:                       /* Question mark */
2867             debug(F110,"cmfld QUESTIONMARK",cmdbuf,0);
2868             if (*xhlp == NUL)
2869               printf(" Please complete this field");
2870             else
2871               printf(" %s",xhlp);
2872             printf("\n%s%s",cmprom,cmdbuf);
2873             fflush(stdout);
2874             break;
2875         }
2876         debug(F111,"cmfld gtword A x",cmdbuf,x);
2877         x = gtword(0);
2878         debug(F111,"cmfld gtword B x",cmdbuf,x);
2879     }
2880 }
2881
2882
2883 /*  C M T X T  --  Get a text string, including confirmation  */
2884
2885 /*
2886   Print help message 'xhlp' if ? typed, supply default 'xdef' if null
2887   string typed.  Returns:
2888
2889    -1 if reparse needed or buffer overflows.
2890     1 otherwise.
2891
2892   with cmflgs set to return code, and xp pointing to result string.
2893 */
2894 int
2895 cmtxt(xhlp,xdef,xp,f) char *xhlp; char *xdef; char **xp; xx_strp f; {
2896
2897     int x, i;
2898     char *xx, *zq;
2899     static int xc;
2900
2901     if (!xhlp) xhlp = "";
2902     if (!xdef) xdef = "";
2903
2904     cmfldflgs = 0;
2905
2906     cmdefault[0] = NUL;
2907     if (*xdef)
2908       ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2909     xdef = cmdefault;
2910
2911     debug(F101,"cmtxt cmflgs","",cmflgs);
2912     inword = 0;                         /* Start atmbuf counter off at 0 */
2913     cc = 0;
2914     if (cmflgs == -1) {                 /* If reparsing, */
2915         *xp = pp;
2916         xc = (int)strlen(*xp);          /* get back the total text length, */
2917         bp = *xp;                       /* and back up the pointers. */
2918         np = *xp;
2919         pp = *xp;
2920     } else {                            /* otherwise, */
2921         /* debug(F100,"cmtxt: fresh start","",0); */
2922         *xp = "";                       /* start fresh. */
2923         xc = 0;
2924     }
2925     *atmbuf = NUL;                      /* And empty the atom buffer. */
2926     rtimer();                           /* Reset timer */
2927     if ((x = cmflgs) != 1) {
2928         int done = 0;
2929         while (!done) {
2930             x = gtword(0);              /* Get first word. */
2931             *xp = pp;                   /* Save pointer to it. */
2932             /* debug(F111,"cmtxt:",*xp,cc); */
2933             if (x == -10) {
2934                 if (gtimer() > timelimit) {
2935                     /* if (!quiet) printf("?Timed out\n"); */
2936                     return(x);
2937                 }
2938             } else
2939               done = 1;
2940         }
2941     }
2942     while (1) {                         /* Loop for each word in text. */
2943         xc += cc;                       /* Char count for all words. */
2944         /* debug(F111,"cmtxt gtword",atmbuf,xc); */
2945         /* debug(F101,"cmtxt x","",x); */
2946         switch (x) {
2947           case -10:
2948             if (gtimer() > timelimit) {
2949 #ifdef IKSD
2950                 extern int inserver;
2951                 if (inserver) {
2952                     printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
2953                     doexit(GOOD_EXIT,0);
2954                 }
2955 #endif /* IKSD */
2956                 /* if (!quiet) printf("?Timed out\n"); */
2957                 return(-10);
2958             } else {
2959                 x = gtword(0);
2960                 continue;
2961             }
2962           case -9:                      /* Buffer overflow */
2963             printf("Command or field too long\n");
2964           case -4:                      /* EOF */
2965 #ifdef MAC
2966           case -3:                      /* Quit/Timeout */
2967 #endif /* MAC */
2968           case -2:                      /* Overflow */
2969           case -1:                      /* Deletion */
2970             return(x);
2971           case 0:                       /* Space */
2972             xc++;                       /* Just count it */
2973             break;
2974           case 1:                       /* CR or LF */
2975             if (xc == 0) *xp = xdef;
2976             if (f) {                    /* If a conversion function is given */
2977                 char * sx = atxbuf;
2978                 zq = atxbuf;            /* Point to the expansion buffer */
2979                 atxn = CMDBL;           /* specify its length */
2980                 debug(F111,"cmtxt calling (*f)",*xp,atxbuf);
2981                 if ((x = (*f)(*xp,&zq,&atxn)) < 0) return(-2);
2982                 sx = atxbuf;
2983 #ifndef COMMENT
2984                 cc = 0;
2985                 while (*sx++) cc++;     /* (faster than calling strlen) */
2986 #else
2987                 cc = (int)strlen(atxbuf);
2988 #endif /* COMMENT */
2989                 /* Should be equal to (CMDBL - atxn) but isn't always. */
2990                 /* Why not? */
2991                 if (cc < 1) {           /* Nothing in expansion buffer? */
2992                     *xp = xdef;         /* Point to default string instead. */
2993 #ifndef COMMENT
2994                     sx = xdef;
2995                     while (*sx++) cc++; /* (faster than calling strlen) */
2996 #else
2997                     cc = strlen(xdef);
2998 #endif /* COMMENT */
2999                 } else {                /* Expansion function got something */
3000                     *xp = atxbuf;       /* return pointer to it. */
3001                 }
3002                 debug(F111,"cmtxt (*f)",*xp,cc);
3003             } else {                    /* No expansion function */
3004 #ifndef COMMENT
3005                 /* Avoid a strlen() call */
3006                 xx = *xp;
3007                 cc = 0;
3008                 while (*xx++) cc++;
3009 #else
3010                 /* NO!  xc is apparently not always set appropriately */
3011                 cc = xc;
3012 #endif /* COMMENT */
3013             }
3014             xx = *xp;
3015 #ifdef COMMENT
3016             /* strlen() no longer needed */
3017             for (i = (int)strlen(xx) - 1; i > 0; i--)
3018 #else
3019             for (i = cc - 1; i > 0; i--)
3020 #endif /* COMMENT */
3021               if (xx[i] != SP)          /* Trim trailing blanks */
3022                 break;
3023               else
3024                 xx[i] = NUL;
3025             return(x);
3026           case 2:                       /* ESC */
3027             if (xc == 0) {              /* Nothing typed yet */
3028                 if (*xdef) {            /* Have a default for this field? */
3029                     printf("%s ",xdef); /* Yes, supply it */
3030                     inword = cmflgs = 0;
3031 #ifdef GEMDOS
3032                     fflush(stdout);
3033 #endif /* GEMDOS */
3034                     cc = addbuf(xdef);
3035                 } else bleep(BP_WARN);  /* No default */
3036             } else {                    /* Already in field */
3037                 int x; char *p;
3038                 x = strlen(atmbuf);
3039                 if (ckstrcmp(atmbuf,xdef,x,0)) {    /* Matches default? */
3040                     bleep(BP_WARN);                 /* No */
3041                 } else if ((int)strlen(xdef) > x) { /* Yes */
3042                     p = xdef + x;
3043                     printf("%s ", p);
3044 #ifdef GEMDOS
3045                     fflush(stdout);
3046 #endif /* GEMDOS */
3047                     addbuf(p);
3048                     inword = cmflgs = 0;
3049                     debug(F110,"cmtxt: addbuf",cmdbuf,0);
3050                 } else {
3051                     bleep(BP_WARN);
3052                 }
3053             }
3054             break;
3055           case 3:                       /* Question Mark */
3056             if (*xhlp == NUL)
3057               printf(" Text string");
3058             else
3059               printf(" %s",xhlp);
3060             printf("\n%s%s",cmprom,cmdbuf);
3061             fflush(stdout);
3062             break;
3063           default:
3064             printf("?Unexpected return code from gtword() - %d\n",x);
3065             return(-2);
3066         }
3067         x = gtword(0);
3068     }
3069 }
3070
3071 /*  C M K E Y  --  Parse a keyword  */
3072
3073 /*
3074  Call with:
3075    table    --  keyword table, in 'struct keytab' format;
3076    n        --  number of entries in table;
3077    xhlp     --  pointer to help string;
3078    xdef     --  pointer to default keyword;
3079    f        --  processing function (e.g. to evaluate variables)
3080    pmsg     --  0 = don't print error messages
3081                 1 = print error messages
3082                 2 = include CM_HLP keywords even if invisible
3083                 3 = 1+2
3084                 4 = parse a switch (keyword possibly ending in : or =)
3085  Returns:
3086    -3       --  no input supplied and no default available
3087    -2       --  input doesn't uniquely match a keyword in the table
3088    -1       --  user deleted too much, command reparse required
3089     n >= 0  --  value associated with keyword
3090 */
3091 int
3092 cmkey(table,n,xhlp,xdef,f)
3093 /* cmkey */  struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3094     return(cmkey2(table,n,xhlp,xdef,"",f,1));
3095 }
3096 int
3097 cmkeyx(table,n,xhlp,xdef,f)
3098 /* cmkeyx */  struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3099     return(cmkey2(table,n,xhlp,xdef,"",f,0));
3100 }
3101 int
3102 cmswi(table,n,xhlp,xdef,f)
3103 /* cmswi */  struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3104     return(cmkey2(table,n,xhlp,xdef,"",f,4));
3105 }
3106
3107 int
3108 cmkey2(table,n,xhlp,xdef,tok,f,pmsg)
3109     struct keytab table[];
3110     int n;
3111     char *xhlp, *xdef;
3112     char *tok;
3113     xx_strp f;
3114     int pmsg;
3115 { /* cmkey2 */
3116     extern int havetoken;
3117     int i, tl, y, z = 0, zz, xc, wordlen = 0, cmswitch;
3118     char *xp, *zq;
3119
3120     if (!xhlp) xhlp = "";
3121     if (!xdef) xdef = "";
3122
3123     cmfldflgs = 0;
3124     if (!table) {
3125         printf("?Keyword table missing\n");
3126         return(-9);
3127     }
3128     tl = (int)strlen(tok);
3129
3130     inword = xc = cc = 0;               /* Clear character counters. */
3131     cmswitch = pmsg & 4;                /* Flag for parsing a switch */
3132
3133     debug(F101,"cmkey: pmsg","",pmsg);
3134     debug(F101,"cmkey: cmflgs","",cmflgs);
3135     debug(F101,"cmkey: cmswitch","",cmswitch);
3136     /* debug(F101,"cmkey: cmdbuf","",cmdbuf);*/
3137
3138     ppvnambuf[0] = NUL;
3139
3140     if ((zz = cmflgs) == 1) {           /* Command already entered? */
3141         if (setatm(xdef,0) < 0) {       /* Yes, copy default into atom buf */
3142             printf("?Default too long\n");
3143             return(-9);
3144         }
3145         rtimer();                        /* Reset timer */
3146     } else {
3147         rtimer();                        /* Reset timer */
3148         zz = gtword((pmsg == 4) ? 1 : 0);/* Otherwise get a command word */
3149     }
3150
3151     debug(F101,"cmkey table length","",n);
3152     debug(F101,"cmkey cmflgs","",cmflgs);
3153     debug(F101,"cmkey cc","",cc);
3154
3155     while (1) {
3156         xc += cc;
3157         debug(F111,"cmkey gtword xc",atmbuf,xc);
3158         debug(F101,"cmkey gtword zz","",zz);
3159
3160         switch (zz) {
3161           case -10:                     /* Timeout */
3162             if (gtimer() < timelimit) {
3163                 zz = gtword((pmsg == 4) ? 1 : 0);
3164                 continue;
3165             } else {
3166 #ifdef IKSD
3167                 extern int inserver;
3168                 if (inserver) {
3169                     printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3170                     doexit(GOOD_EXIT,0);
3171                 }
3172 #endif /* IKSD */
3173                 return(-10);
3174             }
3175           case -5:
3176             return(cmflgs = 0);
3177           case -9:
3178             printf("Command or field too long\n");
3179           case -4:                      /* EOF */
3180           case -3:                      /* Null Command/Quit/Timeout */
3181           case -2:                      /* Buffer overflow */
3182           case -1:                      /* Or user did some deleting. */
3183             return(cmflgs = zz);
3184
3185
3186           case 1:                       /* CR */
3187           case 0:                       /* User terminated word with space */
3188           case 4:                       /* or switch ending in : or = */
3189             wordlen = cc;               /* Length if no conversion */
3190             if (cc == 0) {              /* Supply default if we got nothing */
3191                 if ((wordlen = setatm(xdef,(zz == 4) ? 2 : 0)) < 0) {
3192                     printf("?Default too long\n");
3193                     return(-9);
3194                 }
3195             }
3196             if (zz == 1 && cc == 0)     /* Required field missing */
3197               return(-3);
3198
3199             if (f) {                    /* If a conversion function is given */
3200                 char * p2;
3201                 zq = atxbuf;            /* apply it */
3202                 p2 = atxbuf;
3203                 atxn = CMDBL;
3204                 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3205                 debug(F110,"cmkey atxbuf after *f",atxbuf,0);
3206                 if (!*p2)               /* Supply default if we got nothing */
3207                   p2 = xdef;
3208                 ckstrncpy(ppvnambuf,atmbuf,PPVLEN);
3209                 if ((wordlen = setatm(p2,(zz == 4) ? 2 : 0)) < 0) {
3210                     printf("Evaluated keyword too long\n");
3211                     return(-9);
3212                 }
3213 #ifdef M_UNGW
3214                 /*
3215                   This bit lets us save more than one "word".
3216                   For example, "define \%x echo one two three", "\%x".
3217                   It works too, but it breaks labels, and therefore
3218                   WHILE and FOR loops, etc.
3219                 */
3220                 if (p2[wordlen] >= SP) {
3221                     p2 += wordlen;
3222                     while (*p2 == SP) p2++;
3223                     if (*p2) {
3224                         ungword();
3225                         pp = p2;
3226                     }
3227                 }
3228 #endif /* M_UNGW */
3229             }
3230             if (cmswitch && *atmbuf != '/') {
3231                 if (pmsg & 1) {
3232                     bleep(BP_FAIL);
3233                     printf("?Not a switch - %s\n",atmbuf);
3234                 }
3235                 cmflgs = -2;
3236                 return(-6);
3237             }
3238             if (cmswitch) {
3239                 int i;
3240                 for (i = 0; i < wordlen; i++) {
3241                     if (atmbuf[i] == ':' || atmbuf[i] == '=') {
3242                         brkchar = atmbuf[i];
3243                         atmbuf[i] = NUL;
3244                         break;
3245                     }
3246                 }
3247             }
3248
3249 #ifdef TOKPRECHECK
3250 /* This was an effective optimization but it breaks sometimes on labels. */
3251             if (tl && !isalpha(atmbuf[0])) { /* Precheck for token */
3252                 for (i = 0; i < tl; i++) { /* Save function call to ckstrchr */
3253                     if (tok[i] == atmbuf[0]) {
3254                         debug(F000,"cmkey token:",atmbuf,*atmbuf);
3255                         ungword();  /* Put back the following word */
3256                         return(-5); /* Special return code for token */
3257                     }
3258                 }
3259             }
3260 #endif /* TOKPRECHECK */
3261
3262             y = lookup(table,atmbuf,n,&z); /* Look up word in the table */
3263             debug(F111,"cmkey lookup",atmbuf,y);
3264             debug(F101,"cmkey zz","",zz);
3265             debug(F101,"cmkey cmflgs","",cmflgs);
3266             debug(F101,"cmkey crflag","",crflag);
3267             switch (y) {
3268               case -3:                  /* Nothing to look up */
3269                 break;
3270               case -2:                  /* Ambiguous */
3271                 cmflgs = -2;
3272                 if (pmsg & 1) {
3273                     bleep(BP_FAIL);
3274                     printf("?Ambiguous - %s\n",atmbuf);
3275                     return(-9);
3276                 }
3277                 return(-2);
3278               case -1:                  /* Not found at all */
3279 #ifndef TOKPRECHECK
3280                 if (tl) {
3281                     for (i = 0; i < tl; i++) /* Check for token */
3282                       if (tok[i] == *atmbuf) { /* Got one */
3283                           debug(F000,"cmkey token:",atmbuf,*atmbuf);
3284                           ungword();  /* Put back the following word */
3285                           return(-5); /* Special return code for token */
3286                       }
3287                 }
3288 #endif /* TOKPRECHECK */
3289
3290                 if (tl == 0) {          /* No tokens were included */
3291 #ifdef OS2
3292                     /* In OS/2 and Windows, allow for a disk letter like DOS */
3293                     if (isalpha(*atmbuf) && *(atmbuf+1) == ':')
3294                       return(-7);
3295 #endif /* OS2 */
3296                     if ((pmsg & 1) && !quiet) {
3297                         bleep(BP_FAIL);
3298                         printf("?No keywords match - %s\n",atmbuf); /* cmkey */
3299                     }
3300                     return(cmflgs = -9);
3301                 } else {
3302                     if (cmflgs == 1 || cmswitch) /* cmkey2 or cmswi */
3303                       return(cmflgs = -6);
3304                     else
3305                       return(cmflgs = -2);
3306                     /* The -6 code is to let caller try another table */
3307                 }
3308                 break;
3309               default:
3310 #ifdef CK_RECALL
3311                 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3312 #endif /* CK_RECALL */
3313                 if (zz == 4)
3314                   swarg = 1;
3315                 cmkwflgs = table[z].flgs;
3316                 break;
3317             }
3318             return(y);
3319
3320           case 2:                       /* User terminated word with ESC */
3321             debug(F101,"cmkey Esc cc","",cc);
3322             if (cc == 0) {
3323                 if (*xdef != NUL) {     /* Nothing in atmbuf */
3324                     printf("%s ",xdef); /* Supply default if any */
3325 #ifdef GEMDOS
3326                     fflush(stdout);
3327 #endif /* GEMDOS */
3328                     addbuf(xdef);
3329                     if (setatm(xdef,0) < 0) {
3330                         printf("?Default too long\n");
3331                         return(-9);
3332                     }
3333                     inword = cmflgs = 0;
3334                     debug(F111,"cmkey: default",atmbuf,cc);
3335                 } else {
3336                     debug(F101,"cmkey Esc pmsg","",0);
3337 #ifdef COMMENT
3338 /*
3339   Chained FDBs...  The idea is that this function might not have a default,
3340   but the next one might.  But if it doesn't, there is no way to come back to
3341   this one.  To be revisited later...
3342 */
3343                     if (xcmfdb)         /* Chained fdb -- try next one */
3344                       return(-3);
3345 #endif /* COMMENT */
3346                     if (pmsg & (1|4)) { /* So for now just beep */
3347                         bleep(BP_WARN);
3348                     }
3349                     break;
3350                 }
3351             }
3352             if (f) {                    /* If a conversion function is given */
3353                 char * pp;
3354                 zq = atxbuf;            /* apply it */
3355                 pp = atxbuf;
3356                 atxn = CMDBL;
3357                 if ((*f)(atmbuf,&zq,&atxn) < 0)
3358                   return(-2);
3359                 if (!*pp)
3360                   pp = xdef;
3361                 if (setatm(pp,0) < 0) {
3362                     printf("Evaluated keyword too long\n");
3363                     return(-9);
3364                 }
3365             }
3366             y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
3367             debug(F111,"cmkey lookup y",atmbuf,y);
3368             debug(F111,"cmkey lookup z",atmbuf,z);
3369             if (y == -2 && z >= 0 && z < n) { /* Ambiguous */
3370 #ifndef NOPARTIAL
3371                 int j, k, len = 9999;   /* Do partial completion */
3372                 /* Skip past any abbreviations in the table */
3373                 for ( ; z < n; z++) {
3374                     if ((table[z].flgs & CM_ABR) == 0)
3375                       break;
3376                     if (!(table[z].flgs & CM_HLP) || (pmsg & 2))
3377                       break;
3378                 }
3379                 debug(F111,"cmkey partial z",atmbuf,z);
3380                 debug(F111,"cmkey partial n",atmbuf,n);
3381                 for (j = z+1; j < n; j++) {
3382                     debug(F111,"cmkey partial j",table[j].kwd,j);
3383                     if (ckstrcmp(atmbuf,table[j].kwd,cc,0))
3384                       break;
3385                     if (table[j].flgs & CM_ABR)
3386                       continue;
3387                     if ((table[j].flgs & CM_HLP) && !(pmsg & 2))
3388                       continue;
3389                     k = ckstrpre(table[z].kwd,table[j].kwd);
3390                     debug(F111,"cmkey partial k",table[z].kwd,k);
3391                     if (k < len)
3392                       len = k; /* Length of longest common prefix */
3393                 }
3394                 debug(F111,"cmkey partial len",table[z].kwd,len);
3395                 if (len != 9999 && len > cc) {
3396                     ckstrncat(atmbuf,table[z].kwd+cc,ATMBL);
3397                     atmbuf[len] = NUL;
3398                     printf("%s",atmbuf+cc);
3399                     ckstrncat(cmdbuf,atmbuf+cc,CMDBL);
3400                     xc += (len - cc);
3401                     cc = len;
3402                 }
3403 #endif /* NOPARTIAL */
3404                 bleep(BP_WARN);
3405                 break;
3406             } else if (y == -3) {
3407                 bleep(BP_WARN);
3408                 break;
3409             } else if (y == -1) {       /* Not found */
3410                 if ((pmsg & 1) && !quiet) {
3411                     bleep(BP_FAIL);
3412                     printf("?No keywords match - \"%s\"\n",atmbuf);
3413                 }
3414                 cmflgs = -2;
3415                 return(-9);
3416             }
3417 /*
3418   If we found it, but it's a help-only keyword and the "help" bit is not
3419   set in pmsg, then not found.
3420 */
3421             debug(F101,"cmkey flgs","",table[z].flgs);
3422             if (test(table[z].flgs,CM_HLP) && ((pmsg & 2) == 0)) {
3423                 if ((pmsg & 1) && !quiet) {
3424                     bleep(BP_FAIL);
3425                     printf("?No keywords match - %s\n",atmbuf);
3426                 }
3427                 cmflgs = -2;
3428                 return(-9);
3429             }
3430 /*
3431   See if the keyword just found has the CM_ABR bit set in its flgs field, and
3432   if so, search forwards in the table for a keyword that has the same kwval
3433   but does not have CM_ABR (or CM_INV?) set, and then expand using the full
3434   keyword.  WARNING: This assumes that (a) keywords are in alphabetical order,
3435   and (b) the CM_ABR bit is set only if the the abbreviated keyword is a true
3436   abbreviation (left substring) of the full keyword.
3437 */
3438             if (test(table[z].flgs,CM_ABR)) {
3439                 int zz;
3440                 for (zz = z+1; zz < n; zz++)
3441                   if ((table[zz].kwval == table[z].kwval) &&
3442                       (!test(table[zz].flgs,CM_ABR)) &&
3443                       (!test(table[zz].flgs,CM_INV))) {
3444                       z = zz;
3445                       break;
3446                   }
3447             }
3448             xp = table[z].kwd + cc;
3449             if (cmswitch && test(table[z].flgs,CM_ARG)) {
3450 #ifdef VMS
3451                 printf("%s=",xp);
3452                 brkchar = '=';
3453 #else
3454                 printf("%s:",xp);
3455                 brkchar = ':';
3456 #endif /* VMS */
3457             } else {
3458                 printf("%s ",xp);
3459                 brkchar = SP;
3460             }
3461 #ifdef CK_RECALL
3462             if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3463 #endif /* CK_RECALL */
3464             cmkwflgs = table[z].flgs;
3465 #ifdef GEMDOS
3466             fflush(stdout);
3467 #endif /* GEMDOS */
3468             addbuf(xp);
3469             if (cmswitch && test(table[z].flgs,CM_ARG)) {
3470                 bp--;                   /* Replace trailing space with : */
3471 #ifdef VMS
3472                 *bp++ = '=';
3473 #else
3474                 *bp++ = ':';
3475 #endif /* VMS */
3476                 *bp = NUL;
3477                 np = bp;
3478                 swarg = 1;
3479             }
3480             inword = 0;
3481             cmflgs = 0;
3482             debug(F110,"cmkey: addbuf",cmdbuf,0);
3483             return(y);
3484
3485           case 3:                       /* User typed "?" */
3486             if (f) {                    /* If a conversion function is given */
3487                 char * pp;
3488                 zq = atxbuf;            /* do the conversion now. */
3489                 pp = atxbuf;
3490                 atxn = CMDBL;
3491                 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3492                 if (setatm(pp,0) < 0) {
3493                     printf("?Evaluated keyword too long\n");
3494                     return(-9);
3495                 }
3496             }
3497             y = lookup(table,atmbuf,n,&z); /* Look up what we have so far. */
3498             if (y == -1) {
3499                 /*
3500                   Strictly speaking if the main keyword table search fails,
3501                   then we should look in the token table if one is given.
3502                   But in practice, tokens are also included in the main
3503                   keyword table.
3504                 */
3505                 cmflgs = -2;
3506                 if ((pmsg & 1) && !quiet) {
3507                     bleep(BP_FAIL);
3508                     printf(" No keywords match\n");
3509                     return(-9);
3510                 }
3511                 return(-2);
3512             }
3513 #ifndef COMMENT
3514             /* This is to allow ?-help to work immediately after a token */
3515             /* without having to type an intermediate space */
3516             if (tl) {
3517                 for (i = 0; i < tl; i++) /* Check for token */
3518                   if (tok[i] == *atmbuf) { /* Got one */
3519                       debug(F000,"cmkey token:",atmbuf,*atmbuf);
3520                       ungword();        /* Put back the following word */
3521                       cmflgs = 3;       /* Force help next time around */
3522                       return(-5);       /* Special return code for token */
3523                   }
3524             }
3525 #endif /* COMMENT */
3526
3527             if (*xhlp == NUL)
3528               printf(" One of the following:\n");
3529             else
3530               printf(" %s, one of the following:\n",xhlp);
3531             {
3532                 int x;
3533                 x = pmsg & (2|4);       /* See kwdhelp() comments */
3534                 if (atmbuf[0])          /* If not at beginning of field */
3535                   x |= 1;               /* also show invisibles */
3536                 kwdhelp(table,n,atmbuf,"","",1,x);
3537             }
3538 #ifndef NOSPL
3539             if (!havetoken) {
3540                 extern int topcmd;
3541                 if (tl > 0 && topcmd != XXHLP) /* This is bad... */
3542                   printf("or a macro name (\"do ?\" for a list) ");
3543             }
3544 #endif /* NOSPL */
3545             if (*atmbuf == NUL && !havetoken) {
3546                 if (tl == 1)
3547                   printf("or the token %c\n",*tok);
3548                 else if (tl > 1)
3549                   printf("or one of the tokens: %s\n",ckspread(tok));
3550             }
3551             printf("%s%s", cmprom, cmdbuf);
3552             fflush(stdout);
3553             break;
3554
3555           default:
3556             printf("\n%d - Unexpected return code from gtword\n",zz);
3557             return(cmflgs = -2);
3558         }
3559         zz = gtword((pmsg == 4) ? 1 : 0);
3560         debug(F111,"cmkey gtword zz",atmbuf,zz);
3561     }
3562 }
3563
3564 int
3565 chktok(tlist) char *tlist; {
3566     char *p;
3567     p = tlist;
3568     while (*p != NUL && *p != *atmbuf) p++;
3569     return((*p) ? (int) *p : 0);
3570 }
3571
3572 /* Routines for parsing and converting dates and times */
3573
3574 #define isdatesep(c) (ckstrchr(" -/._",c))
3575
3576 #define CMDATEBUF 1024
3577 char cmdatebuf[CMDATEBUF+4] = { NUL, NUL };
3578 static char * cmdatebp = cmdatebuf;
3579 char * cmdatemsg = NULL;
3580
3581 static struct keytab timeunits[] = {
3582     { "days",   TU_DAYS,   0 },
3583     { "months", TU_MONTHS, 0 },
3584     { "weeks",  TU_WEEKS,  0 },
3585     { "wks",    TU_WEEKS,  0 },
3586     { "years",  TU_YEARS,  0 },
3587     { "yrs",    TU_YEARS,  0 }
3588 };
3589 static int nunits = (sizeof(timeunits) / sizeof(struct keytab));
3590
3591 #define SYM_NOW  0
3592 #define SYM_TODA 1
3593 #define SYM_TOMO 2
3594 #define SYM_YEST 3
3595
3596 static struct keytab symdaytab[] = {
3597     { "now",       SYM_NOW,  0 },
3598     { "today",     SYM_TODA, 0 },
3599     { "tomorrow",  SYM_TOMO, 0 },
3600     { "yesterday", SYM_YEST, 0 }
3601 };
3602 static int nsymdays = (sizeof(symdaytab) / sizeof(struct keytab));
3603
3604 static struct keytab daysofweek[] = {
3605     { "Friday",    5, 0 },
3606     { "Monday",    1, 0 },
3607     { "Saturday",  6, 0 },
3608     { "Sunday",    0, 0 },
3609     { "Thursday",  4, 0 },
3610     { "Tuesday",   2, 0 },
3611     { "Wednesday", 3, 0 }
3612 };
3613
3614 static struct keytab usatz[] = {        /* RFC 822 timezones  */
3615     { "cdt",  5, 0 },                   /* Values are GMT offsets */
3616     { "cst",  6, 0 },
3617     { "edt",  4, 0 },
3618     { "est",  5, 0 },
3619     { "gmt",  0, 0 },
3620     { "mdt",  6, 0 },
3621     { "mst",  7, 0 },
3622     { "pdt",  7, 0 },
3623     { "pst",  8, 0 },
3624     { "utc",  0, 0 },
3625     { "zulu", 0, 0 }
3626 };
3627 static int nusatz = (sizeof(usatz) / sizeof(struct keytab));
3628
3629
3630 /*  C M C V T D A T E  --  Converts free-form date to standard form.  */
3631
3632 /*
3633    Call with
3634      s = pointer to free-format date, time, or date and time.
3635      t = 0: return time only if time was given in s.
3636      t = 1: always return time (00:00:00 if no time given in s).
3637      t = 2: allow time to be > 24:00:00.
3638    Returns:
3639      NULL on failure;
3640      Pointer to "yyyymmdd hh:mm:ss" (local date-time) on success.
3641 */
3642
3643 /*
3644   Before final release the following long lines should be wrapped.
3645   Until then we leave them long since wrapping them wrecks EMACS's
3646   C indentation.
3647 */
3648
3649 /* asctime pattern */
3650 static char * atp1 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9]";
3651
3652 /* asctime pattern with timezone */
3653 static char * atp2 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [A-Z][A-Z][A-Z] [0-9][0-9][0-9][0-9]";
3654
3655 #define DATEBUFLEN 127
3656 #define YYYYMMDD 12
3657
3658 #define isleap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
3659 static int mdays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
3660
3661 #define NEED_DAYS 1
3662 #define NEED_HRS  2
3663 #define NEED_MINS 3
3664 #define NEED_SECS 4
3665 #define NEED_FRAC 5
3666
3667 #define DELTABUF 256
3668 static char deltabuf[DELTABUF];
3669 static char * deltabp = deltabuf;
3670
3671 char *
3672 cmdelta(yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss)
3673     int yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss;
3674 /* cmdelta */ {
3675     int zyy, zmo, zdd, zhh, zmm, zss;
3676     long t1, t2, t3, t4;
3677     long d1 = 0, d2, d3;
3678     char datebuf[DATEBUFLEN+1];
3679
3680 #ifdef DEBUG
3681     if (deblog) {
3682         debug(F101,"cmdelta yy","",yy);
3683         debug(F101,"cmdelta mo","",mo);
3684         debug(F101,"cmdelta dd","",dd);
3685         debug(F101,"cmdelta hh","",hh);
3686         debug(F101,"cmdelta mm","",mm);
3687         debug(F101,"cmdelta ss","",ss);
3688         debug(F101,"cmdelta sin","",sign);
3689         debug(F101,"cmdelta dyy","",dyy);
3690         debug(F101,"cmdelta dmo","",dmo);
3691         debug(F101,"cmdelta ddd","",ddd);
3692         debug(F101,"cmdelta dhh","",dhh);
3693         debug(F101,"cmdelta dmm","",dmm);
3694         debug(F101,"cmdelta dss","",dss);
3695     }
3696 #endif /* DEBLOG */
3697
3698     if (yy < 0 || yy > 9999) {
3699         makestr(&cmdatemsg,"Base year out of range");
3700         debug(F111,"cmdelta",cmdatemsg,-1);
3701         return(NULL);
3702     }
3703     if (mo < 1 || mo > 12) {
3704         makestr(&cmdatemsg,"Base month out of range");
3705         debug(F111,"cmdelta",cmdatemsg,-1);
3706         return(NULL);
3707     }
3708     if (dd < 1 || dd > mdays[mo]) {
3709         makestr(&cmdatemsg,"Base day out of range");
3710         debug(F111,"cmdelta",cmdatemsg,-1);
3711         return(NULL);
3712     }
3713     if (hh < 0 || hh > 23) {
3714         makestr(&cmdatemsg,"Base hour out of range");
3715         debug(F111,"cmdelta",cmdatemsg,-1);
3716         return(NULL);
3717     }
3718     if (mm < 0 || mm > 59) {
3719         makestr(&cmdatemsg,"Base minute out of range");
3720         debug(F111,"cmdelta",cmdatemsg,-1);
3721         return(NULL);
3722     }
3723     if (ss < 0 || ss > 60) {
3724         makestr(&cmdatemsg,"Base second out of range");
3725         debug(F111,"cmdelta",cmdatemsg,-1);
3726         return(NULL);
3727     }
3728     sign = (sign < 0) ? -1 : 1;
3729     if (dmo != 0) {
3730         mo += (sign * dmo);
3731         if (mo > 12 || mo < 0) {
3732             yy += mo / 12;
3733             mo = mo % 12;
3734         }
3735     }
3736     if (dyy != 0) {
3737         yy += (sign * dyy);
3738         if (yy > 9999 || yy < 0) {
3739             makestr(&cmdatemsg,"Result year out of range");
3740             debug(F111,"cmdelta",cmdatemsg,-1);
3741             return(NULL);
3742         }
3743     }
3744     sprintf(datebuf,"%04d%02d%02d %02d:%02d:%02d",yy,mo,dd,hh,mm,ss);
3745     d1 = mjd(datebuf);
3746     debug(F111,"cmdelta mjd",datebuf,d1);    
3747
3748     t1 = hh * 3600 + mm * 60 + ss;      /* Base time to secs since midnight */
3749     t2 = dhh * 3600 + dmm * 60 + dss;   /* Delta time, ditto */
3750     t3 = t1 + (sign * t2);              /* Get sum (or difference) */
3751     
3752     d2 = (sign * ddd);                  /* Delta days */
3753     d2 += t3 / 86400L;
3754
3755     t4 = t3 % 86400L;                   /* Fractional part of day */
3756     if (t4 < 0) {                       /* If negative */
3757         d2--;                           /* one less delta day */
3758         t4 += 86400L;                   /* get positive seconds */
3759     }
3760     hh = (int) (t4 / 3600L);
3761     mm = (int) (t4 % 3600L) / 60;
3762     ss = (int) (t4 % 3600L) % 60;
3763
3764     sprintf(datebuf,"%s %02d:%02d:%02d", mjd2date(d1+d2),hh,mm,ss);
3765     {
3766         int len, k, n;
3767         char * p;
3768         len = strlen(datebuf);
3769         k = deltabp - (char *)deltabuf; /* Space used */
3770         n = DELTABUF - k - 1;           /* Space left */
3771         if (n < len) {                  /* Not enough? */
3772             deltabp = deltabuf;         /* Wrap around */
3773             n = DELTABUF;
3774         }
3775         ckstrncpy(deltabp,datebuf,n);
3776         p = deltabp;
3777         deltabp += len + 1;
3778         return(p);
3779     }
3780 }
3781
3782
3783 /* Convert Delta Time to Seconds */
3784
3785 int
3786 delta2sec(s,result) char * s; long * result; {
3787     long ddays = 0L, zz;
3788     int dsign = 1, dhours = 0, dmins = 0, dsecs = 0, units;
3789     int state = NEED_DAYS;
3790     char *p, *p2, *p3, c = 0;
3791     char buf[64];
3792
3793     if (!s) s = "";
3794     if (!*s)
3795       return(-1);
3796     if ((int)strlen(s) > 63)
3797       return(-1);
3798     ckstrncpy(buf,s,64);
3799     p = buf;
3800
3801     if (*p != '+' && *p != '-')
3802       return(-1);
3803
3804     if (*p++ == '-')
3805       dsign = -1;
3806     while (*p == SP)                    /* Skip intervening spaces */
3807       p++;
3808
3809     while (state) {                     /* FSA to parse delta time */
3810         if (state < 0 || !isdigit(*p))
3811           return(-1);
3812         p2 = p;                         /* Get next numeric field */
3813         while (isdigit(*p2))
3814           p2++;
3815         c = *p2;                        /* And break character */
3816         *p2 = NUL;                      /* Terminate the number */
3817         switch (state) {                /* Interpret according to state */
3818           case NEED_DAYS:               /* Initial */
3819             if ((c == '-') ||           /* VMS format */
3820                 ((c == 'd' || c == 'D')
3821                  && !isalpha(*(p2+1)))) { /* Days */
3822                 ddays = atol(p);
3823                 if (!*(p2+1))                   
3824                   state = 0;
3825                 else                    /* if anything is left */
3826                   state = NEED_HRS;     /* now we want hours. */
3827             } else if (c == ':') {      /* delimiter is colon */
3828                 dhours = atoi(p);       /* so it's hours */
3829                 state = NEED_MINS;      /* now we want minutes */
3830             } else if (!c) {            /* end of string */
3831                 dhours = atoi(p);       /* it's still hours */
3832                 state = 0;              /* and we're done */
3833             } else if (isalpha(c) || c == SP) {
3834                 if (c == SP) {          /* It's a keyword? */
3835                     p2++;               /* Skip spaces */
3836                     while (*p2 == SP)
3837                       p2++;
3838                 } else {                /* or replace first letter */
3839                     *p2 = c;
3840                 }
3841                 p3 = p2;                /* p2 points to beginning of keyword */
3842                 while (isalpha(*p3))    /* Find end of keyword */
3843                   p3++;
3844                 c = *p3;                /* NUL it out so we can look it up */
3845                 if (*p3)                /* p3 points to keyword terminator */
3846                   *p3 = NUL;
3847                 if ((units = lookup(timeunits,p2,nunits,NULL)) < 0)
3848                   return(-1);
3849                 *p2 = NUL;              /* Re-terminate the number */
3850                 *p3 = c;
3851                 while (*p3 == SP)       /* Point at field after units */
3852                   p3++;
3853                 p2 = p3;
3854                 switch (units) {
3855                   case TU_DAYS:
3856                     ddays = atol(p);
3857                     break;
3858                   default:
3859                     return(-1);
3860                 }
3861                 if (*p2) {
3862                     state = NEED_HRS;
3863                     p2--;
3864                 } else
3865                   state = 0;
3866             } else {                    /* Anything else */
3867                 state = -1;             /* is an error */
3868             }
3869             break;
3870           case NEED_HRS:                /* Looking for hours */
3871             if (c == ':') {
3872                 dhours = atoi(p);
3873                 state = NEED_MINS;
3874             } else if (!c) {
3875                 dhours = atoi(p);
3876                 state = 0;
3877             } else {
3878                 state = -1;
3879             }
3880             break;
3881           case NEED_MINS:               /* Looking for minutes */
3882             if (c == ':') {
3883                 dmins = atoi(p);
3884                 state = NEED_SECS;
3885             } else if (!c) {
3886                 dmins = atoi(p);
3887                 state = 0;
3888             } else {
3889                 state = -1;
3890             }
3891             break;
3892           case NEED_SECS:               /* Looking for seconds */
3893             if (c == '.') {
3894                 dsecs = atoi(p);
3895                 state = NEED_FRAC;
3896             } else if (!c) {
3897                 dsecs = atoi(p);
3898                 state = 0;
3899             } else {
3900                 state = -1;
3901             }
3902             break;
3903           case NEED_FRAC:               /* Fraction of second */
3904             if (!c && rdigits(p)) {
3905                 if (*p > '4')
3906                   dsecs++;
3907                 state = 0;
3908             } else {
3909                 state = -1;
3910             }
3911             break;
3912         }
3913         if (c)                          /* next field if any */
3914           p = p2 + 1;
3915     }
3916     if (state < 0)
3917       return(-1);
3918
3919     /* if days > 24854 and sizeof(long) == 32 we overflow */
3920
3921     zz = ddays * 86400L;
3922     if (zz < 0L)                        /* This catches it */
3923       return(-2);
3924     zz += dhours * 3600L + dmins * 60L + dsecs;
3925     zz *= dsign;
3926     *result = zz;
3927     return(0);
3928 }
3929
3930
3931 char *
3932 cmcvtdate(s,t) char * s; int t; {
3933     int x, i, j, k, hh, mm, ss, ff, pmflag = 0, nodate = 0, len, dow;
3934     int units, isgmt = 0, gmtsign = 0, d = 0, state = 0, nday;
3935     int kn = 0, ft[8], isletter = 0, f2len = 0;
3936
3937     int zhh = 0;                        /* Timezone adjustments */
3938     int zmm = 0;
3939     int zdd = 0;
3940
3941     int dsign = 1;                      /* Delta-time adjustments */
3942     int ddays = 0;
3943     int dmonths = 0;
3944     int dyears = 0;
3945     int dhours = 0;
3946     int dmins = 0;
3947     int dsecs = 0;
3948     int havedelta = 0;
3949
3950     char * fld[8], * p = "", * p2, * p3; /* Assorted buffers and pointers  */
3951     char * s2, * s3;
3952     char * year = NULL, * month = NULL, * day = NULL;
3953     char * hour = "00", * min = "00", * sec = "00";
3954     char datesep = 0;
3955     char tmpbuf[8];
3956     char xbuf[DATEBUFLEN+1];
3957     char ybuf[DATEBUFLEN+1];
3958     char zbuf[DATEBUFLEN+1];
3959     char yyyymmdd[YYYYMMDD];
3960     char dbuf[26];
3961     char daybuf[3];
3962     char monbuf[3];
3963     char yearbuf[5];
3964     char timbuf[16], *tb, cc;
3965     char * dp = NULL;                   /* Result pointer */
3966
3967     if (!s) s = "";
3968     tmpbuf[0] = NUL;
3969
3970     while (*s == SP) s++;               /* Gobble any leading blanks */
3971     if (isalpha(*s))                    /* Remember if 1st char is a letter */
3972       isletter = 1;
3973
3974     len = strlen(s);
3975     debug(F110,"cmcvtdate",s,len);
3976     if (len == 0) {                     /* No arg - return current date-time */
3977         dp = ckdate();
3978         goto xcvtdate;
3979     }
3980     if (len > DATEBUFLEN) {             /* Check length of arg */
3981         makestr(&cmdatemsg,"Date-time string too long");
3982         debug(F111,"cmcvtdate",cmdatemsg,-1);
3983         return(NULL);
3984     }
3985     hh = 0;                             /* Init time to 00:00:00.0 */
3986     mm = 0;
3987     ss = 0;
3988     ff = 0;
3989     ztime(&p);
3990     if (!p)
3991       p  = "";
3992     if (*p) {                           /* Init time to current time */
3993         x = ckstrncpy(dbuf,p,26);
3994         if (x > 17) {
3995             hh = atoi(&dbuf[11]);
3996             mm = atoi(&dbuf[14]);
3997             ss = atoi(&dbuf[17]);
3998         }
3999     }
4000     ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD); /* Init date to current date */
4001     ckstrncpy(yearbuf,yyyymmdd,5);
4002     ckstrncpy(monbuf,&yyyymmdd[4],3);
4003     ckstrncpy(daybuf,&yyyymmdd[6],3);
4004     year = yearbuf;
4005     month = monbuf;
4006     day = daybuf;
4007     nday = atoi(daybuf);
4008     ckstrncpy(xbuf,s,DATEBUFLEN);       /* Make a local copy we can poke */
4009     s = xbuf;                           /* Point to it */
4010     s[len] = NUL;
4011     if (s[0] == ':') {
4012         p = s;
4013         goto dotime;
4014     }
4015     /* Special preset formats... */
4016
4017     if (len >= 14) {                    /* FTP MDTM all-numeric date */
4018         char c;
4019         c = s[14];                      /* e.g. 19980615100045.014 */
4020         s[14] = NUL;
4021         x = rdigits(s);
4022         s[14] = c;
4023         if (x) {
4024             ckstrncpy(yyyymmdd,s,8+1);
4025             year = NULL;
4026             p = &s[8];
4027             goto dotime;
4028         }
4029     }
4030     x = 0;                              /* Becomes > 0 for asctime format */
4031     if (isalpha(s[0])) {
4032         if (len == 24) {                /* Asctime format? */
4033             /* Sat Jul 14 15:57:32 2001 */
4034             x = ckmatch(atp1,s,0,0);
4035             debug(F111,"cmcvtdate asctime",s,x);
4036         } else if (len == 28) {         /* Or Asctime plus timezone? */
4037             /* Sat Jul 14 15:15:39 EDT 2001 */
4038             x = ckmatch(atp2,s,0,0);
4039             debug(F111,"cmcvtdate asctime+timezone",s,x);
4040         }
4041     }
4042     if (x > 0) {                        /* Asctime format */
4043         int xx;
4044         strncpy(yearbuf,s + len - 4,4);
4045         yearbuf[4] = NUL;
4046         for (i = 0; i < 3; i++)
4047           tmpbuf[i] = s[i+4];
4048         tmpbuf[3] = NUL;
4049         if ((xx = lookup(cmonths,tmpbuf,12,NULL)) < 0) {
4050             makestr(&cmdatemsg,"Invalid month");
4051             debug(F111,"cmcvtdate",cmdatemsg,-1);
4052             return(NULL);
4053         }
4054         debug(F101,"cmcvtdate asctime month","",xx);
4055         monbuf[0] = (xx / 10) + '0'; 
4056         monbuf[1] = (xx % 10) + '0'; 
4057         monbuf[2] = NUL;
4058         daybuf[0] = (s[8] == ' ' ? '0' : s[8]);
4059         daybuf[1] = s[9];
4060         daybuf[2] = NUL;
4061         xbuf[0] = SP;
4062         for (i = 11; i < 19; i++)
4063           xbuf[i-10] = s[i];
4064         xbuf[9] = NUL;
4065         ckmakmsg(zbuf,18,yearbuf,monbuf,daybuf,xbuf);
4066         debug(F110,"cmcvtdate asctime ok",zbuf,0);
4067         if (len == 24) {
4068             dp = zbuf;
4069             goto xcvtdate;
4070         } else {
4071             int n;
4072             n = ckmakmsg(ybuf,DATEBUFLEN-4,zbuf," ",NULL,NULL);
4073             ybuf[n++] = s[20];
4074             ybuf[n++] = s[21];
4075             ybuf[n++] = s[22];
4076             ybuf[n++] = NUL;
4077             ckstrncpy(xbuf,ybuf,DATEBUFLEN);
4078             s = xbuf;
4079             isletter = 0;
4080         }
4081     }
4082
4083 /* Check for day of week */
4084
4085     p = s;
4086     while (*p == SP) p++;
4087     dow = -1;
4088     if (*p) {
4089         p2 = p;
4090         cc = NUL;
4091         while (1) {
4092             if (*p2 == ',' || *p2 == SP || !*p2) {
4093                 cc = *p2;               /* Save break char */
4094                 *p2 = NUL;              /* NUL it out */
4095                 p3 = p2;                /* Remember this spot */
4096                 if ((dow = lookup(daysofweek,p,7,NULL)) > -1) {
4097                     debug(F111,"cmcvtdate dow",p,dow);
4098                     s = p2;
4099                     if (cc == ',' || cc == SP) { /* Point to next field */
4100                         s++;
4101                         while (*s == SP) s++;
4102                     }
4103                     p = s;
4104                     debug(F111,"cmcvtdate dow new p",p,dow);
4105                     break;
4106                 } else if (isalpha(*p) && cc == ',') {
4107                     makestr(&cmdatemsg,"Unrecognized day of week");
4108                     debug(F111,"cmcvtdate",cmdatemsg,-1);
4109                     return(NULL);
4110                 } else {
4111                     *p3 = cc;
4112                     break;
4113                 }
4114             }
4115             p2++;
4116         }
4117     }
4118     len = strlen(s);            /* Update length */
4119     debug(F111,"cmcvtdate s",s,len);
4120
4121     debug(F111,"cmcvtdate dow",s,dow);
4122     if (dow > -1) {                     /* Have a day-of-week number */
4123         long zz; int n, j;
4124         zz = mjd(zzndate());            /* Get today's MJD */
4125         debug(F111,"cmcvtdate zz","",zz);
4126         j = (((int)(zz % 7L)) + 3) % 7; /* Today's day-of-week number */
4127         debug(F111,"cmcvtdate j","",j);
4128         hh = 0;                         /* Init time to midnight */
4129         mm = 0;
4130         ss = 0;
4131         if (j == dow) {
4132             ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD);
4133             year = NULL;
4134         } else {
4135             n = dow - j;                /* Days from now */
4136             if (dow < j)
4137               n += 7;
4138             if (n < 0) n += 7;          /* Add to MJD */
4139             zz += n;
4140             ckstrncpy(yyyymmdd,mjd2date(zz),YYYYMMDD); /* New date */
4141             year = NULL;
4142         }
4143         debug(F111,"cmcvtdate A",yyyymmdd,len);
4144         if (len == 0) {                 /* No more fields after this */
4145             ckmakmsg(zbuf,18,yyyymmdd," 00:00:00",NULL,NULL);
4146             dp = zbuf;
4147             goto xcvtdate;
4148         }
4149         isletter = 0;
4150         if (rdigits(p) && len < 8)      /* Next field is time? */
4151           goto dotime;                  /* If so go straight to time section */
4152         if (isdigit(*p)) {
4153             if (*(p+1) == ':')
4154               goto dotime;
4155             else if (isdigit(*(p+1)) && (*(p+2) == ':'))
4156               goto dotime;
4157         }
4158     }
4159     debug(F111,"cmcvtdate B s",s,dow);
4160     debug(F111,"cmcvtdate B p",p,dow);
4161
4162     if (*s == '+' || *s == '-') {       /* Delta time only - skip ahead. */
4163         p = s;
4164         goto delta;
4165     }
4166     if (dow > -1) {
4167         /* Day of week given followed by something that is not a time */
4168         /* or a delta so it can't be valid */
4169         makestr(&cmdatemsg,"Invalid tokens after day of week");
4170         debug(F111,"cmcvtdate fail",cmdatemsg,-1);
4171         return(NULL);
4172     }
4173
4174     /* Handle "today", "yesterday", "tomorrow", and +/- n units */
4175
4176     if (ckstrchr("TtYyNn",s[0])) {
4177         int i, k, n, minus = 0;
4178         char c;
4179         long jd;
4180         jd = mjd(ckdate());
4181         debug(F111,"cmcvtdate mjd",s,jd);
4182
4183         /* Symbolic date: TODAY, TOMORROW, etc...? */
4184
4185         s2 = s;                         /* Find end of keyword */
4186         i = 0;
4187         while (isalpha(*s2)) {          /* and get its length */
4188             i++;
4189             s2++;
4190         }
4191         c = *s2;                        /* Zap but save delimiter */
4192         *s2 = NUL;
4193         k = lookup(symdaytab,s,nsymdays,NULL); /* Look up keyword */
4194         *s2 = c;                        /* Replace delimiter */
4195         if (k < 0)                      /* Keyword not found */
4196           goto normal;
4197         s3 = &s[i];
4198         while (*s3 == SP)               /* Skip whitespace */
4199           s3++;
4200         if (*s3 == '_' || *s3 == ':')
4201           s3++;
4202
4203         switch (k) {                    /* Have keyword */
4204           case SYM_NOW:                 /* NOW */
4205             ckstrncpy(ybuf,ckdate(),DATEBUFLEN);
4206             ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4207             year = NULL;
4208             if (*s3) {                  /* No overwriting current time. */
4209                 ckstrncat(ybuf," ",DATEBUFLEN);
4210                 ckstrncat(ybuf,s3,DATEBUFLEN);
4211             }
4212             break;
4213           default:                      /* Yesterday, Today, and Tomorrow */
4214             if (k == SYM_TOMO) {        /* TOMORROW */
4215                 strncpy(ybuf,mjd2date(jd+1),8);
4216             } else if (k == SYM_YEST) { /* YESTERDAY */
4217                 strncpy(ybuf,mjd2date(jd-1),8);
4218             } else {                    /* TODAY */
4219                 strncpy(ybuf,ckdate(),8);
4220             }
4221             strncpy(ybuf+8," 00:00:00",DATEBUFLEN-8); /* Default time is 0 */
4222             ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4223             year = NULL;
4224             if (*s3) {                  /* If something follows keyword... */
4225                 if (isdigit(*s3)) {     /* Time - overwrite default time */
4226                     strncpy(ybuf+8,s+i,DATEBUFLEN-8);
4227                 } else {                /* Something else, keep default time */
4228                     ckstrncat(ybuf," ",DATEBUFLEN); /* and append */
4229                     ckstrncat(ybuf,s3,DATEBUFLEN); /* whatever we have */
4230                 }
4231             }
4232         }
4233         s = ybuf;                       /* Point to rewritten date-time */
4234         len = strlen(s);                /* Update length */
4235         isletter = 0;                   /* Cancel this */
4236     }
4237
4238 /* Regular free-format non-symbolic date */
4239
4240   normal:
4241
4242     debug(F111,"cmcvtdate NORMAL",s,len);
4243     debug(F111,"cmcvtdate dow",s,dow);
4244     if (yyyymmdd[0] && !year) {
4245         ckstrncpy(yearbuf,yyyymmdd,5);
4246         ckstrncpy(monbuf,&yyyymmdd[4],3);
4247         ckstrncpy(daybuf,&yyyymmdd[6],3);
4248         year = yearbuf;
4249         month = monbuf;
4250         day = daybuf;
4251         nday = atoi(daybuf);
4252     }
4253     if (isdigit(s[0])) {                /* Time without date? */
4254         p = s;
4255         if (s[1] == ':') {
4256             debug(F111,"cmcvtdate NORMAL X1",s,len);
4257             goto dotime;
4258         } else if (len > 1 && isdigit(s[1]) && s[2] == ':') {
4259             debug(F111,"cmcvtdate NORMAL X2",s,len);
4260             goto dotime;
4261         } else if (rdigits(s) && len < 8) {
4262             debug(F111,"cmcvtdate NORMAL X3",s,len);
4263             goto dotime;
4264         }
4265     }
4266     if (len >= 8 && isdigit(*s)) {      /* Check first for yyyymmdd* */
4267         debug(F111,"cmcvtdate NORMAL A",s,len);
4268         cc = s[8];
4269         s[8] = NUL;                     /* Isolate first 8 characters */
4270         if (rdigits(s)) {
4271             /* Have valid time separator? */
4272             p2 = cc ? ckstrchr(" Tt_-:",cc) : NULL;
4273             if (!cc || p2) {
4274                 ckstrncpy(yyyymmdd,s,YYYYMMDD); /* Valid separator */
4275                 year = NULL;
4276                 s += 8;                         /* or time not given */
4277                 if (cc) s++;                    /* Keep date */
4278                 p = s;                          /* and go handle time */
4279                 goto dotime;
4280             } else if (!p2) {
4281                 if (isdigit(cc))
4282                   makestr(&cmdatemsg,"Numeric date too long");
4283                 else
4284                   makestr(&cmdatemsg,"Invalid date-time separator");
4285                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4286                 return(NULL);
4287             }
4288         }
4289         s[8] = cc;                      /* Put this back! */
4290     }
4291     debug(F111,"cmcvtdate NORMAL non-yyyymmdd",s,len);
4292
4293     /* Free-format date -- figure it out */
4294
4295 #ifdef COMMENT
4296     if (*s && !isdigit(*s)) {
4297         makestr(&cmdatemsg,"Unrecognized word in date");
4298         debug(F111,"cmcvtdate",cmdatemsg,-1);
4299         return(NULL);
4300     }
4301 #endif /* COMMENT */
4302     for (i = 0; i < 8; i++)             /* Field types */
4303       ft[i] = -1;
4304     fld[i = 0] = (p = s);               /* First field */
4305     while (*p) {                        /* Get next two fields */
4306         if (isdatesep(*p)) {            /* Have a date separator */
4307             if (i == 0) {
4308                 datesep = *p;
4309             } else if (i == 1 && *p != datesep) {
4310                 makestr(&cmdatemsg,"Inconsistent date separators");
4311                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4312                 return(NULL);
4313             }
4314             *p++ = NUL;                 /* Replace by NUL */
4315             if (*p) {                   /* Now we're at the next field */
4316                 while (*p == SP) p++;   /* Skip leading spaces */
4317                 if (!*p) break;         /* Make sure we still have something */
4318                 if (i == 2)             /* Last one? */
4319                   break;
4320                 fld[++i] = p;           /* No, record pointer to this one */
4321             } else {
4322                 break;
4323             }       
4324         } else if ((*p == 'T' || *p == 't') && isdigit(*(p+1))) { /* Time */
4325             *p++ = NUL;
4326             break;
4327         } else if (*p == ':') {
4328             if (i == 0 && p == s) {
4329                 nodate = 1;
4330                 break;
4331             } else if (i != 0) {        /* After a date */
4332                 if (i == 2) {           /* OK as date-time separator (VMS) */
4333                     *p++ = NUL;
4334                     break;
4335                 }
4336                 if (i < 2)
4337                   makestr(&cmdatemsg,"Too few fields in date");
4338                 else
4339                   makestr(&cmdatemsg,"Misplaced time separator");
4340                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4341                 return(NULL);
4342             }
4343             nodate = 1;                 /* Or without a date */
4344             break;
4345         }
4346         p++;
4347     }
4348     if (p > s && i == 0)                /* Make sure we have a date */
4349       nodate = 1;                       /* No date. */
4350
4351     if (nodate && dow > -1) {           /* Have implied date from DOW? */
4352         goto dotime;                    /* Use, use that, go do time. */
4353
4354     } else if (nodate) {                /* No date and no implied date */
4355         char *tmp = NULL;               /* Substitute today's date */
4356         ztime(&tmp);
4357         if (!tmp)
4358           tmp  = "";
4359         if (!*tmp) {
4360             makestr(&cmdatemsg,"Problem supplying current date");
4361             debug(F111,"cmcvtdate",cmdatemsg,-1);
4362             return(NULL);
4363         }
4364         ckstrncpy(dbuf,tmp,26);         /* Reformat */
4365         if (dbuf[8] == SP) dbuf[8] = '0';
4366         fld[0] = dbuf+8;                /* dd */
4367         dbuf[10] = NUL;
4368         fld[1] = dbuf+4;                /* mmm */
4369         dbuf[7] = NUL;
4370         fld[2] = dbuf+20;               /* yyyy */
4371         dbuf[24] = NUL;
4372         hh = atoi(&dbuf[11]);
4373         mm = atoi(&dbuf[14]);
4374         ss = atoi(&dbuf[17]);
4375         p = s;                          /* Back up source pointer to reparse */
4376     } else if (i < 2) {
4377         makestr(&cmdatemsg,"Too few fields in date");
4378         debug(F111,"cmcvtdate",cmdatemsg,-1);
4379         return(NULL);
4380     }
4381     /* Have three date fields - see what they are */
4382
4383     for (k = 0, j = 0; j < 3; j++) {    /* Get number of non-numeric fields */
4384         ft[j] = rdigits(fld[j]);
4385         debug(F111,"cmcvtdate fld",fld[j],j);
4386         if (ft[j] == 0)
4387           k++;
4388     }
4389     kn = k;                             /* How many numeric fields */
4390     month = NULL;                       /* Strike out default values */
4391     year = NULL;
4392     day = NULL;
4393
4394     if (k == 2 && ft[2] > 0) {          /* Jul 20, 2001 */
4395         int xx;
4396         xx = strlen(fld[1]);
4397         p3 = fld[1];
4398         if (xx > 0) if (p3[xx-1] == ',') {
4399             p3[xx-1] = NUL;
4400             if (rdigits(p3)) {
4401                 k = 1;  
4402                 ft[1] = 1;
4403             } else p3[xx-1] = ',';
4404         }
4405     }
4406     if (k > 1) {                        /* We can have only one non-numeric */
4407         if (nodate)
4408           makestr(&cmdatemsg,"Unrecognized word in date"); 
4409         else if (!ft[2] && isdigit(*(fld[2])))
4410           makestr(&cmdatemsg,"Invalid date-time separator"); 
4411         else
4412           makestr(&cmdatemsg,"Too many non-numeric fields in date");
4413         debug(F111,"cmcvtdate",cmdatemsg,-1);
4414         return(NULL);
4415     }
4416     if (!ft[0]) {
4417         k = 0;
4418     } else if (!ft[1]) {
4419         k = 1;
4420     } else if (!ft[2]) {
4421         makestr(&cmdatemsg,"Non-digit in third date field");
4422         debug(F111,"cmcvtdate",cmdatemsg,-1);
4423         return(NULL);
4424     } else
4425       k = -1;
4426
4427     if (k > -1) {
4428         if ((x = lookup(cmonths,fld[k],12,NULL)) < 0) {
4429             makestr(&cmdatemsg,"Unknown month");
4430             debug(F111,"cmcvtdate",cmdatemsg,-1);
4431             return(NULL);
4432         }
4433         sprintf(tmpbuf,"%02d",x);
4434         month = tmpbuf;
4435     }
4436     f2len = strlen(fld[2]);             /* Length of 3rd field */
4437
4438     if (k == 0) {                       /* monthname dd, yyyy */
4439         day = fld[1];
4440         year = fld[2];
4441     } else if (((int)strlen(fld[0]) == 4)) { /* yyyy-xx-dd */
4442         year = fld[0];
4443         day = fld[2];
4444         if (!month)
4445           month = fld[1];               /* yyyy-mm-dd */
4446     } else if (f2len == 4) {            /* xx-xx-yyyy */
4447         year = fld[2];
4448         if (month) {                    /* dd-name-yyyy */
4449             day = fld[0];
4450         } else {                        /* xx-xx-yyyy */
4451             int f0, f1;
4452             f0 = atoi(fld[0]);
4453             f1 = atoi(fld[1]);
4454             if (((f0 > 12) && (f1 <= 12)) || (f1 <= 12 && f0 == f1)) {
4455                 day = fld[0];           /* mm-dd-yyyy */
4456                 month = fld[1];
4457             } else if ((f0 <= 12) && (f1 > 12)) {
4458                 if (!rdigits(fld[1])) {
4459                     makestr(&cmdatemsg,"Day not numeric");
4460                     debug(F111,"cmcvtdate",cmdatemsg,-1);
4461                     return(NULL);
4462                 } else {
4463                     day = fld[1];       /* dd-mm-yyyy */
4464                 }
4465                 month = fld[0];
4466             } else {
4467                 if (!f0 || !f1)
4468                   makestr(&cmdatemsg,"Day or month out of range");
4469                 else
4470                   makestr(&cmdatemsg,"Day and month are ambiguous");
4471                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4472                 return(NULL);
4473             }
4474         }
4475     } else if ((f2len < 4) &&           /* dd mmm yy (RFC822) */
4476                !rdigits(fld[1]) &&      /* middle field is monthname */
4477                rdigits(fld[2])) {
4478         int tmpyear;
4479         day = fld[0];
4480         if (!fld[2][1]) {
4481             makestr(&cmdatemsg,"Too few digits in year");
4482             debug(F111,"cmcvtdate",cmdatemsg,-1);
4483             return(NULL);
4484         }
4485         tmpyear = atoi(fld[2]);
4486         if (tmpyear < 50)               /* RFC 2822 windowing */
4487           tmpyear += 2000;
4488         else                            /* This includes 3-digit years. */
4489           tmpyear += 1900;
4490         year = ckitoa(tmpyear);
4491
4492     } else if ((f2len < 4) && (k < 0) && ((int)strlen(fld[0]) < 4)) {
4493         makestr(&cmdatemsg,"Ambiguous numeric date");
4494         debug(F111,"cmcvtdate",cmdatemsg,-1);
4495         return(NULL);
4496     } else if ((f2len > 4) && ft[2]) {
4497         makestr(&cmdatemsg,"Too many digits in year");
4498         debug(F111,"cmcvtdate",cmdatemsg,-1);
4499         return(NULL);
4500     } else {
4501         makestr(&cmdatemsg,"Unexpected date format");
4502         debug(F111,"cmcvtdate",cmdatemsg,-1);
4503         return(NULL);
4504     }
4505     x = atoi(month);
4506     sprintf(tmpbuf,"%02d",x);           /* 2-digit numeric month */
4507
4508 /*
4509    state = 1 = hours
4510    state = 2 = minutes
4511    state = 3 = seconds
4512    state = 4 = fractions of seconds
4513 */
4514
4515   dotime:
4516     if (isletter && (s == p)) {
4517         makestr(&cmdatemsg,"Unknown date-time word");
4518         debug(F111,"cmcvtdate",cmdatemsg,-1);
4519         return(NULL);
4520     }
4521     if (!year && yyyymmdd[0]) {
4522         debug(F110,"cmcvtdate dotime yyyymmdd",yyyymmdd,0);
4523         for (i = 0; i < 4; i++)
4524           yearbuf[i] = yyyymmdd[i];
4525         yearbuf[4] = NUL;
4526         monbuf[0] = yyyymmdd[4];
4527         monbuf[1] = yyyymmdd[5];
4528         monbuf[2] = NUL;
4529         daybuf[0] = yyyymmdd[6];
4530         daybuf[1] = yyyymmdd[7];
4531         daybuf[2] = NUL;
4532         day = daybuf;
4533         nday = atoi(daybuf);
4534         month = monbuf;
4535         year = yearbuf;
4536     }
4537     if (!year) {
4538         makestr(&cmdatemsg,"Internal error - date not defaulted");
4539         debug(F111,"cmcvtdate",cmdatemsg,-1);
4540         return(NULL);
4541     }
4542     /* Get here with day, month, and year set */
4543     debug(F110,"cmcvtdate dotime day",day,0);
4544     debug(F110,"cmcvtdate dotime month",month,0);
4545     debug(F110,"cmcvtdate dotime year",year,0);
4546     debug(F110,"cmcvtdate dotime s",s,0);
4547     debug(F110,"cmcvtdate dotime p",p,0);
4548     x = atoi(month);
4549     if (x > 12 || x < 1) {
4550         makestr(&cmdatemsg,"Month out of range");
4551         debug(F111,"cmcvtdate",cmdatemsg,-1);
4552         return(NULL);
4553     }
4554     nday  = atoi(day);
4555     i = mdays[x];
4556     if (x == 2) if (isleap(atoi(year))) i++;
4557     if (nday > i || nday < 1) {
4558         makestr(&cmdatemsg,"Day out of range");
4559         debug(F111,"cmcvtdate",cmdatemsg,-1);
4560         return(NULL);
4561     }
4562     if (!*p && t == 0) {
4563         sprintf(zbuf,"%04d%02d%02d",atoi(year),atoi(month),nday);       
4564         dp = zbuf;
4565         goto xcvtdate;
4566     }
4567     if (*p == '+' || *p == '-') {       /* GMT offset without a time */
4568         hh = 0;                         /* so default time to 00:00:00 */
4569         mm = 0;
4570         ss = 0;
4571         goto cmtimezone;                /* and go do timezone */
4572     }
4573     if (*p && !isdigit(*p) && *p != ':') {
4574         makestr(&cmdatemsg,"Invalid time");
4575         debug(F111,"cmcvtdate",cmdatemsg,-1);
4576         return(NULL);
4577     }
4578     sprintf(yyyymmdd,"%s%s%02d",year,month,nday); /* for tz calculations... */
4579
4580     state = 1;                          /* Initialize time-parsing FSA */
4581     hh = 0;                             /* hours */
4582     mm = 0;                             /* minutes */
4583     ss = 0;                             /* seconds */
4584     ff = -1;                            /* fraction */
4585     d = 0;                              /* Digit counter */
4586     p2 = p;                             /* Preliminary digit count... */
4587     while (isdigit(*p2)) {
4588         d++;
4589         p2++;
4590     }
4591     if (d > 6) {
4592         makestr(&cmdatemsg,"Too many time digits");
4593         debug(F111,"cmcvtdate",cmdatemsg,-1);
4594         return(NULL);
4595     }
4596     d = (d & 1 && *p2 != ':') ? 1 : 0;  /* Odd implies leading '0' */
4597
4598     while (*p) {                        /* Get the time, if any */
4599         if (isdigit(*p)) {              /* digit */
4600             if (d++ > 1) {
4601                 state++;
4602                 d = 1;
4603             }
4604             switch (state) {
4605               case 1:                   /* Hours */
4606                 hh = hh * 10 + (*p - '0');
4607                 break;
4608               case 2:                   /* Minutes */
4609                 mm = mm * 10 + (*p - '0');
4610                 break;
4611               case 3:                   /* Seconds */
4612                 ss = ss * 10 + (*p - '0');
4613                 break;
4614               case 4:                   /* Fraction of second */
4615                 if (ff < 0)
4616                   ff = (*p > '4') ? 1 : 0;
4617                 break;
4618             }
4619         } else if (*p == ':') {         /* Colon */
4620             state++;
4621             d = 0;
4622             if (state > 3) {
4623                 makestr(&cmdatemsg,"Too many time fields");
4624                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4625                 return(NULL);
4626             }
4627         } else if (*p == '.') {
4628             if (state == 3) {
4629                 state = 4;
4630                 d = 0;
4631             } else {
4632                 makestr(&cmdatemsg,"Improper fraction");
4633                 debug(F111,"cmcvtdate",cmdatemsg,-1);
4634                 return(NULL);
4635             }
4636         } else if (*p == SP) {          /* Space */
4637             while (*p && (*p == SP))    /* position to first nonspace */
4638               p++;
4639             break;
4640         } else if (isalpha(*p)) {       /* AM/PM/Z or timezone */
4641             break;
4642         } else if (*p == '+' || *p == '-') { /* GMT offset */
4643             break;
4644         } else {
4645             makestr(&cmdatemsg,"Invalid time characters");
4646             debug(F111,"cmcvtdate",cmdatemsg,-1);
4647             return(NULL);
4648         }
4649         p++;
4650     }
4651     if (!*p)                            /* If nothing left */
4652       goto xcmdate;                     /* go finish up */
4653
4654     /* At this point we have HH, MM, SS, and FF */
4655     /* Now handle the rest: AM, PM, and/or timezone info */
4656
4657     if (!ckstrcmp(p,"am",2,0)) {        /* AM/PM... */
4658         pmflag = 0;
4659         p += 2;
4660     } else if (!ckstrcmp(p,"a.m.",4,0)) {
4661         pmflag = 0;
4662         p += 4;
4663     } else if (!ckstrcmp(p,"pm",2,0)) {
4664         pmflag = 1;
4665         p += 2;
4666     } else if (!ckstrcmp(p,"p.m.",4,0)) {
4667         pmflag = 1;
4668         p += 4;
4669     }
4670     if (pmflag && hh < 12)              /* If PM was given */
4671       hh += 12;                         /* add 12 to the hour */
4672
4673     /* Now handle timezone */
4674
4675   cmtimezone:
4676     debug(F110,"cmcvtdate timezone",p,0);
4677
4678     zhh = 0;                            /* GMT offset HH */
4679     zmm = 0;                            /* GMT offset MM */
4680     gmtsign = 0;                        /* Sign of GMT offset */
4681     isgmt = 0;                          /* 1 if time is GMT */
4682
4683     while (*p && *p == SP)              /* Gobble spaces */
4684       p++;
4685     if (!*p)                            /* If nothing left */
4686       goto xcmdate;                     /* we're done */
4687
4688     if (isalpha(*p)) {                  /* Something left */
4689         int zone = 0;                   /* Alphabetic must be timezone */
4690         p2 = p;                         /* Isolate timezone */
4691         p++;
4692         while (isalpha(*p))
4693           p++;
4694         p3 = p;
4695         cc = *p;
4696         *p = NUL;
4697         p = p2;                         /* Have timezone, look it up */
4698         zone = lookup(usatz,p,nusatz,NULL);
4699         debug(F111,"cmcvtdate timezone alpha",p,zone);
4700
4701         if (zone < 0) {                 /* Not found */
4702             makestr(&cmdatemsg,"Unknown timezone");
4703             debug(F111,"cmcvtdate",cmdatemsg,-1);
4704             return(NULL);
4705         }
4706         isgmt++;                        /* All dates are GMT from here down */
4707         if (zone != 0) {                /* But not this one so make it GMT */
4708             hh += zone;                 /* RFC 822 timezone: EST etc */
4709             if (hh > 23) {              /* Offset crosses date boundary */
4710                 long jd;
4711                 jd = mjd(yyyymmdd);     /* Get MJD */
4712                 jd += hh / 24;          /* Add new day(s) */
4713                 hh = hh % 24;           /* and convert back to yyyymmdd */
4714                 ckstrncpy(yyyymmdd,mjd2date(jd),YYYYMMDD);
4715             }
4716         }
4717         p = p3;                         /* Put back whatever we poked above */
4718         *p = cc;
4719
4720     } else if (*p == '+' || *p == '-') { /* GMT/UTC offset */
4721         p3 = p;
4722         debug(F110,"cmcvtdate timezone GMT offset",p,0);
4723         gmtsign = (*p == '+') ? -1 : 1;
4724         isgmt++;
4725         p++;
4726         while (*p == SP) p++;
4727         d = 0;
4728         p2 = p;
4729         while (isdigit(*p)) {           /* Count digits */
4730             d++;
4731             p++;
4732         }
4733         if (d != 4) {                   /* Strict RFC [2]822 */
4734             isgmt = 0;                  /* If not exactly 4 digits */
4735             p = p3;                     /* it's not a GMT offset. */
4736             goto delta;                 /* So treat it as a delta time. */
4737         }
4738         d = (d & 1 && *p != ':') ? 1 : 0; /* Odd implies leading '0' */
4739         p = p2;
4740         debug(F111,"cmcvtdate GMT offset sign",p,gmtsign);
4741         debug(F101,"cmcvtdate GMT offset d","",d);
4742         state = 1;
4743         while (*p) {
4744             if (isdigit(*p)) {          /* digit */
4745                 if (d++ > 1) {
4746                     state++;
4747                     d = 1;
4748                 }
4749                 switch (state) {
4750                   case 1:
4751                     zhh = zhh * 10 + (*p - '0');
4752                     break;
4753                   case 2:
4754                     zmm = zmm * 10 + (*p - '0');
4755                     break;
4756                   default:              /* Ignore seconds or fractions */
4757                     break;
4758                 }                       
4759             } else if (*p == ':') {     /* Colon */
4760                 state++;
4761                 d = 0;
4762             } else if (*p == SP || *p == '(') {
4763                 break;
4764             } else {
4765                 p = p3;                 /* Maybe it's not a GMT offset. */
4766                 goto delta;             /* So treat it as a delta time. */
4767             }
4768             p++;
4769         }
4770     }
4771     debug(F110,"cmcvtdate after timezone",p,0);
4772
4773     if (*p) {                           /* Anything left? */
4774         p2 = p;
4775         while (*p2 == SP)               /* Skip past spaces */
4776           p2++;
4777         if (*p2 == '(') {               /* RFC-822 comment? */
4778             int pc = 1;                 /* paren counter */
4779             p2++;
4780             while (*p2) {
4781                 if (*p2 == ')') {
4782                     if (--pc == 0) {
4783                         p2++;
4784                         break;
4785                     }
4786                 } else if (*p2 == ')') {
4787                     pc++;
4788                 }
4789                 p2++;
4790             }           
4791             while (*p2 == SP)           /* Skip past spaces */
4792               p2++;
4793             if (!*p2)                   /* Anything left? */
4794               *p = NUL;                 /* No, erase comment */
4795         }
4796         if (!*p2)                       /* Anything left? */
4797           goto xcmdate;                 /* No, done. */
4798         p = p2;
4799
4800       delta:
4801         debug(F110,"cmcvtdate delta yyyymmdd",yyyymmdd,0);
4802         debug(F110,"cmcvtdate delta year",year,0);
4803         debug(F110,"cmcvtdate delta p",p,0);
4804
4805         if (*p == '+' || *p == '-') {   /* Delta time */
4806             int state = NEED_DAYS;      /* Start off looking for days */
4807             char c = 0;
4808             dsign = 1;                  /* Get sign */
4809             if (*p++ == '-')
4810               dsign = -1;
4811             while (*p == SP)            /* Skip intervening spaces */
4812               p++;
4813             while (state) {             /* FSA to parse delta time */
4814                 if (state < 0 || !isdigit(*p)) {
4815                     makestr(&cmdatemsg,"Invalid delta time");
4816                     debug(F111,"cmcvtdate",cmdatemsg,-1);
4817                     return(NULL);
4818                 }
4819                 p2 = p;                 /* Get next numeric field */
4820                 while (isdigit(*p2))
4821                   p2++;
4822                 c = *p2;                /* And break character */
4823                 *p2 = NUL;              /* Terminate the number */
4824
4825                 switch (state) {        /* Interpret according to state */
4826                   case NEED_DAYS:       /* Initial */
4827                     if ((c == '-') ||   /* VMS format */
4828                         ((c == 'd' || c == 'D')
4829                          && !isalpha(*(p2+1)))) { /* Days */
4830                         ddays = atoi(p);
4831                         if (!*(p2+1))                   
4832                           state = 0;
4833                         else                  /* if anything is left */
4834                           state = NEED_HRS;   /* now we want hours. */
4835                     } else if ((c == 'W' || c == 'w') && !isalpha(*(p2+1))) {
4836                         ddays = atoi(p) * 7;   /* weeks... */
4837                         if (!*(p2+1))                   
4838                           state = 0;
4839                         else
4840                           state = NEED_HRS;
4841                     } else if ((c == 'M' || c == 'm') && !isalpha(*(p2+1))) {
4842                         dmonths = atoi(p); /* months... */
4843                         if (!*(p2+1))                   
4844                           state = 0;
4845                         else
4846                           state = NEED_HRS;
4847                     } else if ((c == 'Y' || c == 'y') && !isalpha(*(p2+1))) {
4848                         dyears = atoi(p); /* years... */
4849                         if (!*(p2+1))                   
4850                           state = 0;
4851                         else
4852                           state = NEED_HRS;
4853                     } else if (c == ':') { /* delimiter is colon */
4854                         dhours = atoi(p);  /* so it's hours */
4855                         state = NEED_MINS; /* now we want minutes */
4856                     } else if (!c) {       /* end of string */
4857                         dhours = atoi(p);  /* it's still hours */
4858                         state = 0;         /* and we're done */
4859                     } else if (isalpha(c) || c == SP) {
4860                         if (c == SP) {  /* It's a keyword? */
4861                             p2++;       /* Skip spaces */
4862                             while (*p2 == SP)
4863                               p2++;
4864                         } else {        /* or replace first letter */
4865                             *p2 = c;
4866                         }
4867                         p3 = p2;        /* p2 points to beginning of keyword */
4868                         while (isalpha(*p3)) /* Find end of keyword */
4869                           p3++;
4870                         c = *p3;        /* NUL it out so we can look it up */
4871                         if (*p3)        /* p3 points to keyword terminator */
4872                           *p3 = NUL;
4873                         units = lookup(timeunits,p2,nunits,NULL);
4874                         if (units < 0) {
4875                             makestr(&cmdatemsg,"Invalid units in delta time");
4876                             debug(F111,"cmcvtdate",cmdatemsg,-1);
4877                             return(NULL);
4878                         }
4879                         *p2 = NUL;      /* Re-terminate the number */
4880                         *p3 = c;
4881                         while (*p3 == SP) /* Point at field after units */
4882                           p3++;
4883                         p2 = p3;
4884                         switch (units) {
4885                           case TU_DAYS:
4886                             ddays = atoi(p);
4887                             break;
4888                           case TU_WEEKS:
4889                             ddays = atoi(p) * 7;
4890                             break;
4891                           case TU_MONTHS:
4892                             dmonths = atoi(p);
4893                             break;
4894                           case TU_YEARS:
4895                             dyears = atoi(p);
4896                             break;
4897                         }
4898                         if (*p2) {
4899                             state = NEED_HRS;
4900                             p2--;
4901                         } else
4902                           state = 0;
4903
4904                     } else {            /* Anything else */
4905                         state = -1;     /* is an error */
4906                     }
4907                     break;
4908                   case NEED_HRS:        /* Looking for hours */
4909                     debug(F000,"cmcvtdate NEED_HRS",p,c);
4910                     if (c == ':') {
4911                         dhours = atoi(p);
4912                         state = NEED_MINS;
4913                     } else if (!c) {
4914                         dhours = atoi(p);
4915                         state = 0;
4916                     } else {
4917                         state = -1;
4918                     }
4919                     break;
4920                   case NEED_MINS:       /* Looking for minutes */
4921                     if (c == ':') {
4922                         dmins = atoi(p);
4923                         state = NEED_SECS;
4924                     } else if (!c) {
4925                         dmins = atoi(p);
4926                         state = 0;
4927                     } else {
4928                         state = -1;
4929                     }
4930                     break;
4931                   case NEED_SECS:       /* Looking for seconds */
4932                     if (c == '.') {
4933                         dsecs = atoi(p);
4934                         state = NEED_FRAC;
4935                     } else if (!c) {
4936                         dsecs = atoi(p);
4937                         state = 0;
4938                     } else {
4939                         state = -1;
4940                     }
4941                     break;
4942                   case NEED_FRAC:       /* Fraction of second */
4943                     if (!c && rdigits(p)) {
4944                         if (*p > '4')
4945                           dsecs++;
4946                         state = 0;
4947                     } else {
4948                         state = -1;
4949                     }
4950                     break;
4951                 }
4952                 if (c)                  /* next field if any */
4953                   p = p2 + 1;
4954             }
4955             havedelta = 1;
4956
4957         } else {
4958             makestr(&cmdatemsg,"Extraneous material at end");
4959             debug(F111,"cmcvtdate",cmdatemsg,-1);
4960             return(NULL);
4961         }
4962     }
4963
4964  xcmdate:
4965
4966     if ((t != 2 && hh > 24) || hh < 0) { /* Hour range check */
4967         makestr(&cmdatemsg,"Invalid hours");
4968         debug(F111,"cmcvtdate",cmdatemsg,-1);
4969         return(NULL);
4970     }
4971     if (mm > 59) {                      /* Minute range check */
4972         makestr(&cmdatemsg,"Invalid minutes");
4973         debug(F111,"cmcvtdate",cmdatemsg,-1);
4974         return(NULL);
4975     }
4976     if (ff > 0) {                       /* Fraction of second? */
4977         if (ss < 59) {
4978             ss++;
4979             ff = 0;
4980         } else if (mm < 59) {
4981             ss = 0;
4982             mm++;
4983             ff = 0;
4984         } else if (hh < 24) {
4985             ss = 0;
4986             mm = 0;
4987             hh++;
4988             ff = 0;
4989         }
4990         /* Must add a day -- leave ff at 1... */
4991         /* (DO SOMETHING ABOUT THIS LATER) */
4992     }
4993     if (ss > 60) {                      /* Seconds range check */
4994         makestr(&cmdatemsg,"Invalid seconds"); /* 60 is ok because of */
4995         debug(F111,"cmcvtdate",cmdatemsg,-1);  /* Leap Second. */
4996         return(NULL);
4997     }
4998     if ((mm < 0 || ss < 0) ||
4999         (t != 2 && (ss > 0 || mm > 0) && hh > 23)) {
5000         makestr(&cmdatemsg,"Invalid minutes or seconds");
5001         debug(F111,"cmcvtdate",cmdatemsg,-1);
5002         return(NULL);
5003     }
5004     debug(F110,"cmcvtdate year",year,0);
5005     debug(F110,"cmcvtdate month",month,0);
5006     debug(F101,"cmcvtdate nday","",nday);
5007     debug(F101,"cmcvtdate hh","",hh);
5008     debug(F101,"cmcvtdate mm","",mm);
5009     debug(F101,"cmcvtdate ss","",ss);
5010     debug(F101,"cmcvtdate gmtsign","",gmtsign);
5011     debug(F101,"cmcvtdate zhh","",zhh);
5012     debug(F101,"cmcvtdate zmm","",zmm);
5013     debug(F101,"cmcvtdate isgmt","",isgmt);
5014
5015 #ifdef ZLOCALTIME
5016 /* Handle timezone -- first convert to GMT */
5017
5018     zdd = 0;                            /* Days changed */
5019     if (isgmt && (zmm || zhh)) {        /* If GMT offset given */
5020         long sec1, sec2, zz;
5021         sec1 = ss + 60 * mm + 3600 * hh;
5022         sec2 = gmtsign * (60 * zmm + 3600 * zhh);
5023         sec1 += sec2;
5024         if (sec1 < 0) {
5025             sec1 = 0 - sec1;
5026             zdd = 0L - (sec1 / 86400L);
5027             sec1 = sec1 % 86400L;
5028         } else if (sec1 > 86400L) {
5029             zdd = sec1 / 86400L;
5030             sec1 = sec1 % 86400L;
5031         }
5032         ss = sec1 % 60;
5033         zz = sec1 / 60;
5034         mm = zz % 60;
5035         hh = zz / 60;
5036         debug(F101,"cmcvtdate NEW hh","",hh);
5037         debug(F101,"cmcvtdate NEW mm","",mm);
5038         debug(F101,"cmcvtdate NEW dd","",zdd);
5039
5040 /* At this point hh:mm:ss is in GMT and zdd is the calendar adjustment */
5041
5042     }
5043 #endif /* ZLOCALTIME */
5044
5045     if (yyyymmdd[0] && !year) {
5046         ckstrncpy(yearbuf,yyyymmdd,5);
5047         ckstrncpy(monbuf,&yyyymmdd[4],3);
5048         ckstrncpy(daybuf,&yyyymmdd[6],3);
5049         year = yearbuf;
5050         month = monbuf;
5051         day = daybuf;
5052         nday = atoi(daybuf);
5053     }
5054     sprintf(zbuf,"%04d%02d%02d %02d:%02d:%02d", /* SAFE */
5055             atoi(year),atoi(month),nday,hh,mm,ss
5056             );
5057     dp = zbuf;
5058
5059 #ifdef ZLOCALTIME
5060     /* Now convert from GMT to local time */
5061
5062     if (isgmt) {                        /* If GMT convert to local time */
5063         debug(F110,"cmcvtdate GMT 1",dp,0);
5064         if (zdd) {                      /* Apply any calendar adjustment */
5065             long zz;
5066             zz = mjd(dp) + zdd;
5067             sprintf(zbuf,"%s %02d:%02d:%02d",mjd2date(zz),hh,mm,ss);
5068         }
5069         debug(F110,"cmcvtdate GMT 2",dp,0);
5070         if ((p = zlocaltime(dp))) {
5071             debug(F110,"cmcvtdate asctime zlocaltime",p,0);
5072             if (p) ckstrncpy(zbuf,p,18);
5073         }
5074         debug(F110,"cmcvtdate GMT 3",dp,0);
5075         for (i = 0; i < 4; i++)
5076           yearbuf[i] = dp[i];
5077         yearbuf[4] = NUL;
5078         monbuf[0] = dp[4];
5079         monbuf[1] = dp[5];
5080         monbuf[2] = NUL;
5081         daybuf[0] = dp[6];
5082         daybuf[1] = dp[7];
5083         daybuf[2] = NUL;
5084         day = daybuf;
5085         nday = atoi(daybuf);
5086         month = monbuf;
5087         year = yearbuf;
5088         hh = atoi(&dp[9]);
5089         mm = atoi(&dp[12]);
5090         ss = atoi(&dp[15]);
5091     }
5092 #endif /* ZLOCALTIME */
5093
5094 #ifdef DEBUG
5095     if (deblog) {
5096         debug(F101,"cmcvtdate hour","",hh);
5097         debug(F101,"cmcvtdate minute","",mm);
5098         debug(F101,"cmcvtdate second","",ss);
5099     }
5100 #endif /* DEBLOG */
5101
5102     makestr(&cmdatemsg,NULL);
5103     if (havedelta) {
5104 #ifdef DEBUG
5105         if (deblog) {
5106             debug(F110,"cmcvtdate base ",dp,0);
5107             debug(F101,"cmcvtdate delta sign","",dsign);
5108             debug(F101,"cmcvtdate delta yrs ","",dyears);
5109             debug(F101,"cmcvtdate delta mos ","",dmonths);
5110             debug(F101,"cmcvtdate delta days","",ddays);
5111             debug(F101,"cmcvtdate delta hrs ","",dhours);
5112             debug(F101,"cmcvtdate delta mins","",dmins);
5113             debug(F101,"cmcvtdate delta secs","",dsecs);
5114         }
5115 #endif /* DEBLOG */
5116         if (!(dp = cmdelta(atoi(year),
5117                     atoi(month),
5118                     nday, hh, mm, ss,
5119                     dsign, dyears, dmonths, ddays, dhours, dmins, dsecs))) {
5120             debug(F111,"cmcvtdate",cmdatemsg,-1);
5121             return(NULL);
5122         }
5123     }
5124
5125   xcvtdate:                             /* Exit point for success */
5126     {
5127         int len, k, n;
5128         char * p;
5129         debug(F110,"cmcvtdate xcvtdate dp",dp,0);
5130         if (!dp) dp = "";               /* Shouldn't happen */
5131         if (!*dp) return(NULL);         /* ... */
5132         len = strlen(dp);
5133         debug(F111,"cmcvtdate result",dp,len);
5134         k = cmdatebp - (char *)cmdatebuf; /* Space used */
5135         n = CMDATEBUF - k - 1;          /* Space left */
5136         if (n < len) {                  /* Not enough? */
5137             cmdatebp = cmdatebuf;       /* Wrap around */
5138             n = CMDATEBUF;
5139         }
5140         ckstrncpy(cmdatebp,dp,n);
5141         p = cmdatebp;
5142         cmdatebp += len + 1;
5143         return(p);
5144     }
5145 }
5146
5147 int
5148 cmvdate(d) char * d; {                  /* Verify date-time */
5149     int i;
5150     if (!d) return(0);
5151     if ((int)strlen(d) != 17) return(0);
5152     for (i = 0; i < 8; i++) { if (!isdigit(d[i])) return(0); }
5153     if (!isdigit(d[9])  || !isdigit(d[10]) ||
5154         !isdigit(d[12]) || !isdigit(d[13]) ||
5155         !isdigit(d[15]) || !isdigit(d[16]))
5156       return(0);
5157     if (!ckstrchr(" Tt_-:",d[8])) return(0);
5158     if (d[11] != ':' && d[14] != ':') return(0);
5159     return(1);
5160 }
5161
5162 /* c m d i f f d a t e  --  Get difference between two date-times */
5163
5164 char *
5165 cmdiffdate(d1,d2) char * d1, * d2; {
5166     char d1buf[9], d2buf[9];
5167     char x1buf[18], x2buf[18];
5168     char * p;
5169
5170     int hh1 = 0, mm1 = 0, ss1 = 0;
5171     int hh2 = 0, mm2 = 0, ss2 = 0;
5172     int hh, mm, ss;
5173     int sign;
5174     long jd1, jd2, jd, f1, f2, fx;
5175     static char result[24], *rp;
5176
5177     debug(F110,"cmdiffdate d1 A",d1,0);
5178     debug(F110,"cmdiffdate d2 A",d2,0);
5179
5180     if (!(p = cmcvtdate(d1,1)))         /* Convert dates to standard format */
5181       return(NULL);
5182     ckstrncpy(x1buf,p,18);
5183     d1 = x1buf;
5184
5185     if (!(p = cmcvtdate(d2,1)))
5186       return(NULL);
5187     ckstrncpy(x2buf,p,18);
5188     d2 = x2buf;
5189
5190     debug(F110,"cmdiffdate d1 B",d1,0);
5191     debug(F110,"cmdiffdate d2 B",d2,0);
5192     if (!cmvdate(d1) || !cmvdate(d2))
5193       return(NULL);
5194
5195     hh1 = atoi(&d1[9]);                 /* Get hours, minutes, and seconds */
5196     mm1 = atoi(&d1[12]);                /* for first date */
5197     ss1 = atoi(&d1[15]);
5198     ckstrncpy(d1buf,d1,9);
5199
5200     hh2 = atoi(&d2[9]);                 /* ditto for second date */
5201     mm2 = atoi(&d2[12]);
5202     ss2 = atoi(&d2[15]);
5203     ckstrncpy(d2buf,d2,9);
5204     
5205     jd1 = mjd(d1buf);                   /* Get the two Julian dates */
5206     jd2 = mjd(d2buf);
5207     f1 = ss1 + 60 * mm1 + 3600 * hh1;   /* Convert first time to seconds */
5208
5209     f2 = ss2 + 60 * mm2 + 3600 * hh2;   /* Ditto for second time */
5210     debug(F101,"cmdiffdate jd1","",jd1);
5211     debug(F101,"cmdiffdate f1","",f1);
5212     debug(F101,"cmdiffdate jd2","",jd2);
5213     debug(F101,"cmdiffdate f2","",f2);
5214   
5215     if (jd2 > jd1 || (jd1 == jd2 && f2 > f1)) {
5216         sign = -1; 
5217         if (f1 > f2) {jd2--; f2 += 86400L;}
5218         jd = jd2 - jd1;
5219         fx = f2 - f1;
5220     } else {
5221         sign = 1;
5222         if (f2 > f1) {jd1--; f1 += 86400L;}
5223         jd = jd1 - jd2;
5224         fx = f1 - f2;
5225     }
5226     debug(F111,"cmdiffdate sign jd",sign<0?"-":"+",jd);
5227     debug(F101,"cmdiffdate fx","",fx);
5228   
5229     hh = (int) (fx / 3600L);            /* Convert seconds to hh:mm:ss */
5230
5231     mm = (int) (fx % 3600L) / 60L;
5232     ss = (int) (fx % 3600L) % 60L;
5233
5234     rp = result;                        /* Format the result */
5235     *rp++ = (sign < 0) ? '-' : '+';
5236     if (jd != 0 && hh+mm+ss == 0) {
5237         sprintf(rp,"%ldd",jd);
5238     } else if (jd == 0) {
5239         if (ss == 0)
5240           sprintf(rp,"%d:%02d",hh,mm);
5241         else
5242           sprintf(rp,"%d:%02d:%02d",hh,mm,ss);
5243     } else {
5244         if (ss == 0)
5245           sprintf(rp,"%ldd%d:%02d",jd,hh,mm);
5246         else
5247           sprintf(rp,"%ldd%d:%02d:%02d",jd,hh,mm,ss);
5248     }
5249     debug(F110,"cmdiffdate result",result,0);
5250     return((char *)result);
5251 }
5252
5253 /* s h u f f l e d a t e  --  Rearrange date string */
5254
5255 /*
5256   Call with:
5257     A date string in standard format: yyyymmdd hh:mm:ss (time optional).
5258     Options:
5259       1: Reformat date to yyyy-mmm-dd (mmm = English month abbreviation).
5260       2: Reformat date to dd-mmm-yyyy (mmm = English month abbreviation).
5261       3: Reformat as numeric yyyymmddhhmmss.
5262     Returns:
5263       Pointer to result if args valid, otherwise original arg pointer.
5264 */
5265 char *
5266 shuffledate(p,opt) char * p; int opt; {
5267     int len;
5268     char ibuf[32];
5269     static char obuf[48];
5270     char c;
5271     int yy, dd, mm;
5272
5273     if (!p) p = "";
5274     if (!*p) p = ckdate();
5275     if (opt < 1 || opt > 3)
5276       return(p);
5277     len = strlen(p);
5278     if (len < 8 || len > 31) return(p);
5279     if (opt == 3) {
5280         ckstrncpy(obuf,p,48);
5281         /* yyyymmdd hh:mm:ss */
5282         /* 01234567890123456 */
5283         /* yyyymmddhhmmss    */
5284         obuf[8] = obuf[9];
5285         obuf[9] = obuf[10];
5286         obuf[10] = obuf[12];
5287         obuf[11] = obuf[13];
5288         obuf[12] = obuf[15];
5289         obuf[13] = obuf[16];
5290         obuf[14] = NUL;
5291         return((char *)obuf);
5292     }
5293     ckstrncpy(ibuf,p,32);
5294     c = ibuf[4];                        /* Warning: not Y10K compliant */
5295     ibuf[4] = NUL;
5296     if (!rdigits(ibuf))
5297       return(p);
5298     yy = atoi(ibuf);
5299     if (yy < 1 || yy > 9999)
5300       return(p);
5301     ibuf[4] = c;
5302     c = ibuf[6];
5303     ibuf[6] = NUL;
5304     if (!rdigits(&ibuf[4]))
5305       return(p);
5306     mm = atoi(&ibuf[4]);
5307     if (mm < 1 || mm > 12)
5308       return(p);
5309     ibuf[6] = c;
5310     c = ibuf[8];
5311     ibuf[8] = NUL;
5312     if (!rdigits(&ibuf[6]))
5313       return(p);
5314     dd = atoi(&ibuf[6]);
5315     ibuf[8] = c;
5316     if (dd < 1 || mm > 31)
5317       return(p);
5318     /* IGNORE WARNINGS ABOUT moname[] REFS OUT OF RANGE - it's prechecked. */
5319     switch (opt) {
5320       case 1:
5321         sprintf(obuf,"%04d-%s-%02d%s",yy,moname[mm-1],dd,&ibuf[8]);
5322         break;
5323       case 2:
5324         sprintf(obuf,"%02d-%s-%04d%s",dd,moname[mm-1],yy,&ibuf[8]);
5325     }
5326     return((char *)obuf);
5327 }
5328
5329 /*  C K C V T D A T E  --  Like cmcvtdate(), but returns string.  */
5330 /*  For use by date-related functions */
5331 /*  See calling conventions for cmcvtdate() above. */
5332
5333 char *
5334 ckcvtdate(p,t) char * p; int t; {
5335     char * s;
5336     if (!(s = cmcvtdate(p,t)))
5337       return("<BAD_DATE_OR_TIME>");     /* \fblah() error message */
5338     else
5339       return(s);
5340 }
5341
5342
5343 /*  C M D A T E  --  Parse a date and/or time  */
5344
5345 /*
5346   Accepts date in various formats.  If the date is recognized,
5347   this routine returns 0 or greater with the result string pointer
5348   pointing to a buffer containing the date as "yyyymmdd hh:mm:ss".
5349 */
5350 int
5351 cmdate(xhlp,xdef,xp,quiet,f) char *xhlp, *xdef, **xp; int quiet; xx_strp f; {
5352     int x, rc;
5353     char *o, *s, *zq, *dp;
5354
5355     cmfldflgs = 0;
5356     if (!xhlp) xhlp = "";
5357     if (!xdef) xdef = "";
5358     if (!*xhlp) xhlp = "Date and/or time";
5359     *xp = "";
5360
5361     rc = cmfld(xhlp,xdef,&s,(xx_strp)0);
5362     debug(F101,"cmdate cmfld rc","",rc);
5363     if (rc < 0)
5364       return(rc);
5365     debug(F110,"cmdate 1",s,0);
5366     o = s;                              /* Remember what they typed. */
5367     s = brstrip(s);
5368     debug(F110,"cmdate 2",s,0);
5369
5370     x = 0;
5371     if (f) {                            /* If a conversion function is given */
5372         char * pp;
5373         zq = atxbuf;                    /* do the conversion. */
5374         pp = atxbuf;
5375         atxn = CMDBL;
5376         if ((x = (*f)(s,&zq,&atxn)) < 0) return(-2);
5377         if (!*pp)
5378           pp = xdef;
5379         if (setatm(pp,0) < 0) {
5380             if (!quiet) printf("?Evaluated date too long\n");
5381             return(-9);
5382         }
5383         s = atxbuf;
5384     }
5385     dp = cmcvtdate(s,1);
5386     if (!dp) {
5387         if (!quiet) printf("?%s\n",cmdatemsg);
5388         return(-9);
5389     }
5390     *xp = dp;
5391     return(0);
5392 }
5393
5394 #ifdef CK_RECALL                        /* Command-recall functions */
5395
5396 /*  C M R I N I  --  Initialize or change size of command recall buffer */
5397
5398 int
5399 cmrini(n) int n; {
5400     int i;
5401     if (recall && in_recall) {          /* Free old storage, if any */
5402         for (i = 0; i < cm_recall; i++) {
5403             if (recall[i]) {
5404                 free(recall[i]);
5405                 recall[i] = NULL;
5406             }
5407         }
5408         free(recall);
5409         recall = NULL;
5410     }
5411     cm_recall = n;                      /* Set new size */
5412     rlast = current = -1;               /* Initialize pointers */
5413     if (n > 0) {
5414         recall = (char **)malloc((cm_recall + 1) * sizeof(char *));
5415         if (!recall)
5416           return(1);
5417         for (i = 0; i < cm_recall; i++) {
5418             recall[i] = NULL;
5419         }
5420         in_recall = 1;                  /* Recall buffers init'd */
5421     }
5422     return(0);
5423 }
5424
5425 /*  C M A D D N E X T  --  Force addition of next command */
5426
5427 VOID
5428 cmaddnext() {
5429     if (on_recall && in_recall) {       /* Even if it doesn't come */
5430         force_add = 1;                  /* from the keyboard */
5431         newcmd = 1;
5432         no_recall = 0;
5433     }
5434 }
5435
5436 /*  C M G E T C M D  --  Find most recent matching command  */
5437
5438 char *
5439 cmgetcmd(s) char * s; {
5440     int i;
5441     for (i = current; i >= 0; i--) {    /* Search backward thru history list */
5442         if (!recall[i]) continue;       /* This one's null, skip it */
5443         if (ckmatch(s,recall[i],0,1))   /* Match? */
5444           return(recall[i]);            /* Yes, return pointer */
5445     }
5446     return(NULL);                       /* No match, return NULL pointer */
5447 }
5448 #endif /* CK_RECALL */
5449
5450 /*  A D D C M D  --  Add a command to the recall buffer  */
5451
5452 VOID
5453 addcmd(s) char * s; {
5454     int len = 0, nq = 0;
5455     char * p;
5456 #ifdef CKLEARN
5457     extern int learning;
5458 #endif /* CKLEARN */
5459
5460     if (xcmdsrc)                        /* Only for interactive commands */
5461       return;
5462
5463     if (!newcmd)                        /* The command has been here already */
5464       return;                           /* so ignore it. */
5465     newcmd = 0;                         /* It's new but do this only once. */
5466
5467     if (!s) s = cmdbuf;
5468     if (s[0])
5469       len = strlen(s);
5470
5471     if (len < 1)                        /* Don't save empty commands */
5472       return;
5473
5474     p = s;
5475     while (*p) { if (*p++ == '?') nq++; } /* Count question marks */
5476
5477 #ifdef CKLEARN
5478     if (learning)                       /* If a learned script is active */
5479       learncmd(s);                      /* record this command. */
5480 #endif /* CKLEARN */
5481
5482     debug(F010,"CMD(P)",s,0);           /* Maybe record it in the debug log */
5483
5484 #ifdef CKSYSLOG
5485     if (ckxlogging) {                   /* Maybe record it in syslog */
5486         if (ckxsyslog >= SYSLG_CX || ckxsyslog >= SYSLG_CM)
5487           cksyslog(SYSLG_CX, 1, "command", s, NULL);
5488     }
5489 #endif /* CKSYSLOG */
5490
5491 #ifdef CK_RECALL
5492     last_recall = 0;
5493
5494     if (on_recall &&                    /* Command recall is on? */
5495         cm_recall > 0 &&                /* Recall buffer size is > 0? */
5496         !no_recall) {                   /* Not not saving this command? */
5497
5498         if (!force_add && rlast > -1)   /* If previous command was identical */
5499           if (!strcmp(s,recall[rlast])) /* don't add another copy */
5500             return;
5501
5502         force_add = 0;                  /* Reset now in case it was set */
5503
5504         if (rlast >= cm_recall - 1) {   /* Recall buffer full? */
5505             int i;
5506             if (recall[0]) {            /* Discard oldest command */
5507                 free(recall[0]);
5508                 recall[0] = NULL;
5509             }
5510             for (i = 0; i < rlast; i++) {  /* The rest */
5511                 recall[i] = recall[i+1];   /* move back */
5512             }
5513             rlast--;                    /* Now we have one less */
5514         }
5515         rlast++;                        /* Index of last command in buffer */
5516         current = rlast;                /* Also now the current command */
5517         if (current >= cm_recall) {     /* Shouldn't happen */
5518             printf("?Command history error\n"); /* but if it does */
5519             on_recall = 0;                      /* turn off command saving */
5520 #ifdef COMMENT
5521         } else if (nq > 0) {            /* Have at least one question mark */
5522             recall[current] = malloc(len+nq+1);
5523             if (recall[current]) {
5524                 p = recall[current];
5525                 while (*s) {
5526                     if (*s == '?')
5527                       *p++ = '\\';
5528                     *p++ = *s++;
5529                 }
5530                 *p = NUL;
5531             }
5532 #endif /* COMMENT */
5533         } else {                        /* Normal case, just copy */
5534             recall[current] = malloc(len+1);
5535             if (recall[current])
5536               ckstrncpy(recall[current],s,len+1);
5537         }
5538     }
5539 #endif /* CK_RECALL */
5540 }
5541
5542
5543 #ifdef CK_RECALL
5544
5545 /* C M H I S T O R Y */
5546
5547 VOID
5548 cmhistory() {
5549     int i, lc = 1;
5550     for (i = 0; i <= current; i++) {
5551         printf(" %s\n", recall[i]);
5552         if (++lc > (cmd_rows - 2)) {    /* Screen full? */
5553             if (!askmore())             /* Do more-prompting... */
5554               break;
5555             else
5556               lc = 0;
5557         }
5558     }
5559 }
5560
5561 int
5562 savhistory(s,disp) char *s; int disp; {
5563     FILE * fp;
5564     int i;
5565
5566     fp = fopen(s, disp ? "a" : "w");
5567     if (!fp) {
5568         perror(s);
5569         return(0);
5570     }
5571     for (i = 0; i <= current; i++)
5572       fprintf(fp,"%s\n", recall[i]);
5573     fclose(fp);
5574     return(1);
5575 }
5576 #endif /* CK_RECALL */
5577
5578 #ifdef COMMENT
5579 /* apparently not used */
5580 int
5581 cmgetlc(s) char * s; {                  /* Get leading char */
5582     char c;
5583     while ((c = *s++) <= SP) {
5584         if (!c)
5585           break;
5586     }
5587     return(c);
5588 }
5589 #endif /* COMMENT */
5590
5591
5592 /*  C M C F M  --  Parse command confirmation (end of line)  */
5593
5594 /*
5595  Returns
5596    -2: User typed anything but whitespace or newline
5597    -1: Reparse needed
5598     0: Confirmation was received
5599 */
5600 int
5601 cmcfm() {
5602     int x, xc;
5603     debug(F101,"cmcfm: cmflgs","",cmflgs);
5604     debug(F110,"cmcfm: atmbuf",atmbuf,0);
5605     inword = xc = cc = 0;
5606
5607     setatm("",0);                       /* (Probably unnecessary) */
5608
5609     while (cmflgs != 1) {
5610         x = gtword(0);
5611         xc += cc;
5612
5613         switch (x) {
5614           case -9:
5615             printf("Command or field too long\n");
5616           case -4:                      /* EOF */
5617           case -2:
5618           case -1:
5619             return(x);
5620           case 1:                       /* End of line */
5621             if (xc > 0) {
5622                 if (xcmfdb) {
5623                     return(-6);
5624                 } else {
5625                     printf("?Not confirmed - %s\n",atmbuf);
5626                     return(-9);
5627                 }
5628             } else
5629               break;                    /* Finish up below */
5630           case 2:                       /* ESC */
5631             if (xc == 0) {
5632                 bleep(BP_WARN);
5633                 continue;               /* or fall thru. */
5634             }
5635           case 0:                       /* Space */
5636             if (xc == 0)                /* If no chars typed, continue, */
5637               continue;                 /* else fall thru. */
5638             /* else fall thru... */
5639
5640           case 3:                       /* Question mark */
5641             if (xc > 0) {
5642                 if (xcmfdb) {
5643                     return(-6);
5644                 } else {
5645                     printf("?Not confirmed - %s\n",atmbuf);
5646                     return(-9);
5647                 }
5648             }
5649             printf(
5650                "\n Press the Return or Enter key to confirm the command\n");
5651             printf("%s%s",cmprom,cmdbuf);
5652             fflush(stdout);
5653             continue;
5654         }
5655     }
5656     debok = 1;
5657     return(0);
5658 }
5659
5660
5661 /* The following material supports chained parsing functions. */
5662 /* See ckucmd.h for FDB and OFDB definitions. */
5663
5664 struct OFDB cmresult = {                /* Universal cmfdb result holder */
5665     NULL,
5666     0,
5667     NULL,
5668     0
5669 };
5670
5671 VOID
5672 cmfdbi(p,fc,s1,s2,s3,n1,n2,f,k,nxt)     /* Initialize an FDB */
5673     struct FDB * p;
5674     int fc;
5675     char * s1, * s2, * s3;
5676     int n1, n2;
5677     xx_strp f;
5678     struct keytab * k;
5679     struct FDB * nxt; {
5680
5681     p->fcode = fc;
5682     p->hlpmsg = s1;
5683     p->dflt = s2;
5684     p->sdata = s3;
5685     p->ndata1 = n1;
5686     p->ndata2 = n2;
5687     p->spf = f;
5688     p->kwdtbl = k;
5689     p->nxtfdb = nxt;
5690 }
5691
5692 /*  C M F D B  --  Parse a field with several possible functions  */
5693
5694 int
5695 cmfdb(fdbin) struct FDB * fdbin; {
5696 #ifndef NOSPL
5697     extern int x_ifnum;                 /* IF NUMERIC - disables warnings */
5698 #endif /* NOSPL */
5699     struct FDB * in = fdbin;
5700     struct OFDB * out = &cmresult;
5701     int x = 0, n, r;
5702     char *s, *xp, *m = NULL;
5703     int errbits = 0;
5704
5705     xp = bp;
5706
5707     out->fcode = -1;                    /* Initialize output struct */
5708     out->fdbaddr = NULL;
5709     out->sresult = NULL;
5710     out->nresult = 0;
5711 /*
5712   Currently we make one trip through the FDBs.  So if the user types Esc or
5713   Tab at the beginning of a field, only the first FDB is examined for a
5714   default.  If the user types ?, help is given only for one FDB.  We should
5715   search through the FDBs for all matching possibilities -- and in particular
5716   display the pertinent context-sensitive help for each function, rather than
5717   the only the first one that works, and then rewind the FDB pointer so we
5718   are not locked out of the earlier ones.
5719 */
5720     cmfldflgs = 0;
5721     while (1) {                         /* Loop through the chain of FDBs */
5722         nomsg = 1;
5723         xcmfdb = 1;
5724         s = NULL;
5725         n = 0;
5726         debug(F101,"cmfdb in->fcode","",in->fcode);
5727         switch (in->fcode) {            /* Current parsing function code */
5728           case _CMNUM:
5729             r = in->ndata1;
5730             if (r != 10 && r != 8) r = 10;
5731 #ifndef NOSPL
5732             x_ifnum = 1;                /* Disables warning messages */
5733 #endif /* NOSPL */
5734             x = cmnum(in->hlpmsg,in->dflt,r,&n,in->spf);
5735 #ifndef NOSPL
5736             x_ifnum = 0;
5737 #endif /* NOSPL */
5738             debug(F101,"cmfdb cmnum","",x);
5739             if (x < 0) errbits |= 1;
5740             break;
5741           case _CMOFI:
5742             x = cmofi(in->hlpmsg,in->dflt,&s,in->spf);
5743             debug(F101,"cmfdb cmofi","",x);
5744             if (x < 0) errbits |= 2;
5745             break;
5746           case _CMIFI:
5747             x = cmifi2(in->hlpmsg,
5748                        in->dflt,
5749                        &s,
5750                        &n,
5751                        in->ndata1,
5752                        in->sdata,
5753                        in->spf,
5754                        in->ndata2
5755                        );
5756             debug(F101,"cmfdb cmifi2 x","",x);
5757             debug(F101,"cmfdb cmifi2 n","",n);
5758             if (x < 0) errbits |= 4;
5759             break;
5760           case _CMFLD:
5761             cmfldflgs = in->ndata1;
5762             x = cmfld(in->hlpmsg,in->dflt,&s,in->spf);
5763             debug(F101,"cmfdb cmfld","",x);
5764             if (x < 0) errbits |= 8;
5765             break;
5766           case _CMTXT:
5767             x = cmtxt(in->hlpmsg,in->dflt,&s,in->spf);
5768             debug(F101,"cmfdb cmtxt","",x);
5769             if (x < 0) errbits |= 16;
5770             break;
5771           case _CMKEY:
5772             x = cmkey2(in->kwdtbl,
5773                        in->ndata1,
5774                        in->hlpmsg,in->dflt,in->sdata,in->spf,in->ndata2);
5775             debug(F101,"cmfdb cmkey","",x);
5776             if (x < 0) errbits |= ((in->ndata2 & 4) ? 32 : 64);
5777             break;
5778           case _CMCFM:
5779             x = cmcfm();
5780             debug(F101,"cmfdb cmcfm","",x);
5781             if (x < 0) errbits |= 128;
5782             break;
5783           default:
5784             debug(F101,"cmfdb - unexpected function code","",in->fcode);
5785             printf("?cmfdb - unexpected function code: %d\n",in->fcode);
5786         }
5787         debug(F101,"cmfdb x","",x);
5788         debug(F101,"cmfdb cmflgs","",cmflgs);
5789         debug(F101,"cmfdb crflag","",crflag);
5790         debug(F101,"cmfdb qmflag","",qmflag);
5791         debug(F101,"cmfdb esflag","",esflag);
5792
5793         if (x > -1) {                   /* Success */
5794             out->fcode = in->fcode;     /* Fill in output struct */
5795             out->fdbaddr = in;
5796             out->sresult = s;
5797             out->nresult = (in->fcode == _CMKEY) ? x : n;
5798             out->kflags = (in->fcode == _CMKEY) ? cmkwflgs : 0;
5799             debug(F111,"cmfdb out->nresult",out->sresult,out->nresult);
5800             nomsg = 0;
5801             xcmfdb = 0;
5802             /* debug(F111,"cmfdb cmdbuf & crflag",cmdbuf,crflag); */
5803             if (crflag) {
5804                 cmflgs = 1;
5805             }
5806             return(x);                  /* and return */
5807         }
5808         in = in->nxtfdb;                /* Failed, get next parsing function */
5809         nomsg = 0;
5810         xcmfdb = 0;
5811         if (!in) {                      /* No more */
5812             debug(F101,"cmfdb failure x","",x);
5813             debug(F101,"cmfdb failure errbits","",errbits);
5814             if (x == -6)
5815               x = -9;
5816             if (x == -9) {
5817 #ifdef CKROOT
5818                 if (ckrooterr)
5819                   m = "Off Limits";
5820                 else
5821 #endif /* CKROOT */
5822                 /* Make informative messages for a few common cases */
5823                 switch (errbits) {
5824                   case 4+32: m = "Does not match filename or switch"; break;
5825                   case 4+64: m = "Does not match filename or keyword"; break;
5826                   case 1+32: m = "Not a number or valid keyword"; break;
5827                   case 1+64: m = "Not a number or valid switch"; break;
5828                   default: m = "Not valid in this position";
5829                 }
5830                 printf("?%s: \"%s\"\n",m, atmbuf);
5831             }
5832             return(x);
5833         }
5834         if (x != -2 && x != -6 && x != -9 && x != -3) /* Editing or somesuch */
5835           return(x);                    /* Go back and reparse */
5836         pp = np = bp = xp;              /* Back up pointers */
5837         cmflgs = -1;                    /* Force a reparse */
5838
5839
5840 #ifndef NOSPL
5841         if (!askflag) {                 /* If not executing ASK-class cmd... */
5842 #endif /* NOSPL */
5843             if (crflag) {               /* If CR was typed, put it back */
5844                 pushc = LF;             /* But as a linefeed */
5845             } else if (qmflag) {        /* Ditto for Question mark */
5846                 pushc = '?';
5847             } else if (esflag) {        /* and Escape or Tab */
5848                 pushc = ESC;
5849             }
5850 #ifndef NOSPL
5851         }
5852 #endif /* NOSPL */
5853     }
5854 }
5855
5856
5857 /*  G T W O R D  --  Gets a "word" from the command input stream  */
5858
5859 /*
5860 Usage: retcode = gtword(brk);
5861   brk = 0 for normal word breaks (space, CR, Esc, ?)
5862   brk = 1 to add ':' and '=' (for parsing switches).  These characters
5863         act as break characters only if the first character of the field
5864         is slash ('/'), i.e. switch introducer.
5865
5866 Returns:
5867 -10 Timelimit set and timed out
5868  -9 if input was too long
5869  -4 if end of file (e.g. pipe broken)
5870  -3 if null field
5871  -2 if command buffer overflows
5872  -1 if user did some deleting
5873   0 if word terminates with SP or tab
5874   1 if ... CR
5875   2 if ... ESC
5876   3 if ... ? (question mark)
5877   4 if ... : or = and called with brk != 0
5878
5879 With:
5880   pp pointing to beginning of word in buffer
5881   bp pointing to after current position
5882   atmbuf containing a copy of the word
5883   cc containing the number of characters in the word copied to atmbuf
5884 */
5885
5886 int
5887 ungword() {                             /* Unget a word */
5888     debug(F101,"ungword cmflgs","",cmflgs);
5889     if (ungw) return(0);
5890     cmfsav = cmflgs;
5891     ungw = 1;
5892     cmflgs = 0;
5893     return(0);
5894 }
5895
5896 /* Un-un-get word.  Undo ungword() if it has been done. */
5897
5898 VOID
5899 unungw() {
5900     debug(F010,"unungw atmbuf",atmbuf,0);
5901     if (ungw) {
5902         ungw = 0;
5903         cmflgs = cmfsav;
5904         atmbuf[0] = NUL;
5905     }
5906 }
5907
5908 static int
5909 gtword(brk) int brk; {
5910     int c;                              /* Current char */
5911     int quote = 0;                      /* Flag for quote character */
5912     int echof = 0;                      /* Flag for whether to echo */
5913     int comment = 0;                    /* Flag for in comment */
5914     char *cp = NULL;                    /* Comment pointer */
5915     int eintr = 0;                      /* Flag for syscall interrupted */
5916     int bracelvl = 0;                   /* nested brace counter [jrs] */
5917     int iscontd = 0;                    /* Flag for continuation */
5918     int realtty = 0;                    /* Stdin is really a tty */
5919     char firstnb  = NUL;
5920     char lastchar = NUL;
5921     char prevchar = NUL;
5922     char lbrace, rbrace;
5923     int dq = 0;                         /* Doublequote flag */
5924     int dqn = 0;                        /* and count */
5925     int isesc = 0;
5926
5927 #ifdef RTU
5928     extern int rtu_bug;
5929 #endif /* RTU */
5930
5931 #ifdef IKSD
5932     extern int inserver;
5933 #endif /* IKSD */
5934     extern int kstartactive;
5935
5936 #ifdef datageneral
5937     extern int termtype;                /* DG terminal type flag */
5938     extern int con_reads_mt;            /* Console read asynch is active */
5939     if (con_reads_mt) connoi_mt();      /* Task would interfere w/cons read */
5940 #endif /* datageneral */
5941
5942
5943 #ifdef COMMENT
5944 #ifdef DEBUG
5945     if (deblog) {
5946         debug(F101,"gtword brk","",brk);
5947         debug(F101,"gtword cmfldflgs","",cmfldflgs);
5948         debug(F101,"gtword swarg","",swarg);
5949         debug(F101,"gtword dpx","",dpx);
5950         debug(F101,"gtword echof","",echof);
5951 #ifndef NOSPL
5952         debug(F101,"gtword askflag","",askflag);
5953         debug(F101,"gtword timelimit","",timelimit);
5954 #ifndef NOLOCAL
5955 #ifndef NOXFER
5956 #ifdef CK_AUTODL
5957         debug(F101,"gtword cmdadl","",cmdadl);
5958 #endif /* CK_AUTODL */
5959 #endif /* NOXFER */
5960 #endif /* NOLOCAL */
5961 #endif /* NOSPL */
5962     }
5963 #endif /* DEBUG */
5964 #endif /* COMMENT */
5965
5966     realtty = is_a_tty(0);              /* Stdin is really a tty? */
5967
5968     if (cmfldflgs & 1) {
5969         lbrace = '(';
5970         rbrace = ')';
5971     } else {
5972         lbrace = '{';
5973         rbrace = '}';
5974     }
5975     crflag = 0;
5976     qmflag = 0;
5977     esflag = 0;
5978
5979     if (swarg) {                        /* No leading space for switch args */
5980         inword = 1;
5981         swarg = 0;
5982     }
5983     if (ungw) {                         /* Have a word saved? */
5984 #ifdef M_UNGW
5985         /* Experimental code to allow ungetting multiple words. */
5986         /* See comments in ckmkey2() above. */
5987         int x;
5988         if (np > pp) pp = np;
5989         while (*pp == SP) pp++;
5990         if (!*pp) {
5991             ungw = 0;
5992             cmflgs = cmfsav;
5993         } else {
5994             if ((x = setatm(pp,2)) < 0) {
5995                 printf("?Saved word too long\n");
5996                 return(-9);
5997             }
5998             if (pp[x] >= SP) {
5999                 char *p2;
6000                 p2 = pp;
6001                 p2 += x;
6002                 while (*p2 == SP) p2++;
6003                 if (*p2) {
6004                     np = p2;
6005                     ungword();
6006                 }
6007             } else {
6008                 ungw = 0;
6009                 cmflgs = cmfsav;
6010                 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6011             }
6012         }
6013         return(cmflgs);
6014 #else
6015         /*
6016            You would think the following should be:
6017              while (*pp == SP) pp++;
6018            but you would be wrong -- making this change breaks GOTO.
6019         */
6020         while (*pp++ == SP) ;
6021         if (setatm(pp,2) < 0) {
6022             printf("?Saved word too long\n");
6023             return(-9);
6024         }
6025         ungw = 0;
6026         cmflgs = cmfsav;
6027         debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6028         return(cmflgs);
6029 #endif /* M_UNGW */
6030     }
6031     pp = np;                            /* Start of current field */
6032
6033 #ifdef COMMENT
6034 #ifdef DEBUG
6035     if (deblog) {
6036         debug(F110,"gtword cmdbuf",cmdbuf,0);
6037         debug(F110,"gtword bp",bp,0);
6038         debug(F110,"gtword pp",pp,0);
6039     }
6040 #endif /* DEBUG */
6041 #endif /* COMMENT */
6042     {
6043         /* If we are reparsing we have to recount any braces or doublequotes */
6044         char * p = pp;
6045         char c;
6046         if (*p == '"')
6047           dq++;
6048         while ((c = *p++))
6049           if (c == lbrace)
6050             bracelvl++;
6051           else if (c == rbrace)
6052             bracelvl--;
6053           else if (dq && c == '"')
6054             dqn++;
6055     }
6056     while (bp < cmdbuf+CMDBL) {         /* Big get-a-character loop */
6057         echof = 0;                      /* Assume we don't echo because */
6058         chsrc = 0;                      /* character came from reparse buf. */
6059 #ifdef BS_DIRSEP
6060 CMDIRPARSE:
6061 #endif /* BS_DIRSEP */
6062
6063         c = *bp;
6064         if (!c) {                       /* If no char waiting in reparse buf */
6065             if (dpx && (!pushc
6066 #ifndef NOSPL
6067                         || askflag
6068 #endif /* NOSPL */
6069                         ))              /* Get from tty, set echo flag */
6070               echof = 1;
6071             c = cmdgetc(timelimit);     /* Read a command character. */
6072 #ifdef DEBUG
6073             debug(F101,"gtword c","",c);
6074 #endif /* DEBUG */
6075
6076             if (timelimit && c < -1) {  /* Timed out */
6077                 return(-10);
6078             }
6079
6080 #ifndef NOXFER
6081 /*
6082   The following allows packet recognition in the command parser.
6083   Presently it works only for Kermit packets, and if our current protocol
6084   happens to be anything besides Kermit, we simply force it to Kermit.
6085   We don't use the APC mechanism here for mechanical reasons, and also
6086   because this way, it works even with minimally configured interactive
6087   versions.  Add Zmodem later...
6088 */
6089 #ifdef CK_AUTODL
6090             if ((!local && cmdadl)      /* Autodownload enabled? */
6091 #ifdef IKS_OPTION
6092                 || TELOPT_SB(TELOPT_KERMIT).kermit.me_start
6093 #endif /* IKS_OPTION */
6094                 ) {
6095                 int k;
6096                 k = kstart((CHAR)c);    /* Kermit S or I packet? */
6097                 if (k) {
6098                     int ksign = 0;
6099                     if (k < 0) {        /* Minus-Protocol? */
6100 #ifdef NOSERVER
6101                         goto noserver;  /* Need server mode for this */
6102 #else
6103                         ksign = 1;      /* Remember */
6104                         k = 0 - k;      /* Convert to actual protocol */
6105                         justone = 1;    /* Flag for protocol module */
6106 #endif /* NOSERVER */
6107                     } else
6108                       justone = 0;
6109                     k--;                /* Adjust kstart's return value */
6110                     if (k == PROTO_K) {
6111                         extern int protocol, g_proto;
6112                         extern CHAR sstate;
6113                         g_proto = protocol;
6114                         protocol = PROTO_K; /* Crude... */
6115                         sstate = ksign ? 'x' : 'v';
6116                         cmdbuf[0] = NUL;
6117                         return(-3);
6118                     }
6119                 }
6120             }
6121 #ifdef NOSERVER
6122           noserver:
6123 #endif /* NOSERVER */
6124 #endif /* CK_AUTODL */
6125 #endif /* NOXFER */
6126
6127             chsrc = 1;                  /* Remember character source is tty. */
6128             brkchar = c;
6129
6130 #ifdef IKSD
6131             if (inserver && c < 0) {    /* End of session? */
6132                 debug(F111,"gtword c < 0","exiting",c);
6133                 return(-4);             /* Cleanup and terminate */
6134             }
6135 #endif /* IKSD */
6136
6137 #ifdef OS2
6138            if (c < 0) {                 /* Error */
6139                if (c == -3) {           /* Empty word? */
6140                    if (blocklvl > 0)    /* In a block */
6141                      continue;          /* so keep looking for block end */
6142                    else
6143                      return(-3);        /* Otherwise say we got nothing */
6144                } else {                 /* Not empty word */
6145                    return(-4);          /* So some kind of i/o error */
6146                }
6147            }
6148 #else
6149 #ifdef MAC
6150            if (c == -3)                 /* Empty word... */
6151              if (blocklvl > 0)
6152                continue;
6153              else
6154                return(-3);
6155 #endif /* MAC */
6156 #endif /* OS2 */
6157            if (c == EOF) {              /* This can happen if stdin not tty. */
6158 #ifdef EINTR
6159 /*
6160   Some operating and/or C runtime systems return EINTR for no good reason,
6161   when the end of the standard input "file" is encountered.  In cases like
6162   this, we get into an infinite loop; hence the eintr counter, which is reset
6163   to 0 upon each call to this routine.
6164 */
6165                 debug(F101,"gtword EOF","",errno);
6166                 if (errno == EINTR && ++eintr < 4) /* When bg'd process is */
6167                   continue;             /* fg'd again. */
6168 #endif /* EINTR */
6169                 return(-4);
6170             }
6171             c &= cmdmsk;                /* Strip any parity bit */
6172         }                               /* if desired. */
6173
6174 /* Now we have the next character */
6175
6176         isesc = (c == ESC);             /* A real ESC? */
6177
6178         if (!firstnb && c > SP) {       /* First nonblank */
6179             firstnb = c;
6180             if (c == '"')               /* Starts with doublequote */
6181               dq = 1;
6182         }
6183         if (c == '"')                   /* Count doublequotes */
6184           dqn++;
6185
6186         if (quote && (c == CR || c == LF)) { /* Enter key following quote */
6187             *bp++ = CMDQ;               /* Double it */
6188             *bp = NUL;
6189             quote = 0;
6190         }
6191         if (quote == 0) {               /* If this is not a quoted character */
6192             switch (c) {
6193               case CMDQ:                /* Got the quote character itself */
6194                 if (!comment && quoting)
6195                   quote = 1;            /* Flag it if not in a comment */
6196                 break;
6197               case FF:                  /* Formfeed. */
6198                 c = NL;                 /* Replace with newline */
6199                 cmdclrscn();            /* Clear the screen */
6200                 break;
6201               case HT:                  /* Horizontal Tab */
6202                 if (comment)            /* If in comment, */
6203                   c = SP;               /* substitute space */
6204                 else                    /* otherwise */
6205                   c = ESC;              /* substitute ESC (for completion) */
6206                 break;
6207               case ';':                 /* Trailing comment */
6208               case '#':
6209                 if (inword == 0 && quoting) { /* If not in a word */
6210                     comment = 1;        /* start a comment. */
6211                     cp = bp;            /* remember where it starts. */
6212                 }
6213                 break;
6214             }
6215             if (!kstartactive &&        /* Not in possible Kermit packet */
6216                 !comment && c == SP) {  /* Space not in comment */
6217                 *bp++ = (char) c;       /* deposit in buffer if not already */
6218                 /* debug(F101,"gtword echof 2","",echof); */
6219 #ifdef BEBOX
6220                 if (echof) {
6221                     putchar(c);         /* echo it. */
6222                     fflush(stdout);
6223                     fflush(stderr);
6224                 }
6225 #else
6226                 if (echof) {            /* echo it. */
6227                     putchar((CHAR)c);
6228                     if (timelimit)
6229                       fflush(stdout);
6230                 }
6231 #endif /* BEBOX */
6232                 if (inword == 0) {      /* If leading, gobble it. */
6233                     pp++;
6234                     continue;
6235                 } else {                /* If terminating, return. */
6236                     if ((!dq && ((*pp != lbrace) || (bracelvl == 0))) ||
6237                         (dq && dqn > 1 && *(bp-2) == '"')) {
6238                         np = bp;
6239                         cmbptr = np;
6240                         if (setatm(pp,0) < 0) {
6241                             printf("?Field too long error 1\n");
6242                             debug(F111,"gtword too long #1",pp,strlen(pp));
6243                             return(-9);
6244                         }
6245                         brkchar = c;
6246                         inword = cmflgs = 0;
6247                         return(0);
6248                     }
6249                     continue;
6250                 }
6251             }
6252             if (c == lbrace) {
6253                 bracelvl++;
6254                 /* debug(F101,"gtword bracelvl++","",bracelvl); */
6255             }
6256             if (c == rbrace && bracelvl > 0) {
6257                 bracelvl--;
6258                 /* debug(F101,"gtword bracelvl--","",bracelvl); */
6259                 if (linebegin)
6260                   blocklvl--;
6261             }
6262             if ((c == '=' || c == ':') &&
6263                 !kstartactive && !comment && brk && (firstnb == '/')
6264                 ) {
6265                 *bp++ = (char) c;       /* Switch argument separator */
6266                 /* debug(F111,"gtword switch argsep",cmdbuf,brk); */
6267 #ifdef BEBOX
6268                 if (echof) {
6269                     putchar(c);         /* Echo it. */
6270                     fflush(stdout);
6271                     fflush(stderr);
6272                 }
6273 #else
6274                 if (echof) {
6275                     putchar((CHAR)c);
6276                     if (timelimit)
6277                       fflush(stdout);
6278                 }
6279 #endif /* BEBOX */
6280                 if ((*pp != lbrace) || (bracelvl == 0)) {
6281                     np = bp;
6282                     cmbptr = np;
6283                     if (setatm(pp,0) < 0) {
6284                         printf("?Field too long error 1\n");
6285                         debug(F111,"gtword too long #1",pp,strlen(pp));
6286                         return(-9);
6287                     }
6288                     inword = cmflgs = 0;
6289                     brkchar = c;
6290                     return(4);
6291                 }
6292             }
6293             if (c == LF || c == CR) {   /* CR or LF. */
6294                 if (echof) {
6295                     cmdnewl((char)c);   /* echo it. */
6296 #ifdef BEBOX
6297                     fflush(stdout);
6298                     fflush(stderr);
6299 #endif /* BEBOX */
6300                 }
6301                 {
6302                     /* Trim trailing comment and whitespace */
6303                     char *qq;
6304                     if (comment) {      /* Erase comment */
6305                         while (bp >= cp) /* Back to comment pointer */
6306                           *bp-- = NUL;
6307                         bp++;
6308                         pp = bp;        /* Adjust other pointers */
6309                         inword = 0;     /* and flags */
6310                         comment = 0;
6311                         cp = NULL;
6312                     }
6313                     qq = inword ? pp : (char *)cmdbuf;
6314                     /* Erase trailing whitespace */
6315                     while (bp > qq && (*(bp-1) == SP || *(bp-1) == HT)) {
6316                         bp--;
6317                         /* debug(F000,"erasing","",*bp); */
6318                         *bp = NUL;
6319                     }
6320                     lastchar = (bp > qq) ? *(bp-1) : NUL;
6321                     prevchar = (bp > qq+1) ? *(bp-2) : NUL;
6322                 }
6323                 if (linebegin && blocklvl > 0) /* Blank line in {...} block */
6324                   continue;
6325
6326                 linebegin = 1;          /* At beginning of next line */
6327                 iscontd = prevchar != CMDQ &&
6328                   (lastchar == '-' || lastchar == lbrace);
6329                 debug(F101,"gtword iscontd","",iscontd);
6330
6331                 if (iscontd) {          /* If line is continued... */
6332                     if (chsrc) {        /* If reading from tty, */
6333                         if (*(bp-1) == lbrace) { /* Check for "begin block" */
6334                             *bp++ = SP; /* Insert a space for neatness */
6335                             blocklvl++; /* Count block nesting level */
6336                         } else {        /* Or hyphen */
6337                             bp--;       /* Overwrite the hyphen */
6338                         }
6339                         *bp = NUL;      /* erase the dash, */
6340                         continue;       /* and go back for next char now. */
6341                     }
6342                 } else if (blocklvl > 0) { /* No continuation character */
6343                     if (chsrc) {        /* But we're in a "block" */
6344                         *bp++ = ',';    /* Add comma */
6345                         *bp = NUL;
6346                         continue;
6347                     }
6348                 } else {                /* No continuation, end of command. */
6349                     *bp = NUL;          /* Terminate the command string. */
6350                     if (comment) {      /* If we're in a comment, */
6351                         comment = 0;    /* Say we're not any more, */
6352                         *cp = NUL;      /* cut it off. */
6353                     }
6354                     np = bp;            /* Where to start next field. */
6355                     cmbptr = np;
6356                     if (setatm(pp,0) < 0) { /* Copy field to atom buffer */
6357                         debug(F111,"gtword too long #2",pp,strlen(pp));
6358                         printf("?Field too long error 2\n");
6359                         return(-9);
6360                     }
6361                     inword = 0;         /* Not in a word any more. */
6362                     crflag = 1;
6363                     /* debug(F110,"gtword","crflag is set",0); */
6364 #ifdef CK_RECALL
6365                     current = rlast;
6366 #endif /* CK_RECALL */
6367                     cmflgs = 1;
6368                     if (!xcmdsrc
6369 #ifdef CK_RECALL
6370                         || force_add
6371 #endif /* CK_RECALL */
6372                         )
6373                       addcmd(cmdbuf);
6374                     return(cmflgs);
6375                 }
6376             }
6377 /*
6378   This section handles interactive help, completion, editing, and history.
6379   Rearranged as a switch statement executed only if we're at top level since
6380   there is no need for any of this within command files and macros: Aug 2000.
6381   Jun 2001: Even if at top level, skip this if the character was fetched from
6382   the reparse or recall buffer, or if stdin is redirected.
6383 */
6384             if ((xcmdsrc == 0           /* Only at top level */
6385 #ifndef NOSPL
6386                 || askflag              /* or user is typing ASK response */
6387 #endif /* NOSPL */
6388                  ) && chsrc != 0 && realtty) { /* from the real keyboard */
6389
6390 /* Use ANSI / VT100 up and down arrow keys for command recall.  */
6391
6392                 if (isesc && (
6393 #ifdef IKSD
6394                     inserver
6395 #else
6396                     0
6397 #endif /* IKSD */
6398 #ifdef USE_ARROWKEYS
6399                               || 1
6400 #endif /* USE_ARROWKEYS */
6401                              )
6402                      ) {                /* A real ESC was typed */
6403                     int x;
6404                     msleep(200);        /* Wait 1/5 sec */
6405                     x = cmdconchk();    /* Was it followed by anything? */
6406                     debug(F101,"Arrowkey ESC cmdconchk","",x);
6407
6408                     if (x > 1) {        /* If followed by at least 2 chars */
6409                         int c2;
6410                         c2 = cmdgetc(0); /* Get the first one */
6411                         debug(F101,"Arrowkey ESC c2","",c2);
6412
6413                         if (c2 != '[' && c2 != 'O') { /* If not [ or O */
6414                             pushc = c2; /* Push it and take the ESC solo */
6415                         } else {
6416                             c2 = cmdgetc(0); /* Get the second one */
6417                             debug(F101,"Arrowkey ESC c3","",c2);
6418                             switch (c2) {
6419 #ifndef NORECALL
6420                               case 'A': /* Up */
6421                                 c = BEL;
6422                                 c = C_UP;
6423                                 break;
6424                               case 'B': /* Down */
6425                                 c = BEL;
6426                                 c = C_DN;
6427                                 break;
6428                               case 'C': /* Right */
6429                               case 'D': /* Left */
6430 #else
6431                               default:
6432 #endif /* NORECALL */
6433                                 c = BEL; /* We don't use these yet */
6434                                 break;
6435                             }
6436                         }
6437                     }
6438                 }
6439
6440                 switch (c) {
6441                   case '?':             /* ?-Help */
6442 #ifndef NOSPL
6443                     if (askflag)        /* No help in ASK response */
6444                       break;
6445 #endif /* NOSPL */
6446                     if (quoting
6447                         && !kstartactive
6448                         && !comment
6449                         ) {
6450                         putchar((CHAR)c);
6451                         *bp = NUL;
6452                         if (setatm(pp,0) < 0) {
6453                             debug(F111,"gtword too long ?",pp,strlen(pp));
6454                             printf("?Too long\n");
6455                             return(-9);
6456                         }
6457                         qmflag = 1;
6458                         return(cmflgs = 3);
6459                     }
6460
6461                   case ESC:             /* Esc or Tab completion */
6462                     if (!comment) {
6463                         *bp = NUL;
6464                         if (setatm(pp,0) < 0) {
6465                             debug(F111,"gtword too long Esc",pp,strlen(pp));
6466                             printf("?Too long\n");
6467                             return(-9);
6468                         }
6469                         esflag = 1;
6470                         return(cmflgs = 2);
6471                     } else {
6472                         bleep(BP_WARN);
6473                         continue;
6474                     }
6475
6476                   case BS:              /* Character deletion */
6477                   case RUB:
6478                     if (bp > cmdbuf) {  /* If still in buffer... */
6479                         cmdchardel();   /* erase it. */
6480                         bp--;           /* point behind it, */
6481                         if (*bp == lbrace) bracelvl--; /* Adjust brace count */
6482                         if (*bp == rbrace) bracelvl++;
6483                         if ((*bp == SP) && /* Flag if current field gone */
6484                             (*pp != lbrace || bracelvl == 0))
6485                           inword = 0;
6486                         *bp = NUL;      /* Erase character from buffer. */
6487                     } else {            /* Otherwise, */
6488                         bleep(BP_WARN);
6489                         cmres();        /* and start parsing a new command. */
6490                         *bp = *atmbuf = NUL;
6491                     }
6492                     if (pp < bp)
6493                       continue;
6494                     else
6495                       return(cmflgs = -1);
6496
6497                   case LDEL:            /* ^U, line deletion */
6498                     while ((bp--) > cmdbuf) {
6499                         cmdchardel();
6500                         *bp = NUL;
6501                     }
6502                     cmres();            /* Restart the command. */
6503                     *bp = *atmbuf = NUL;
6504                     inword = 0;
6505                     return(cmflgs = -1);
6506
6507                   case WDEL:            /* ^W, word deletion */
6508                     if (bp <= cmdbuf) { /* Beep if nothing to delete */
6509                         bleep(BP_WARN);
6510                         cmres();
6511                         *bp = *atmbuf = NUL;
6512                         return(cmflgs = -1);
6513                     }
6514                     bp--;
6515                     /* Back up over any trailing nonalphanums */
6516                     /* This is dependent on ASCII collating sequence */
6517                     /* but isalphanum() is not available everywhere. */
6518                     for ( ;
6519                          (bp >= cmdbuf) &&
6520                          ((*bp < '0') ||
6521                          ((*bp > '9') && (*bp < '@')) ||
6522                          ((*bp > 'Z') && (*bp < 'a')) ||
6523                          (*bp > 'z'));
6524                          bp--
6525                          ) {
6526                         cmdchardel();
6527                         *bp = NUL;
6528                     }
6529                     /* Now delete back to rightmost remaining nonalphanum */
6530                     for ( ; (bp >= cmdbuf) && (*bp) ; bp--) {
6531                         if ((*bp < '0') ||
6532                             (*bp > '9' && *bp < '@') ||
6533                             (*bp > 'Z' && *bp < 'a') ||
6534                             (*bp > 'z'))
6535                           break;
6536                         cmdchardel();
6537                         *bp = NUL;
6538                     }
6539                     bp++;
6540                     inword = 0;
6541                     return(cmflgs = -1);
6542
6543                   case RDIS: {          /* ^R, redisplay */
6544                       char *cpx; char cx;
6545                       *bp = NUL;
6546                       printf("\n%s",cmprom);
6547                       cpx = cmdbuf;
6548                       while ((cx = *cpx++)) {
6549 #ifdef isprint
6550                           putchar((CHAR) (isprint(cx) ? cx : '^'));
6551 #else
6552                           putchar((CHAR) ((cx >= SP && cx < DEL) ? cx : '^'));
6553 #endif /* isprint */
6554                       }
6555                       fflush(stdout);
6556                       continue;
6557                   }
6558                 }
6559
6560
6561 #ifdef CK_RECALL
6562                 if (on_recall &&        /* Reading commands from keyboard? */
6563                     (cm_recall > 0) &&  /* Saving commands? */
6564                     (c == C_UP || c == C_UP2)) { /* Go up one */
6565                     if (last_recall == 2 && current > 0)
6566                       current--;
6567                     if (current < 0) {  /* Nowhere to go, */
6568                         bleep(BP_WARN);
6569                         continue;
6570                     }
6571                     if (recall[current]) { /* We have a previous command */
6572                         while ((bp--) > cmdbuf) { /* Erase current line */
6573                             cmdchardel();
6574                             *bp = NUL;
6575                         }
6576                         ckstrncpy(cmdbuf,recall[current],CMDBL);
6577 #ifdef OSK
6578                         fflush(stdout);
6579                         write(fileno(stdout), "\r", 1);
6580                         printf("%s%s",cmprom,cmdbuf);
6581 #else
6582                         printf("\r%s%s",cmprom,cmdbuf);
6583 #endif /* OSK */
6584                         current--;
6585                     }
6586                     last_recall = 1;
6587                     return(cmflgs = -1); /* Force a reparse */
6588                 }
6589                 if (on_recall &&        /* Reading commands from keyboard? */
6590                     (cm_recall > 0) &&  /* Saving commands? */
6591                     (c == C_DN)) {      /* Down one */
6592                     int x = 1;
6593                     if (last_recall == 1)
6594                       x++;
6595                     if (current + x > rlast) { /* Already at bottom, beep */
6596                         bleep(BP_WARN);
6597                         continue;
6598                     }
6599                     current += x;       /* OK to go down */
6600                     if (recall[current]) {
6601                         while ((bp--) > cmdbuf) { /* Erase current line */
6602                             cmdchardel();
6603                             *bp = NUL;
6604                         }
6605                         ckstrncpy(cmdbuf,recall[current],CMDBL);
6606 #ifdef OSK
6607                         fflush(stdout);
6608                         write(fileno(stdout), "\r", 1);
6609                         printf("%s%s",cmprom,cmdbuf);
6610 #else
6611                         printf("\r%s%s",cmprom,cmdbuf);
6612 #endif /* OSK */
6613                         last_recall = 2;
6614                         return(cmflgs = -1); /* Force reparse */
6615                     }
6616                 }
6617 #endif /* CK_RECALL */
6618             }
6619
6620             if (c < SP && quote == 0) { /* Any other unquoted control char */
6621                 if (!chsrc) {           /* If cmd file, point past it */
6622                     bp++;
6623                 } else {
6624                     bleep(BP_WARN);
6625                 }
6626                 continue;               /* continue, don't put in buffer */
6627             }
6628             linebegin = 0;              /* Not at beginning of line */
6629 #ifdef BEBOX
6630             if (echof) {
6631                 cmdecho((char) c, 0);   /* Echo what was typed. */
6632                 fflush (stdout);
6633                 fflush(stderr);
6634             }
6635 #else
6636             if (echof) cmdecho((char) c, 0); /* Echo what was typed. */
6637 #endif /* BEBOX */
6638         } else {                        /* This character was quoted. */
6639             int qf = 1;
6640             quote = 0;                  /* Unset the quote flag. */
6641             /* debug(F000,"gtword quote 0","",c); */
6642             /* Quote character at this level is only for SP, ?, and controls */
6643             /* If anything else was quoted, leave quote in, and let */
6644             /* the command-specific parsing routines handle it, e.g. \007 */
6645             if (c > 32 && c != '?' && c != RUB && chsrc != 0) {
6646                 /* debug(F000,"gtword quote 1","",c); */
6647                 *bp++ = CMDQ;           /* Deposit \ if it came from tty */
6648                 qf = 0;                 /* and don't erase it from screen */
6649                 linebegin = 0;          /* Not at beginning of line */
6650 #ifdef BS_DIRSEP
6651 /*
6652   This is a hack to handle "cd \" or "cd foo\" on OS/2 and similar systems.
6653   If we were called from cmdir() and the previous character was the quote
6654   character, i.e. backslash, and this character is the command terminator,
6655   then we stuff an extra backslash into the buffer without echoing, then
6656   we stuff the carriage return back in again, and go back and process it,
6657   this time with the quote flag off.
6658 */
6659             } else if (dirnamflg && (c == CR || c == LF || c == SP)) {
6660                 /* debug(F000,"gtword quote 2","",c); */
6661                 *bp++ = CMDQ;
6662                 linebegin = 0;          /* Not at beginning of line */
6663                 *bp = (c == SP ? SP : CR);
6664                 goto CMDIRPARSE;
6665 #endif /* BS_DIRSEP */
6666             }
6667 #ifdef BEBOX
6668             if (echof) {
6669                 cmdecho((char) c, qf);  /* Echo what was typed. */
6670                 fflush (stdout);
6671                 fflush(stderr);
6672             }
6673 #else
6674             if (echof) cmdecho((char) c, qf); /* Now echo quoted character */
6675 #endif /* BEBOX */
6676             /* debug(F111,"gtword quote",cmdbuf,c); */
6677         }
6678 #ifdef COMMENT
6679         if (echof) cmdecho((char) c,quote); /* Echo what was typed. */
6680 #endif /* COMMENT */
6681         if (!comment) inword = 1;       /* Flag we're in a word. */
6682         if (quote) continue;            /* Don't deposit quote character. */
6683         if (c != NL) {                  /* Deposit command character. */
6684             *bp++ = (char) c;           /* and make sure there is a NUL */
6685 #ifdef COMMENT
6686             *bp = NUL;                  /* after it */
6687 #endif /* COMMENT */
6688         }
6689     }                                   /* End of big while */
6690     bleep(BP_WARN);
6691     printf("?Command too long, maximum length: %d.\n",CMDBL);
6692     cmflgs = -2;
6693     return(-9);
6694 }
6695
6696 /* Utility functions */
6697
6698 /* A D D B U F  -- Add the string pointed to by cp to the command buffer  */
6699
6700 static int
6701 addbuf(cp) char *cp; {
6702     int len = 0;
6703     while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
6704         *bp++ = *cp++;                  /* Copy and */
6705         len++;                          /* count the characters. */
6706     }
6707     *bp++ = SP;                         /* Put a space at the end */
6708     *bp = NUL;                          /* Terminate with a null */
6709     np = bp;                            /* Update the next-field pointer */
6710     cmbptr = np;
6711     return(len);                        /* Return the length */
6712 }
6713
6714 /*  S E T A T M  --  Deposit a token in the atom buffer.  */
6715 /*
6716   Break on space, newline, carriage return, or NUL.
6717   Call with:
6718     cp = Pointer to string to copy to atom buffer.
6719     fcode = 0 means break on whitespace or EOL.
6720     fcode = 1 means don't break on space.
6721     fcode = 2 means break on space, ':', or '='.
6722     fcode = 3 means copy the whole string.
6723   Null-terminate the result.
6724   Return length of token, and also set global "cc" to this length.
6725   Return -1 if token was too long.
6726 */
6727 static int
6728 setatm(cp,fcode) char *cp; int fcode; {
6729     char *ap, *xp, *dqp = NULL, lbrace, rbrace;
6730     int bracelvl = 0, dq = 0;
6731
6732     register char * s;
6733     register int n = 0;
6734
6735     if (cmfldflgs & 1) {                /* Handle grouping */
6736         lbrace = '(';
6737         rbrace = ')';
6738     } else {
6739         lbrace = '{';
6740         rbrace = '}';
6741     }
6742     cc = 0;                             /* Character counter */
6743     ap = atmbuf;                        /* Address of atom buffer */
6744
6745     s = cp;
6746
6747     while (*s++) n++;                   /* Save a call to strlen */
6748
6749     if (n > ATMBL) {
6750         printf("?Command buffer overflow\n");
6751         return(-1);
6752     }
6753     /* debug(F111,"setatm",cp,n); */
6754     if (cp == ap) {                     /* In case source is atom buffer */
6755         xp = atybuf;                    /* make a copy */
6756 #ifdef COMMENT
6757         strncpy(xp,ap,ATMBL);           /* so we can copy it back, edited. */
6758         cp = xp;
6759 #else
6760         s = ap;
6761         while ((*xp++ = *s++)) ;        /* We already know it's big enough */
6762         cp = xp = atybuf;
6763 #endif /* COMMENT */
6764     }
6765     *ap = NUL;                          /* Zero the atom buffer */
6766     if (fcode == 1) {                   /* Trim trailing blanks */
6767         while (--n >= 0 && cp[n] == SP)
6768           ;
6769         cp[n+1] = NUL;
6770     }
6771     while (*cp == SP) {                 /* Trim leading spaces */
6772         cp++;
6773         n--;
6774     }
6775     if (*cp == '"') {                   /* Starts with doublequote? */
6776         dq = 1;
6777         dqp = cp;
6778     }
6779     while (*cp) {
6780         if (*cp == lbrace)
6781           bracelvl++;
6782         else if (*cp == rbrace)
6783           bracelvl--;
6784         if (bracelvl < 0)
6785           bracelvl = 0;
6786         if (bracelvl == 0) {
6787             if (dq) {
6788                 if (*cp == SP || *cp == HT) {
6789                     if (cp > dqp+1) {
6790                         if (*(cp-1) == '"' && *(cp-2) != CMDQ) {
6791                             break;
6792                         }
6793                     }
6794                 }
6795             } else if ((*cp == SP || *cp == HT) && fcode != 1 && fcode != 3)
6796               break;
6797             if ((fcode == 2) && (*cp == '=' || *cp == ':')) break;
6798             if ((fcode != 3) && (*cp == LF || *cp == CR)) break;
6799         }
6800         *ap++ = *cp++;
6801         cc++;
6802     }
6803     *ap = NUL;                          /* Terminate the string. */
6804     /* debug(F111,"setatm result",atmbuf,cc); */
6805     return(cc);                         /* Return length. */
6806 }
6807
6808 /*
6809   These functions attempt to hide system dependencies from the mainline
6810   code in gtword().  Dummy arg for cmdgetc() needed for compatibility with
6811   coninc(), ttinc(), etc, since a pointer to this routine can be passed in
6812   place of those to tn_doop().
6813
6814   No longer static.  Used by askmore().  Fri Aug 20 15:03:34 1999.
6815 */
6816 #define CMD_CONINC                      /* How we get keyboard chars */
6817
6818 int
6819 cmdgetc(timelimit) int timelimit; {     /* Get a character from the tty. */
6820     int c;
6821 #ifdef IKSD
6822     extern int inserver;
6823 #endif /* IKSD */
6824 #ifdef CK_LOGIN
6825     extern int x_logged;
6826 #endif /* CK_LOGIN */
6827 #ifdef TNCODE
6828     static int got_cr = 0;
6829     extern int ckxech;
6830     int tx = 0, is_tn = 0;
6831 #endif /* TNCODE */
6832
6833     if (pushc
6834 #ifndef NOSPL
6835         && !askflag
6836 #endif /* NOSPL */
6837         ) {
6838         debug(F111,"cmdgetc()","pushc",pushc);
6839         c = pushc;
6840         pushc = NUL;
6841         if (xcmfdb && c == '?')         /* Don't echo ? twice if chaining. */
6842           cmdchardel();
6843         return(c);
6844     }
6845 #ifdef datageneral
6846     {
6847         char ch;
6848         c = dgncinb(0,&ch,1);           /* -1 is EOF, -2 TO,
6849                                          * -c is AOS/VS error */
6850         if (c == -2) {                  /* timeout was enabled? */
6851             resto(channel(0));          /* reset timeouts */
6852             c = dgncinb(0,&ch,1);       /* retry this now! */
6853         }
6854         if (c < 0) return(-4);          /* EOF or some error */
6855         else c = (int) ch & 0177;       /* Get char without parity */
6856 /*      echof = 1; */
6857     }
6858 #else /* Not datageneral */
6859 #ifndef MINIX2
6860     if (
6861 #ifdef IKSD
6862         (!local && inserver) ||
6863 #endif /* IKSD */
6864         timelimit > 0) {
6865 #ifdef TNCODE
6866           GETNEXTCH:
6867             is_tn = !pushc && !local && sstelnet;
6868 #endif /* TNCODE */
6869 #ifdef COMMENT
6870             c = coninc(timelimit > 0 ? 1 : 0);
6871 #else /* COMMENT */
6872             /* This is likely to break the asktimeout... */
6873             c = coninc(timelimit);
6874 #endif /* COMMENT */
6875             /* debug(F101,"cmdgetc coninc","",c); */
6876 #ifdef TNCODE
6877             if (c >= 0 && is_tn) {      /* Server-side Telnet */
6878                 switch (c) {
6879                   case IAC:
6880                     /* debug(F111,"gtword IAC","c",c); */
6881                     got_cr = 0;
6882                     if ((tx = tn_doop((CHAR)(c & 0xff),ckxech,coninc)) == 0) {
6883                         goto GETNEXTCH;
6884                     } else if (tx <= -1) { /* I/O error */
6885                         /* If there was a fatal I/O error then ttclos()    */
6886                         /* has been called and the next GETNEXTCH attempt  */
6887                         /* will be !is_tn since ttclos() sets sstelnet = 0 */
6888                         doexit(BAD_EXIT,-1); /* (or return(-4)? */
6889                     } else if (tx == 1) { /* ECHO change */
6890                         ckxech = dpx = 1; /* Get next char */
6891                         goto GETNEXTCH;
6892                     } else if (tx == 2) { /* ECHO change */
6893                         ckxech = dpx = 0; /* Get next char */
6894                         goto GETNEXTCH;
6895                     } else if (tx == 3) { /* Quoted IAC */
6896                         c = 255;        /* proceeed with it. */
6897                     }
6898 #ifdef IKS_OPTION
6899                     else if (tx == 4) { /* IKS State Change */
6900                         goto GETNEXTCH;
6901                     }
6902 #endif /* IKS_OPTION */
6903                     else if (tx == 6) { /* Remote Logout */
6904                         doexit(GOOD_EXIT,0);
6905                     } else {
6906                         goto GETNEXTCH; /* Unknown, get next char */
6907                     }
6908                     break;
6909 #ifdef COMMENT
6910                   case CR:
6911                     if (!TELOPT_U(TELOPT_BINARY)) {
6912                         if (got_cr) {
6913                             /* This means the sender is violating Telnet   */
6914                             /* protocol because we received two CRs in a   */
6915                             /* row without getting either LF or NUL.       */
6916                             /* This will not solve the problem but it      */
6917                             /* will at least allow two CRs to do something */
6918                             /* whereas before the user would have to guess */
6919                             /* to send LF or NUL after the CR.             */
6920                             debug(F100,"gtword CR telnet error","",0);
6921                             c = LF;
6922                         } else {
6923                             debug(F100,"gtword skipping CR","",0);
6924                             got_cr = 1; /* Remember a CR was received */
6925                             goto GETNEXTCH;
6926                         }
6927                     } else {
6928                         debug(F100,"gtword CR to LF","",0);
6929                         c = LF;
6930                     }
6931                     break;
6932                   case LF:
6933                     if (!TELOPT_U(TELOPT_BINARY)) {
6934                         got_cr = 0;
6935                         debug(F100,"gtword LF","",0);
6936                     } else {
6937                         if (got_cr) {
6938                             got_cr = 0;
6939                             debug(F100,"gtword skipping LF","",0);
6940                             goto GETNEXTCH;
6941                         }
6942                     }
6943                     break;
6944                   case NUL:
6945                     if (!TELOPT_U(TELOPT_BINARY) && got_cr) {
6946                         c = LF;
6947                         debug(F100,"gtword NUL to LF","",0);
6948                     } else {
6949                         debug(F100,"gtword NUL","",0);
6950                     }
6951                     got_cr = 0;
6952                     break;
6953 #else /* COMMENT */
6954                   case CR:
6955                     if ( !TELOPT_U(TELOPT_BINARY) && got_cr ) {
6956                         /* This means the sender is violating Telnet   */
6957                         /* protocol because we received two CRs in a   */
6958                         /* row without getting either LF or NUL.       */
6959                         /* This will not solve the problem but it      */
6960                         /* will at least allow two CRs to do something */
6961                         /* whereas before the user would have to guess */
6962                         /* to send LF or NUL after the CR.             */
6963                         debug(F100,"gtword CR telnet error","",0);
6964                     } else {
6965                         got_cr = 1;     /* Remember a CR was received */
6966                     }
6967                     /* debug(F100,"gtword CR to LF","",0); */
6968                     c = LF;
6969                     break;
6970                   case LF:
6971                     if (got_cr) {
6972                         got_cr = 0;
6973                         /* debug(F100,"gtword skipping LF","",0); */
6974                         goto GETNEXTCH;
6975                     }
6976                     break;
6977                   case NUL:
6978                     if (got_cr) {
6979                         got_cr = 0;
6980                         /* debug(F100,"gtword skipping NUL","",0); */
6981                         goto GETNEXTCH;
6982 #ifdef COMMENT
6983                     } else {
6984                       debug(F100,"gtword NUL","",0);
6985 #endif /* COMMENT */
6986                     }
6987                     break;
6988 #endif /* COMMENT */
6989 #ifdef IKSD
6990                   case ETX:             /* Ctrl-C... */
6991                   case EOT:             /* EOT = EOF */
6992                       if (inserver
6993 #ifdef CK_LOGIN
6994                           && !x_logged
6995 #endif /* CK_LOGIN */
6996                           )
6997                           return(-4);
6998                     break;
6999 #endif /* IKSD */
7000                   default:
7001                       got_cr = 0;
7002                 }
7003             }
7004 #endif /* TNCODE */
7005     } else {
7006 #ifdef OS2
7007         c = coninc(0);
7008 #else /* OS2 */
7009 #ifdef CMD_CONINC
7010 #undef CMD_CONINC
7011 #endif /* CMD_CONINC */
7012         c = getchar();
7013 #endif /* OS2 */
7014     }
7015 #else  /* MINIX2 */
7016 #undef getc
7017 #ifdef CMD_CONINC
7018 #undef CMD_CONINC
7019 #endif /* CMD_CONINC */
7020     c = getc(stdin);
7021     /* debug(F101,"cmdgetc getc","",c); */
7022 #endif /* MINIX2 */
7023 #ifdef RTU
7024     if (rtu_bug) {
7025 #ifdef CMD_CONINC
7026 #undef CMD_CONINC
7027 #endif /* CMD_CONINC */
7028         c = getchar();                  /* RTU doesn't discard the ^Z */
7029         rtu_bug = 0;
7030     }
7031 #endif /* RTU */
7032 #endif /* datageneral */
7033     return(c);                          /* Return what we got */
7034 }
7035
7036 /* #ifdef USE_ARROWKEYS */
7037
7038 /* Mechanism to use for peeking into stdin buffer */
7039
7040 #ifndef USE_FILE_CNT                    /* stdin->__cnt */
7041 #ifndef USE_FILE__CNT                   /* Note: two underscores */
7042 #ifdef HPUX                             /* HPUX 7-11 */
7043 #ifndef HPUX5
7044 #ifndef HPUX6
7045 #define USE_FILE__CNT
7046 #endif /* HPUX6 */
7047 #endif /* HPUX5 */
7048 #else
7049 #ifdef ANYSCO                           /* SCO UNIX, OSR5, Unixware, etc */
7050 #ifndef OLD_UNIXWARE                    /* But not Unixware 1.x or 2.0 */
7051 #ifndef UNIXWARE2                       /* or 2.1.0 */
7052 #define USE_FILE__CNT
7053 #endif /* UNIXWARE2 */
7054 #endif /* OLD_UNIXWARE */
7055 #endif /* ANYSCO */
7056 #endif /* HPUX */
7057 #endif /* USE_FILE__CNT */
7058 #endif /* USE_FILE_CNT */
7059
7060 #ifndef USE_FILE_R                      /* stdin->_r */
7061 #ifndef USE_FILE_CNT
7062 #ifndef USE_FILE__CNT
7063 #ifdef BSD44                            /* {Free,Open,Net}BSD, BSDI */
7064 #define USE_FILE_R
7065 #endif /* BSD44 */
7066 #endif /* USE_FILE__CNT */
7067 #endif /* USE_FILE_CNT */
7068 #endif /* USE_FILE_R */
7069
7070 #ifndef USE_FILE_R                      /* stdin->_cnt */
7071 #ifndef USE_FILE_CNT
7072 #ifndef USE_FILE__CNT
7073 #define USE_FILE_CNT                    /* Everybody else (but Linux) */
7074 #endif /* USE_FILE__CNT */
7075 #endif /* USE_FILE_CNT */
7076 #endif /* USE_FILE_R */
7077
7078
7079 /*
7080   c m d c o n c h k
7081
7082   How many characters are waiting to be read at the console?  Normally
7083   conchk() would tell us, but in Unix and VMS cmdgetc() uses stdio getchar(),
7084   thus bypassing coninc()/conchk(), so we have to peek into the stdin buffer,
7085   which is totally nonportable.  Which is why this routine is, at least for
7086   now, used only for checking for arrow-key sequences from the keyboard after
7087   an ESC was read.  Wouldn't it be nice if the stdio package had a function
7088   that returned the number of bytes waiting to be read from its buffer?
7089   Returns 0 or greater always.
7090 */
7091 int
7092 cmdconchk() {
7093     int x = 0, y;
7094     y = pushc ? 1 : 0;                  /* Have command character pushed? */
7095 #ifdef OS2
7096     x = conchk();                       /* Check device-driver buffer */
7097     if (x < 0) x = 0;
7098 #else /* OS2 */
7099 #ifdef CMD_CONINC                       /* See cmdgetc() */
7100     x = conchk();                       /* Check device-driver buffer */
7101     if (x < 0) x = 0;
7102 #else  /* CMD_CONINC */
7103
7104 /* Here we must look inside the stdin buffer - highly platform dependent */
7105
7106 #ifdef _IO_file_flags                   /* Linux */
7107     x = (int) ((stdin->_IO_read_end) - (stdin->_IO_read_ptr));
7108     debug(F101,"cmdconchk _IO_file_flags","",x);
7109 #else  /* _IO_file_flags */
7110 #ifdef USE_FILE_CNT                     /* Traditional */
7111 #ifdef VMS
7112     debug(F101,"cmdconchk (*stdin)->_cnt","",(*stdin)->_cnt);
7113     x = (*stdin)->_cnt;
7114 #else
7115 #ifdef NOARROWKEYS
7116     debug(F101,"cmdconchk NOARROWKEYS x","",0);
7117 #else
7118     debug(F101,"cmdconchk stdin->_cnt","",stdin->_cnt);
7119     x = stdin->_cnt;
7120 #endif /* NOARROWKEYS */
7121 #endif /* VMS */
7122     if (x == 0) x = conchk();
7123     if (x < 0) x = 0;
7124 #else  /* USE_FILE_CNT */
7125 #ifdef USE_FILE__CNT                    /* HP-UX */
7126     debug(F101,"cmdconchk stdin->__cnt","",stdin->__cnt);
7127     x = stdin->__cnt;
7128     if (x == 0) x = conchk();
7129     if (x < 0) x = 0;
7130 #else  /* USE_FILE_CNT */
7131 #ifdef USE_FILE_R                       /* FreeBSD, OpenBSD, etc */
7132     debug(F101,"cmdconchk stdin->_r","",stdin->_r);
7133     x = stdin->_r;
7134     if (x == 0) x = conchk();
7135     if (x < 0) x = 0;
7136
7137     /* Fill in any others here... */
7138
7139 #endif /* USE_FILE_R */
7140 #endif /* USE_FILE__CNT */
7141 #endif /* USE_FILE_CNT */
7142 #endif /* _IO_file_flags */
7143 #endif /* CMD_CONINC */
7144 #endif /* OS2 */
7145     return(x + y);
7146 }
7147 /* #endif */ /* USE_ARROWKEYS */
7148
7149
7150 static VOID
7151 cmdclrscn() {                           /* Clear the screen */
7152     ck_cls();
7153 }
7154
7155 static VOID                             /* What to echo at end of command */
7156 #ifdef CK_ANSIC
7157 cmdnewl(char c)
7158 #else
7159 cmdnewl(c) char c;
7160 #endif /* CK_ANSIC */
7161 /* cmdnewl */ {
7162 #ifdef OS2
7163 #ifdef IKSD
7164     extern int inserver;
7165     if (inserver && c == LF)
7166       putchar(CR);
7167 #endif /* IKSD */
7168 #endif /* OS2 */
7169
7170     putchar(c);                         /* c is the terminating character */
7171
7172 #ifdef WINTCP                           /* what is this doing here? */
7173     if (c == CR) putchar(NL);
7174 #endif /* WINTCP */
7175
7176 /*
7177   A.A. Chernov, who sent in changes for FreeBSD, said we also needed this
7178   for SVORPOSIX because "setup terminal by termios and curses does
7179   not convert \r to \n, so additional \n needed in newline function."  But
7180   it is also very likely to result in unwanted blank lines.
7181 */
7182 #ifdef BSD44
7183     if (c == CR) putchar(NL);
7184 #endif /* BSD44 */
7185
7186 #ifdef COMMENT
7187     /* OS2 no longer needs this as all CR are converted to NL in coninc() */
7188     /* This eliminates the ugly extra blank lines discussed above.        */
7189 #ifdef OS2
7190     if (c == CR) putchar(NL);
7191 #endif /* OS2 */
7192 #endif /* COMMENT */
7193 #ifdef aegis
7194     if (c == CR) putchar(NL);
7195 #endif /* aegis */
7196 #ifdef AMIGA
7197     if (c == CR) putchar(NL);
7198 #endif /* AMIGA */
7199 #ifdef datageneral
7200     if (c == CR) putchar(NL);
7201 #endif /* datageneral */
7202 #ifdef GEMDOS
7203     if (c == CR) putchar(NL);
7204 #endif /* GEMDOS */
7205 #ifdef STRATUS
7206     if (c == CR) putchar(NL);
7207 #endif /* STRATUS */
7208 }
7209
7210 static VOID
7211 cmdchardel() {                          /* Erase a character from the screen */
7212     if (!dpx) return;
7213 #ifdef datageneral
7214     /* DG '\b' is EM (^y or \031) */
7215     if (termtype == 1)
7216       /* Erase a character from non-DG screen, */
7217       dgncoub(1,"\010 \010",3);
7218     else
7219 #endif /* datageneral */
7220       printf("\b \b");
7221 #ifdef GEMDOS
7222     fflush(stdout);
7223 #else
7224 #ifdef BEBOX
7225     fflush(stdout);
7226 #endif /* BEBOX */
7227 #endif /* GEMDOS */
7228 }
7229
7230 static VOID
7231 #ifdef CK_ANSIC
7232 cmdecho(char c, int quote)
7233 #else
7234 cmdecho(c,quote) char c; int quote;
7235 #endif /* CK_ANSIC */
7236 { /* cmdecho */
7237     if (!dpx) return;
7238     /* Echo tty input character c */
7239     if (quote) {
7240         putchar(BS);
7241         putchar(SP);
7242         putchar(BS);
7243 #ifdef isprint
7244         putchar((CHAR) (isprint(c) ? c : '^' ));
7245 #else
7246         putchar((CHAR) ((c >= SP && c < DEL) ? c : '^'));
7247 #endif /* isprint */
7248     } else putchar(c);
7249 #ifdef OS2
7250     if (quote==1 && c==CR) putchar((CHAR) NL);
7251 #endif /* OS2 */
7252     if (timelimit)
7253       fflush(stdout);
7254 }
7255
7256 /* Return pointer to current position in command buffer. */
7257
7258 char *
7259 cmpeek() {
7260     return(np);
7261 }
7262 #endif /* NOICP */
7263
7264
7265 #ifdef NOICP
7266 #include "ckcdeb.h"
7267 #include "ckucmd.h"
7268 #include "ckcasc.h"
7269 #endif /* NOICP */
7270
7271 /*  X X E S C  --  Interprets backslash codes  */
7272 /*  Returns the int value of the backslash code if it is > -1 and < 256 */
7273 /*  and updates the string pointer to first character after backslash code. */
7274 /*  If the argument is invalid, leaves pointer unchanged and returns -1. */
7275
7276 int
7277 xxesc(s) char **s; {                    /* Expand backslash escapes */
7278     int x, y, brace, radix;             /* Returns the int value */
7279     char hd = '9';                      /* Highest digit in radix */
7280     char *p;
7281
7282     p = *s;                             /* pointer to beginning */
7283     if (!p) return(-1);                 /* watch out for null pointer */
7284     x = *p++;                           /* character at beginning */
7285     if (x != CMDQ) return(-1);          /* make sure it's a backslash code */
7286
7287     x = *p;                             /* it is, get the next character */
7288     if (x == '{') {                     /* bracketed quantity? */
7289         p++;                            /* begin past bracket */
7290         x = *p;
7291         brace = 1;
7292     } else brace = 0;
7293     switch (x) {                        /* Start interpreting */
7294       case 'd':                         /* Decimal radix indicator */
7295       case 'D':
7296         p++;                            /* Just point past it and fall thru */
7297       case '0':                         /* Starts with digit */
7298       case '1':
7299       case '2':  case '3':  case '4':  case '5':
7300       case '6':  case '7':  case '8':  case '9':
7301         radix = 10;                     /* Decimal */
7302         hd = '9';                       /* highest valid digit */
7303         break;
7304       case 'o':                         /* Starts with o or O */
7305       case 'O':
7306         radix = 8;                      /* Octal */
7307         hd = '7';                       /* highest valid digit */
7308         p++;                            /* point past radix indicator */
7309         break;
7310       case 'x':                         /* Starts with x or X */
7311       case 'X':
7312         radix = 16;                     /* Hexadecimal */
7313         p++;                            /* point past radix indicator */
7314         break;
7315       default:                          /* All others */
7316 #ifdef COMMENT
7317         *s = p+1;                       /* Treat as quote of next char */
7318         return(*p);
7319 #else
7320         return(-1);
7321 #endif /* COMMENT */
7322     }
7323     /* For OS/2, there are "wide" characters required for the keyboard
7324      * binding, i.e \644 and similar codes larger than 255 (byte).
7325      * For this purpose, give up checking for < 256. If someone means
7326      * \266 should result in \26 followed by a "6" character, he should
7327      * always write \{26}6 anyway.  Now, return only the lower byte of
7328      * the result, i.e. 10, but eat up the whole \266 sequence and
7329      * put the wide result 266 into a global variable.  Yes, that's not
7330      * the most beautiful programming style but requires the least
7331      * amount of changes to other routines.
7332      */
7333     if (*p == '{') {                    /* Sun May 11 20:00:40 2003 */
7334         brace = 1;                      /* Allow {} after radix indicator */
7335         p++;
7336     }
7337     if (radix <= 10) {                  /* Number in radix 8 or 10 */
7338         for ( x = y = 0;
7339               (*p) && (*p >= '0') && (*p <= hd)
7340 #ifdef OS2
7341                    && (y < 5) && (x*radix < KMSIZE);
7342               /* the maximum needed value \8196 is 4 digits long */
7343               /* while as octal it requires \1377, i.e. 5 digits */
7344 #else
7345                    && (y < 3) && (x*radix < 256);
7346 #endif /* OS2 */
7347               p++,y++) {
7348             x = x * radix + (int) *p - 48;
7349         }
7350 #ifdef OS2
7351         wideresult = x;                 /* Remember wide result */
7352         x &= 255;
7353 #endif /* OS2 */
7354         if (y == 0 || x > 255) {        /* No valid digits? */
7355             *s = p;                     /* point after it */
7356             return(-1);                 /* return failure. */
7357         }
7358     } else if (radix == 16) {           /* Special case for hex */
7359         if ((x = unhex(*p++)) < 0) { *s = p - 1; return(-1); }
7360         if ((y = unhex(*p++)) < 0) { *s = p - 2; return(-1); }
7361         x = ((x << 4) & 0xF0) | (y & 0x0F);
7362 #ifdef OS2
7363         wideresult = x;
7364         if ((y = unhex(*p)) >= 0) {
7365            p++;
7366            wideresult = ((x << 4) & 0xFF0) | (y & 0x0F);
7367            x = wideresult & 255;
7368         }
7369 #endif /* OS2 */
7370     } else x = -1;
7371     if (brace && *p == '}' && x > -1)   /* Point past closing brace, if any */
7372       p++;
7373     *s = p;                             /* Point to next char after sequence */
7374     return(x);                          /* Return value of sequence */
7375 }
7376
7377 int                                     /* Convert hex string to int */
7378 #ifdef CK_ANSIC
7379 unhex(char x)
7380 #else
7381 unhex(x) char x;
7382 #endif /* CK_ANSIC */
7383 /* unhex */ {
7384
7385     if (x >= '0' && x <= '9')           /* 0-9 is offset by hex 30 */
7386       return(x - 0x30);
7387     else if (x >= 'A' && x <= 'F')      /* A-F offset by hex 37 */
7388       return(x - 0x37);
7389     else if (x >= 'a' && x <= 'f')      /* a-f offset by hex 57 */
7390       return(x - 0x57);                 /* (obviously ASCII dependent) */
7391     else return(-1);
7392 }
7393
7394 /*  L O O K U P  --  Lookup the string in the given array of strings  */
7395
7396 /*
7397   Call this way:  v = lookup(table,word,n,&x);
7398
7399     table - a 'struct keytab' table.
7400     word  - the target string to look up in the table.
7401     n     - the number of elements in the table.
7402     x     - address of an integer for returning the table array index,
7403             or NULL if you don't need a table index.
7404
7405   The keyword table must be arranged in ascending alphabetical order;
7406   alphabetic case doesn't matter but letters are treated as lowercase
7407   for purposes of ordering; thus "^" and "_" come *before* the letters,
7408   not after them.
7409
7410   Returns the keyword's associated value (zero or greater) if found,
7411   with the variable x set to the keyword-table index.  If is lookup()
7412   is not successful, it returns:
7413
7414    -3 if nothing to look up (target was null),
7415    -2 if ambiguous,
7416    -1 if not found.
7417
7418   A match is successful if the target matches a keyword exactly, or if
7419   the target is a prefix of exactly one keyword.  It is ambiguous if the
7420   target matches two or more keywords from the table.
7421
7422   Lookup() is the critical routine in scripts and so is optimized with a
7423   simple static cache plus some other tricks.  Maybe it could be improved
7424   further with binary search or hash techniques but I doubt it since most
7425   keyword tables are fairly short.
7426 */
7427
7428 #ifdef USE_LUCACHE                      /* Lookup cache */
7429 extern int lusize;                      /* (initialized in ckuus5.c) */
7430 extern char * lucmd[];
7431 extern int luval[];
7432 extern int luidx[];
7433 extern struct keytab * lutab[];
7434 long luhits = 0L;
7435 long lucalls = 0L;
7436 long xxhits = 0L;
7437 long luloop = 0L;
7438 #endif /* USE_LUCACHE */
7439
7440 int
7441 lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {
7442
7443     register int i, m;
7444     int v, len, cmdlen = 0;
7445     char c = NUL, c1, *s;
7446
7447 /* Get 1st char of search object, if it's null return -3. */
7448
7449     if (!cmd || n < 1)                  /* Defense de nullarg */
7450       return(-3);
7451     c1 = *cmd;                          /* First character */
7452     if (!c1)                            /* Make sure there is one */
7453       return(-3);
7454     if (isupper(c1))                    /* If letter make it lowercase */
7455       c1 = tolower(c1);
7456
7457 #ifdef USE_LUCACHE                      /* lookup() cache */
7458     m = lusize;
7459     lucalls++;                          /* Count this lookup() call */
7460     for (i = 0; i < m; i++) {           /* Loop thru cache */
7461         if (*(lucmd[i]) == c1) {        /* Same as 1st char of search item? */
7462             if (lutab[i] == table) {    /* Yes - same table too? */
7463                 if (!strcmp(cmd,lucmd[i])) { /* Yes - compare */
7464                     if (x) *x = luidx[i];    /* Match - return index */
7465                     luhits++;                /* Count cache hit */
7466                     return(luval[i]);        /* Return associated value */
7467                 }
7468             }
7469         }
7470     }
7471 #endif /* USE_LUCACHE */
7472
7473 /* Not null, not in cache, look it up */
7474
7475     s = cmd;
7476     while (*s++) cmdlen++;              /* Length of target */
7477 /*
7478   Quick binary search to find last table entry whose first character is
7479   lexically less than the first character of the search object.  This is
7480   the starting point of the next loop, which must go in sequence since it
7481   compares adjacent table entries.
7482 */
7483     if (n < 5) {                        /* Not worth it for small tables */
7484         i = 0;
7485     } else {
7486         int lo = 0;
7487         int hi = n;
7488         int count = 0;
7489         while (lo+2 < hi && ++count < 12) {
7490             i = lo + ((hi - lo) / 2);
7491             c = *(table[i].kwd);
7492             if (isupper(c)) c = tolower(c);
7493             if (c < c1) {
7494                 lo = i;
7495             } else {
7496                 hi = i;
7497             }
7498         }
7499         i = (c < c1) ? lo+1 : lo;
7500 #ifdef USE_LUCACHE
7501         if (i > 0) xxhits++;
7502 #endif /* USE_LUCACHE */
7503     }
7504     for ( ; i < n-1; i++) {
7505 #ifdef USE_LUCACHE
7506         luloop++;
7507 #endif /* USE_LUCACHE */
7508         v = 0;
7509         c = *(table[i].kwd);
7510         if (c) {
7511             if (isupper(c)) c = tolower(c);
7512
7513             /* The following is a big performance booster but makes it */
7514             /* absolutely essential that all lookup() tables are in order. */
7515
7516             if (c > c1)                 /* Leave early if past our mark */
7517               return(-1);
7518
7519 #ifdef DEBUG
7520             /* Use LOG DEBUG to check */
7521
7522             if (deblog) {
7523                 if (ckstrcmp(table[i].kwd,table[i+1].kwd,0,0) > 0) {
7524                     printf("TABLE OUT OF ORDER [%s] [%s]\n",
7525                            table[i].kwd,table[i+1].kwd);
7526
7527                 }
7528             }
7529 #endif /* DEBUG */
7530
7531             if (c == c1) {
7532                 len = 0;
7533                 s = table[i].kwd;
7534                 while (*s++) len++;
7535                 if ((len == cmdlen && !ckstrcmp(table[i].kwd,cmd,len,0)) ||
7536                     ((v = !ckstrcmp(table[i].kwd,cmd,cmdlen,0)) &&
7537                      ckstrcmp(table[i+1].kwd,cmd,cmdlen,0))) {
7538                     if (x) *x = i;
7539                     return(table[i].kwval);
7540                 }
7541             } else v = 0;
7542         }
7543         if (v) {                        /* Ambiguous */
7544             if (x) *x = i;              /* Set index of first match */
7545             return(-2);
7546         }
7547     }
7548
7549 /* Last (or only) element */
7550
7551     if (!ckstrcmp(table[n-1].kwd,cmd,cmdlen,0)) {
7552         if (x) *x = n-1;
7553         debug(F111,"lookup",table[i].kwd,table);
7554         return(table[n-1].kwval);
7555     } else return(-1);
7556 }
7557
7558 /*
7559   x l o o k u p
7560
7561   Like lookup, but requires a full (but case-independent) match
7562   and does NOT require the table to be in order.
7563 */
7564 int
7565 xlookup(table,cmd,n,x) struct keytab table[]; char *cmd; int n, *x; {
7566     register int i;
7567     int len, cmdlen, one = 0;
7568     register char c, c2, * s, * s2;
7569
7570     if (!cmd) cmd = "";                 /* Check args */
7571     if (!*cmd || n < 1) return(-3);
7572
7573     c = *cmd;                           /* First char of string to look up */
7574     if (!*(cmd+1)) {                    /* Special handling for 1-char names */
7575         cmdlen = 1;
7576         if (isupper(c))
7577           c = tolower(c);
7578         one = 1;
7579     } else {
7580         cmdlen = 0;
7581         s = cmd;
7582         while (*s++) cmdlen++;
7583         c = *cmd;
7584         if (isupper(c))
7585           c = tolower(c);
7586     }
7587     if (cmdlen < 1)
7588       return(-3);
7589
7590     for (i = 0; i < n; i++) {
7591         s = table[i].kwd;               /* This entry */
7592         if (!s) s = "";
7593         if (!*s) continue;              /* Empty table entry */
7594         c2 = *s;
7595         if (isupper(c2)) c2 = tolower(c2);
7596         if (c != c2) continue;          /* First char doesn't match */
7597         if (one) {                      /* Name is one char long */
7598             if (!*(s+1)) {
7599                 if (x) *x = i;
7600                 *cmd = c; 
7601                 return(table[i].kwval); /* So is table entry */
7602             }
7603         } else {                        /* Otherwise do string comparison */
7604             s2 = s;
7605             len = 0;
7606             while (*s2++) len++;
7607             if (len == cmdlen && !ckstrcmp(s,cmd,-1,0)) {
7608                 if (x) *x = i;
7609                 return(table[i].kwval);
7610             }
7611         }
7612     }
7613     return(-1);
7614 }
7615
7616 /* Reverse lookup */
7617
7618 char *
7619 rlookup(table,n,x) struct keytab table[]; int n, x; {
7620     int i;
7621     for (i = 0; i < n; i++) {
7622         if (table[i].kwval == x)
7623           return(table[i].kwd);
7624     }
7625     return(NULL);
7626 }
7627
7628 #ifndef NOICP
7629 int
7630 cmdsquo(x) int x; {
7631     quoting = x;
7632     return(1);
7633 }
7634
7635 int
7636 cmdgquo() {
7637     return(quoting);
7638 }
7639 #endif /* NOICP */