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