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