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