source: trunk/third/kermit/ckucmd.c @ 20081

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