3 char *cmdv = "Command package 8.0.157, 11 May 2003";
5 /* C K U C M D -- Interactive command package for Unix */
8 Author: Frank da Cruz (fdc@columbia.edu),
9 Columbia University Academic Information Systems, New York City.
11 Copyright (C) 1985, 2004,
12 Trustees of Columbia University in the City of New York.
13 All rights reserved. See the C-Kermit COPYING.TXT file or the
14 copyright text in the ckcmai.c module for disclaimer and permissions.
21 /* Command-terminal-to-C-Kermit character mask */
24 int cmdmsk = 255; /* (always was 255) */
25 #else /* All others... */
26 int cmdmsk = 255; /* 31 Dec 2000 (was 127) */
29 #ifdef BS_DIRSEP /* Directory separator is backslash */
31 #endif /* BS_DIRSEP */
35 #endif /* BS_DIRSEP */
39 #include "ckcdeb.h" /* Formats for debug(), etc. */
40 #include "ckcker.h" /* Needed for BIGBUFOK definition */
41 #include "ckcnet.h" /* Needed for server-side Telnet */
42 #include "ckucmd.h" /* Needed for everything */
43 #include "ckuusr.h" /* Needed for prompt length */
48 #define USE_ARROWKEYS /* Use arrow keys for command recall */
49 #endif /* VMSORUNIX */
51 #endif /* NOARROWKEYS */
55 _PROTOTYP( int unhex, (char) );
56 _PROTOTYP( static VOID cmdclrscn, (void) );
59 _PROTOTYP( VOID learncmd, (char *) );
62 static char *moname[] = {
63 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
64 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
67 struct keytab cmonths[] = {
70 { "december", 12, 0 },
77 { "november", 11, 0 },
82 #ifndef NOICP /* The rest only if interactive command parsing selected */
85 _PROTOTYP( int chkvar, (char *) );
97 int cmfldflgs = 0; /* Flags for cmfld() */
98 int cmkwflgs = 0; /* Flags from last keyword parse */
99 static int blocklvl = 0; /* Block nesting level */
100 static int linebegin = 0; /* Flag for at start of a line */
101 static int quoting = 1; /* Quoting is allowed */
102 static int swarg = 0; /* Parsing a switch argument */
103 static int xcmfdb = 0; /* Flag for parsing chained fdbs... */
104 static int chsrc = 0; /* Source of character, 1 = tty */
105 static int newcmd = 0; /* See addcmd() */
108 static int dirnamflg = 0;
109 #endif /* BS_DIRSEP */
112 Modeled after the DECSYSTEM-20 command parser (the COMND JSYS), RIP. Features:
114 . parses and verifies keywords, filenames, text strings, numbers, other data
115 . displays appropriate menu or help message when user types "?"
116 . does keyword and filename completion when user types ESC or TAB
117 . does partial keyword and filename completion
118 . accepts any unique abbreviation for a keyword
119 . allows keywords to have attributes, like "invisible" and "abbreviation"
120 . can supply defaults for fields omitted by user
121 . provides command retry and recall
122 . provides character, word, and line deletion (but only from the end)
123 . accepts input from keyboard, command files, macros, or redirected stdin
124 . allows for full or half duplex operation, character or line input
125 . allows \-escapes for special characters
126 . allows specification of a user exit to expand variables, etc.
127 . settable prompt, protected from deletion, dynamically re-evaluated each time
128 . allows chained parse functions.
131 cmsetp - Set prompt (cmprom is prompt string)
132 cmsavp - Save current prompt
133 cmgetp = Get current prompt
134 prompt - Issue prompt
135 cmini - Clear the command buffer (before parsing a new command)
136 cmres - Reset command buffer pointers (before reparsing)
137 cmkey - Parse a keyword or token (also cmkey2)
138 cmswi - Parse a switch
139 cmnum - Parse a number
140 cmifi - Parse an input file name
141 cmofi - Parse an output file name (also cmifip, cmifi2, ...)
142 cmdir - Parse a directory name (also cmdirp)
143 cmfld - Parse an arbitrary field
144 cmtxt - Parse a text string
145 cmdate - Parse a date-time string
146 cmcfm - Parse command confirmation (end of line)
147 cmfdb - Parse any of a list of the foregoing (chained parse functions)
150 -9: like -2 except this module already printed the error message
151 -3: no input provided when required
152 -2: input was invalid (e.g. not a number when a number was required)
153 -1: reparse required (user deleted into a preceding field)
154 0 or greater: success
155 See individual functions for greater detail.
157 Before using these routines, the caller should #include "ckucmd.h" and set the
158 program's prompt by calling cmsetp(). If the file parsing functions cmifi,
159 cmofi, or cmdir are to be used, this module must be linked with a ck?fio file
160 system support module for the appropriate system, e.g. ckufio for Unix. If
161 the caller puts the terminal in character wakeup ("cbreak") mode with no echo,
162 then these functions will provide line editing -- character, word, and line
163 deletion, as well as keyword and filename completion upon ESC and help
164 strings, keyword, or file menus upon '?'. If the caller puts the terminal
165 into character wakeup/noecho mode, care should be taken to restore it before
166 exit from or interruption of the program. If the character wakeup mode is not
167 set, the system's own line editor may be used.
169 NOTE: Contrary to expectations, many #ifdef's have been added to this module.
170 Any operation requiring an #ifdef (like clear screen, get character from
171 keyboard, erase character from screen, etc) should eventually be turned into a
172 call to a function that is defined in ck?tio.c, but then all the ck?tio.c
173 modules would have to be changed...
179 #include "ckcasc.h" /* ASCII character symbols */
180 #include "ckucmd.h" /* Command parsing definitions */
186 #endif /* _NO_PROTO */
187 #endif /* CK_ANSIC */
190 #include <errno.h> /* Error number symbols */
195 #define INCL_VIO /* Needed for ckocon.h */
207 #define stricmp _stricmp
211 #define cc ccount /* OS-9/68K compiler bug */
214 #ifdef GEMDOS /* Atari ST */
218 #define putchar(x) conoc(x)
222 extern int cmdadl, justone;
223 #endif /* CK_AUTODL */
225 extern int timelimit, nzxopts, nopush, nolocal, xcmdsrc, keepallchars;
229 #ifdef CKXPRINTF /* Our printf macro conflicts with */
230 #undef printf /* use of "printf" in syslog.h */
231 #endif /* CKXPRINTF */
233 #include <sys/syslog.h>
238 #define printf ckxprintf
239 #endif /* CKXPRINTF */
241 #endif /* CKSYSLOG */
243 /* Local variables */
246 int psetf = 0, /* Flag that prompt has been set */
247 cc = 0, /* Character count */
248 dpx = 0, /* Duplex (0 = full) */
249 inword = 0; /* In the middle of getting a word */
251 char *dfprom = "Command? "; /* Default prompt */
253 int cmflgs; /* Command flags */
254 int cmfsav; /* A saved version of them */
256 static char pushc = NUL;
257 static char brkchar = NUL;
259 #define CMDEFAULT 1023
260 static char cmdefault[CMDEFAULT+1];
263 char *cmdbuf = NULL; /* Command buffer */
264 char *savbuf = NULL; /* Buffer to save copy of command */
265 char *atmbuf = NULL; /* Atom buffer - for current field */
266 char *atxbuf = NULL; /* For expanding the atom buffer */
267 static char *atybuf = NULL; /* For copying atom buffer */
268 static char *filbuf = NULL; /* File name buffer */
269 static char *cmprom = NULL; /* Program's prompt */
270 static char *cmprxx = NULL; /* Program's prompt, unevaluated */
274 Command recall is available only if we can make profligate use of malloc().
276 #define R_MAX 10 /* How many commands to save */
277 int cm_recall = R_MAX; /* Size of command recall buffer */
278 int on_recall = 1; /* Recall feature is ON */
279 static int no_recall = 0; /* Recall OFF for this cmd only */
280 static int force_add = 0; /* Force cmd into recall buffer */
281 static int last_recall = -1; /* Last recall-related action */
284 0 = CR (a command was entered)
288 int in_recall = 0; /* Recall buffers are init'd */
290 current = -1, /* Pointer to current command */
291 rlast = -1; /* Index of last command in buffer */
292 static char **recall = NULL; /* Array of recall buffer pointers */
293 #endif /* CK_RECALL */
295 char cmdbuf[CMDBL+4]; /* Command buffer */
296 char savbuf[CMDBL+4]; /* Buffer to save copy of command */
297 char atmbuf[ATMBL+4]; /* Atom buffer */
298 char atxbuf[CMDBL+4]; /* For expanding the atom buffer */
299 static char atybuf[ATMBL+4]; /* For copying atom buffer */
300 static char filbuf[ATMBL+4]; /* File name buffer */
301 static char cmprom[PROMPTL+1]; /* Program's prompt */
302 static char cmprxx[PROMPTL+1]; /* Program's prompt, unevaluated */
305 /* Command buffer pointers */
308 char ppvnambuf[PPVLEN+1] = { NUL, NUL };
310 char * cmbptr = NULL; /* Current position (for export) */
312 static char *bp, /* Current command buffer position */
313 *pp, /* Start of current field */
314 *np; /* Start of next field */
316 static int ungw, /* For ungetting words */
317 atxn; /* Expansion buffer (atxbuf) length */
320 extern int wideresult;
323 extern int cmd_cols, cmd_rows, local, quiet;
332 _PROTOTYP( static int gtword, (int) );
333 _PROTOTYP( static int addbuf, (char *) );
334 _PROTOTYP( static int setatm, (char *, int) );
335 _PROTOTYP( static VOID cmdnewl, (char) );
336 _PROTOTYP( static VOID cmdchardel, (void) );
337 _PROTOTYP( static VOID cmdecho, (char, int) );
338 _PROTOTYP( static int test, (int, int) );
340 _PROTOTYP( extern char *strchr, (char *, int) );
345 /* The following are for use with chained FDB's */
347 static int crflag = 0; /* Carriage return was typed */
348 static int qmflag = 0; /* Question mark was typed */
349 static int esflag = 0; /* Escape was typed */
351 /* Directory separator */
354 static char dirsep = '\\';
357 static char dirsep = ':';
360 static char dirsep = ':';
363 static char dirsep = '.';
366 static char dirsep = '>';
368 static char dirsep = '/'; /* UNIX, OS/2, OS-9, Amiga, etc. */
372 #endif /* datageneral */
375 /* H A S N O P A T H */
377 /* Returns 0 if filespec s includes any path segments; 1 if it doesn't. */
380 hasnopath(s) char * s; {
385 return(ckstrcmp(s,p,CKMAXPATH,filecase) == 0 ? 1 : 0);
388 /* C K S P R E A D -- Print string double-spaced */
390 static char * sprptr = NULL;
393 ckspread(s) char * s; {
399 sprptr = malloc(n + n + 3);
408 return(sprptr ? sprptr : "");
411 /* T E S T -- Bit test */
414 test(x,m) int x, m; { /* Returns 1 if any bits from m are on in x, else 0 */
415 return((x & m) ? 1 : 0);
418 /* K W D H E L P -- Given a keyword table, print keywords in columns. */
422 n - number of entries
423 pat - pattern (left substring) that must match for each keyword
424 pre - prefix to add to each keyword
425 post - suffix to add to each keyword
426 off - offset on first screenful, allowing room for introductory text
427 xhlp - 1 to print any CM_INV keywords that are not also abbreviations.
428 2 to print CM_INV keywords if CM_HLP also set
429 4 if it's a switch table (to show ':' if CM_ARG)
431 Arranges keywords in columns with width based on longest keyword.
432 Does "more?" prompting at end of screen.
433 Uses global cmd_rows and cmd_cols for screen size.
436 kwdhelp(s,n,pat,pre,post,off,xhlp)
437 struct keytab s[]; int n, off, xhlp; char *pat, *pre, *post;
442 int cols, height, i, j, k, lc, n2 = 0;
443 char *b = NULL, *p, *q;
450 if (!s) return; /* Nothing to do */
451 if (n < 1) return; /* Ditto */
452 if (off < 0) off = 0; /* Offset for first page */
453 if (!pre) pre = ""; /* Handle null string pointers */
454 if (!post) post = "";
455 lc = off; /* Screen-line counter */
457 if (xhlp & 4) /* For switches */
458 tmpbuf = (char *)malloc(TMPBUFSIZ+1);
460 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
461 for (i = 0; i < n; i++) { /* Find longest keyword */
463 if (ckstrcmp(s[i].kwd,pat,cc,0))
466 if (s[i].flgs & CM_PSH /* NOPUSH or nopush screening */
472 if (s[i].flgs & CM_LOC /* NOLOCAL or nolocal screening */
479 if (s[i].flgs & CM_INV) {
481 /* This code does not show invisible keywords at all except for "help ?" */
482 /* and then only help topics (CM_HLP) in the top-level keyword list. */
486 else if ((s[i].flgs & CM_HLP) == 0)
489 /* This code shows invisible keywords that are not also abbreviations when */
490 /* ? was typed AFTER the beginning of the field so the user can find out */
491 /* what they are and (for example) why completion doesn't work at this point */
493 if (s[i].flgs & CM_ABR)
495 else if ((xhlp & 3) == 0)
497 else if ((xhlp & 2) && ((s[i].flgs & CM_HLP) == 0))
501 j = strlen(s[i].kwd);
502 if (!(xhlp & 4) || !tmpbuf) { /* Regular keyword table */
503 s2[n2++] = s[i].kwd; /* Copy pointers to visible ones */
504 } else { /* Switches */
505 ckmakmsg(tmpbuf, /* Make a copy that shows ":" if */
506 TMPBUFSIZ, /* the switch takes an argument. */
508 (s[i].flgs & CM_ARG) ? ":" : "",
512 makestr(&(s2[n2]),tmpbuf);
513 if (s[i].flgs & CM_ARG) j++;
522 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
525 width += (int)strlen(pre) + (int)strlen(post) + 2;
526 cols = cmd_cols / width; /* How many columns? */
527 if (cols < 1) cols = 1;
528 height = n / cols; /* How long is each column? */
529 if (n % cols) height++; /* Add one for remainder, if any */
531 for (i = 0; i < height; i++) { /* Loop for each row */
532 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
534 for (j = 0; j < cols; j++) { /* Loop for each column in row */
535 k = i + (j * height); /* Index of next keyword */
536 if (k < n) { /* In range? */
539 p = s2[k]; /* Point to verb name */
540 q = b + (j * width) + 1; /* Where to copy it to */
541 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
542 q--; /* Back up over NUL */
543 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
544 q--; /* Back up over NUL */
545 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
548 *q = SP; /* Replace the space */
552 p = b + cmd_cols - 1; /* Last char in line */
553 while (*p-- == SP) ; /* Trim */
555 printf("%s\n",b); /* Print the line */
556 if (++lc > (cmd_rows - 2)) { /* Screen full? */
557 if (!askmore()) /* Do more-prompting... */
563 /* printf("\n"); */ /* Blank line at end of report */
564 } else { /* Malloc failure, no columns */
565 for (i = 0; i < n; i++) {
566 if (s[i].flgs & CM_INV) /* Use original keyword table */
567 continue; /* skipping invisible entries */
568 printf("%s%s%s\n",pre,s[i].kwd,post);
569 if (++lc > (cmd_rows - 2)) { /* Screen full? */
570 if (!askmore()) /* Do more-prompting... */
579 if (tmpbuf) free((char *)tmpbuf);
580 for (i = 0; i < n; i++)
581 if (s2[i]) free(s2[i]);
583 if (s2) free(s2); /* Free array copy */
584 if (b) free(b); /* Free line buffer */
588 /* F I L H E L P -- Given a file list, print names in columns. */
591 n - number of entries
592 pre - prefix to add to each filename
593 post - suffix to add to each filename
594 off - offset on first screenful, allowing room for introductory text
595 cmdirflg - 1 if only directory names should be listed, 0 to list all files
597 Arranges filenames in columns with width based on longest filename.
598 Does "more?" prompting at end of screen.
599 Uses global cmd_rows and cmd_cols for screen size.
603 filhelp(n,pre,post,off,cmdirflg) int n, off; char *pre, *post; int cmdirflg; {
604 char filbuf[CKMAXPATH + 1]; /* Temp buffer for one filename */
606 int cols, height, i, j, k, lc, n2 = 0, rc = 0, itsadir = 0;
607 char *b = NULL, *p, *q;
611 char * cdp = zgtdir();
614 if (n < 1) return(0);
615 if (off < 0) off = 0; /* Offset for first page */
616 if (!pre) pre = ""; /* Handle null string pointers */
617 if (!post) post = "";
619 lc = off; /* Screen-line counter */
621 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
622 for (i = 0; i < n; i++) { /* Loop through filenames */
624 s2[i] = NULL; /* Initialize each pointer to NULL */
625 znext(filbuf); /* Get next filename */
626 if (!filbuf[0]) /* Shouldn't happen */
629 itsadir = isdir(filbuf); /* Is it a directory? */
630 if (cmdirflg && !itsadir) /* No, listing directories only? */
631 continue; /* So skip this one. */
634 ckstrncpy(filbuf,zrelname(filbuf,cdp),CKMAXPATH);
638 if (itsadir && j < CKMAXPATH - 1 && j > 0) {
639 if (filbuf[j-1] != dirsep) {
640 filbuf[j++] = dirsep;
645 if (!(s2[n2] = malloc(j+1))) {
646 printf("?Memory allocation failure\n");
650 if (j <= CKMAXPATH) {
651 strcpy(s2[n2],filbuf);
654 printf("?Name too long - %s\n", filbuf);
658 if (j > width) /* Get width of widest one */
661 n = n2; /* How many we actually got */
663 sh_sort(s2,NULL,n,0,0,filecase); /* Alphabetize the list */
666 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
669 width += (int)strlen(pre) + (int)strlen(post) + 2;
670 cols = cmd_cols / width; /* How many columns? */
671 if (cols < 1) cols = 1;
672 height = n / cols; /* How long is each column? */
673 if (n % cols) height++; /* Add one for remainder, if any */
675 for (i = 0; i < height; i++) { /* Loop for each row */
676 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
678 for (j = 0; j < cols; j++) { /* Loop for each column in row */
679 k = i + (j * height); /* Index of next filename */
680 if (k < n) { /* In range? */
683 p = s2[k]; /* Point to filename */
684 q = b + (j * width) + 1; /* and destination */
685 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
686 q--; /* Back up over NUL */
687 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
688 q--; /* Back up over NUL */
689 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
692 *q = SP; /* Replace the space */
696 p = b + cmd_cols - 1; /* Last char in line */
697 while (*p-- == SP) ; /* Trim */
699 printf("%s\n",b); /* Print the line */
700 if (++lc > (cmd_rows - 2)) { /* Screen full? */
701 if (!askmore()) { /* Do more-prompting... */
708 printf("\n"); /* Blank line at end of report */
710 } else { /* Malloc failure, no columns */
711 for (i = 0; i < n; i++) {
713 if (!filbuf[0]) break;
714 printf("%s%s%s\n",pre,filbuf,post);
715 if (++lc > (cmd_rows - 2)) { /* Screen full? */
716 if (!askmore()) { /* Do more-prompting... */
724 for (i = 0; i < n2; i++)
725 if (s2[i]) free(s2[i]);
726 if (s2) free((char *)s2);
731 /* C M S E T U P -- Set up command buffers */
736 if (!(cmdbuf = malloc(CMDBL + 4))) return(-1);
737 if (!(savbuf = malloc(CMDBL + 4))) return(-1);
739 if (!(atmbuf = malloc(ATMBL + 4))) return(-1);
740 if (!(atxbuf = malloc(CMDBL + 4))) return(-1);
741 if (!(atybuf = malloc(ATMBL + 4))) return(-1);
742 if (!(filbuf = malloc(ATMBL + 4))) return(-1);
743 if (!(cmprom = malloc(PROMPTL + 4))) return(-1);
744 if (!(cmprxx = malloc(PROMPTL + 4))) return(-1);
747 #endif /* CK_RECALL */
752 /* C M S E T P -- Set the program prompt. */
757 ckstrncpy(cmprxx,s,PROMPTL);
758 psetf = 1; /* Flag that prompt has been set. */
761 /* C M S A V P -- Save a copy of the current prompt. */
765 cmsavp(char s[], int n)
767 cmsavp(s,n) char s[]; int n;
768 #endif /* CK_ANSIC */
770 if (psetf) /* But not if no prompt is set. */
771 ckstrncpy(s,cmprxx,n);
789 /* P R O M P T -- Issue the program prompt. */
792 prompt(f) xx_strp f; {
793 char *sx, *sy; int n;
795 extern int ssl_active_flag, tls_active_flag;
798 extern int display_demo;
800 /* If there is a demo screen to be displayed, display it */
801 if (display_demo && xcmdsrc == 0) {
807 if (psetf == 0) /* If no prompt set, set default. */
810 sx = cmprxx; /* Unevaluated copy */
811 if (f) { /* If conversion function given */
812 sy = cmprom; /* Evaluate it */
813 debug(F101,"prompt sx","",sx);
814 debug(F101,"prompt sy","",sy);
816 if ((*f)(sx,&sy,&n) < 0) /* If evaluation failed */
817 sx = cmprxx; /* revert to unevaluated copy */
818 else if (!*cmprom) /* ditto if it came up empty */
823 ckstrncpy(cmprom,sx,PROMPTL);
824 cmprom[PROMPTL-1] = NUL;
825 if (!*sx) /* Don't print if empty */
835 if (inserver) { /* Print the prompt. */
836 ttoc(CR); /* If TELNET Server */
837 ttoc(NUL); /* must folloW CR by NUL */
843 if (!(ssl_active_flag || tls_active_flag))
845 fflush(stdout); /* Now! */
852 pushcmd(s) char * s; { /* For use with IF command. */
854 ckstrncpy(savbuf,s,CMDBL); /* Save the dependent clause, */
855 cmres(); /* and clear the command buffer. */
856 debug(F110, "pushcmd savbuf", savbuf, 0);
860 pushqcmd(s) char * s; { /* For use with ELSE command. */
861 char c, * p = savbuf; /* Dest */
862 if (!s) s = np; /* Source */
863 while (*s) { /* Get first nonwhitespace char */
869 if (*s != '{') { /* If it's not "{" */
870 pushcmd(s); /* do regular pushcmd */
873 while ((c = *s++)) { /* Otherwise insert quotes */
878 cmres(); /* and clear the command buffer. */
879 debug(F110, "pushqcmd savbuf", savbuf, 0);
884 /* no longer used... */
887 ckstrncpy(cmdbuf,savbuf,CMDBL); /* Put back the saved material */
888 *savbuf = '\0'; /* and clear the save buffer */
893 /* C M R E S -- Reset pointers to beginning of command buffer. */
897 inword = 0; /* We're not in a word */
898 cc = 0; /* Character count is zero */
900 /* Initialize pointers */
902 pp = cmdbuf; /* Beginning of current field */
903 bp = cmdbuf; /* Current position within buffer */
904 np = cmdbuf; /* Where to start next field */
907 cmflgs = -5; /* Parse not yet started. */
908 ungw = 0; /* Don't need to unget a word. */
911 /* C M I N I -- Clear the command and atom buffers, reset pointers. */
914 The argument specifies who is to echo the user's typein --
915 1 means the cmd package echoes
916 0 somebody else (system, front end, terminal) echoes
923 fatal("fatal error: unable to allocate command buffers");
926 memset(cmdbuf,0,CMDBL);
927 memset(atmbuf,0,ATMBL);
929 for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
930 for (bp = atmbuf; bp < atmbuf+ATMBL; bp++) *bp = NUL;
931 #endif /* USE_MEMCPY */
933 *atmbuf = *savbuf = *atxbuf = *atybuf = *filbuf = NUL;
934 blocklvl = 0; /* Block level is 0 */
935 linebegin = 1; /* At the beginning of a line */
936 dpx = d; /* Global copy of the echo flag */
937 debug(F101,"cmini dpx","",dpx);
938 crflag = 0; /* Reset flags */
942 no_recall = 0; /* Start out with recall enabled */
943 #endif /* CK_RECALL */
944 cmres(); /* Sets bp etc */
945 newcmd = 1; /* See addcmd() */
950 The following bits are to allow the command package to call itself
951 in the middle of a parse. To do this, begin by calling cmpush, and
952 end by calling cmpop. As you can see, this is rather expensive.
956 int i[5]; /* stack for integers */
957 char *c[3]; /* stack for pointers */
958 char *b[8]; /* stack for buffer contents */
962 int cmp_i[CMDDEP+1][5]; /* Stack for integers */
963 char *cmp_c[CMDDEP+1][5]; /* for misc pointers */
964 char *cmp_b[CMDDEP+1][7]; /* for buffer contents pointers */
967 int cmddep = -1; /* Current stack depth */
970 cmpush() { /* Save the command environment */
971 char *cp; /* Character pointer */
973 if (cmddep >= CMDDEP) /* Enter a new command depth */
976 debug(F101,"&cmpush to depth","",cmddep);
979 /* allocate memory for cmp if not already done */
980 if (!cmp && !(cmp = (struct cmp *) malloc(sizeof(struct cmp)*(CMDDEP+1))))
981 fatal("cmpush: no memory for cmp");
982 cmp[cmddep].i[0] = cmflgs; /* First do the global ints */
983 cmp[cmddep].i[1] = cmfsav;
984 cmp[cmddep].i[2] = atxn;
985 cmp[cmddep].i[3] = ungw;
987 cmp[cmddep].c[0] = bp; /* Then the global pointers */
988 cmp[cmddep].c[1] = pp;
989 cmp[cmddep].c[2] = np;
991 cmp_i[cmddep][0] = cmflgs; /* First do the global ints */
992 cmp_i[cmddep][1] = cmfsav;
993 cmp_i[cmddep][2] = atxn;
994 cmp_i[cmddep][3] = ungw;
996 cmp_c[cmddep][0] = bp; /* Then the global pointers */
997 cmp_c[cmddep][1] = pp;
998 cmp_c[cmddep][2] = np;
1001 /* Now the buffers themselves. A lot of repititious code... */
1004 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1005 if (cp) strcpy(cp,cmdbuf);
1006 cmp[cmddep].b[0] = cp;
1007 if (cp == NULL) return(-1);
1009 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1010 if (cp) strcpy(cp,savbuf);
1011 cmp[cmddep].b[1] = cp;
1012 if (cp == NULL) return(-1);
1014 cmp[cmddep].b[2] = NULL;
1016 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1017 if (cp) strcpy(cp,atmbuf);
1018 cmp[cmddep].b[3] = cp;
1019 if (cp == NULL) return(-1);
1021 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1022 if (cp) strcpy(cp,atxbuf);
1023 cmp[cmddep].b[4] = cp;
1024 if (cp == NULL) return(-1);
1026 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1027 if (cp) strcpy(cp,atybuf);
1028 cmp[cmddep].b[5] = cp;
1029 if (cp == NULL) return(-1);
1031 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1032 if (cp) strcpy(cp,filbuf);
1033 cmp[cmddep].b[6] = cp;
1034 if (cp == NULL) return(-1);
1036 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1037 if (cp) strcpy(cp,cmdbuf);
1038 cmp_b[cmddep][0] = cp;
1039 if (cp == NULL) return(-1);
1041 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1042 if (cp) strcpy(cp,savbuf);
1043 cmp_b[cmddep][1] = cp;
1044 if (cp == NULL) return(-1);
1046 cmp_b[cmddep][2] = NULL;
1048 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1049 if (cp) strcpy(cp,atmbuf);
1050 cmp_b[cmddep][3] = cp;
1051 if (cp == NULL) return(-1);
1053 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1054 if (cp) strcpy(cp,atxbuf);
1055 cmp_b[cmddep][4] = cp;
1056 if (cp == NULL) return(-1);
1058 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1059 if (cp) strcpy(cp,atybuf);
1060 cmp_b[cmddep][5] = cp;
1061 if (cp == NULL) return(-1);
1063 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1064 if (cp) strcpy(cp,filbuf);
1065 cmp_b[cmddep][6] = cp;
1066 if (cp == NULL) return(-1);
1067 #endif /* DCMDBUF */
1069 cmini(dpx); /* Initize the command parser */
1074 cmpop() { /* Restore the command environment */
1076 debug(F100,"&cmpop called from top level","",0);
1077 return(-1); /* Don't pop too much! */
1080 cmflgs = cmp[cmddep].i[0]; /* First do the global ints */
1081 cmfsav = cmp[cmddep].i[1];
1082 atxn = cmp[cmddep].i[2];
1083 ungw = cmp[cmddep].i[3];
1085 bp = cmp[cmddep].c[0]; /* Then the global pointers */
1086 pp = cmp[cmddep].c[1];
1087 np = cmp[cmddep].c[2];
1089 cmflgs = cmp_i[cmddep][0]; /* First do the global ints */
1090 cmfsav = cmp_i[cmddep][1];
1091 atxn = cmp_i[cmddep][2];
1092 ungw = cmp_i[cmddep][3];
1094 bp = cmp_c[cmddep][0]; /* Then the global pointers */
1095 pp = cmp_c[cmddep][1];
1096 np = cmp_c[cmddep][2];
1097 #endif /* DCMDBUF */
1099 /* Now the buffers themselves. */
1100 /* Note: strncpy(), not ckstrncpy() -- Here we WANT the NUL padding... */
1103 if (cmp[cmddep].b[0]) {
1105 strncpy(cmdbuf,cmp[cmddep].b[0],CMDBL); /* 0: Command buffer */
1106 free(cmp[cmddep].b[0]);
1107 cmp[cmddep].b[0] = NULL;
1109 if (cmp[cmddep].b[1]) {
1110 strncpy(savbuf,cmp[cmddep].b[1],CMDBL); /* 1: Save buffer */
1111 free(cmp[cmddep].b[1]);
1112 cmp[cmddep].b[1] = NULL;
1114 if (cmp[cmddep].b[3]) {
1115 strncpy(atmbuf,cmp[cmddep].b[3],ATMBL); /* 3: Atomic buffer! */
1116 free(cmp[cmddep].b[3]);
1117 cmp[cmddep].b[3] = NULL;
1119 if (cmp[cmddep].b[4]) {
1120 strncpy(atxbuf,cmp[cmddep].b[4],ATMBL); /* 4: eXpansion buffer */
1121 free(cmp[cmddep].b[4]);
1122 cmp[cmddep].b[4] = NULL;
1124 if (cmp[cmddep].b[5]) {
1125 strncpy(atybuf,cmp[cmddep].b[5],ATMBL); /* 5: Atom buffer copY */
1126 free(cmp[cmddep].b[5]);
1127 cmp[cmddep].b[5] = NULL;
1129 if (cmp[cmddep].b[6]) {
1130 strncpy(filbuf,cmp[cmddep].b[6],ATMBL); /* 6: Filename buffer */
1131 free(cmp[cmddep].b[6]);
1132 cmp[cmddep].b[6] = NULL;
1135 if (cmp_b[cmddep][0]) {
1136 strncpy(cmdbuf,cmp_b[cmddep][0],CMDBL); /* 0: Command buffer */
1137 free(cmp_b[cmddep][0]);
1138 cmp_b[cmddep][0] = NULL;
1140 if (cmp_b[cmddep][1]) {
1141 strncpy(savbuf,cmp_b[cmddep][1],CMDBL); /* 1: Save buffer */
1142 free(cmp_b[cmddep][1]);
1143 cmp_b[cmddep][1] = NULL;
1145 if (cmp_b[cmddep][3]) {
1146 strncpy(atmbuf,cmp_b[cmddep][3],ATMBL); /* 3: Atomic buffer! */
1147 free(cmp_b[cmddep][3]);
1148 cmp_b[cmddep][3] = NULL;
1150 if (cmp_b[cmddep][4]) {
1151 strncpy(atxbuf,cmp_b[cmddep][4],ATMBL); /* 4: eXpansion buffer */
1152 free(cmp_b[cmddep][4]);
1153 cmp_b[cmddep][4] = NULL;
1155 if (cmp_b[cmddep][5]) {
1156 strncpy(atybuf,cmp_b[cmddep][5],ATMBL); /* 5: Atom buffer copY */
1157 free(cmp_b[cmddep][5]);
1158 cmp_b[cmddep][5] = NULL;
1160 if (cmp_b[cmddep][6]) {
1161 strncpy(filbuf,cmp_b[cmddep][6],ATMBL); /* 6: Filename buffer */
1162 free(cmp_b[cmddep][6]);
1163 cmp_b[cmddep][6] = NULL;
1165 #endif /* DCMDBUF */
1167 cmddep--; /* Rise, rise */
1168 debug(F101,"&cmpop to depth","",cmddep);
1175 stripq(s) char *s; { /* Function to strip '\' quotes */
1179 for (t = s; *t != '\0'; t++) *t = *(t+1);
1184 #endif /* COMMENT */
1186 /* Convert tabs to spaces, one for one */
1190 if (*s == HT) *s = SP;
1195 /* C M N U M -- Parse a number in the indicated radix */
1198 The radix is specified in the arg list.
1199 Parses unquoted numeric strings in the given radix.
1200 Parses backslash-quoted numbers in the radix indicated by the quote:
1201 \nnn = \dnnn = decimal, \onnn = octal, \xnn = Hexadecimal.
1202 If these fail, then if a preprocessing function is supplied, that is applied
1203 and then a second attempt is made to parse an unquoted decimal string.
1204 And if that fails, the preprocessed string is passed to an arithmetic
1205 expression evaluator.
1208 -3 if no input present when required,
1209 -2 if user typed an illegal number,
1210 -1 if reparse needed,
1211 0 otherwise, with argument n set to the number that was parsed
1214 cmnum(xhlp,xdef,radix,n,f) char *xhlp, *xdef; int radix, *n; xx_strp f; {
1215 int x; char *s, *zp, *zq;
1217 char lbrace, rbrace;
1218 #endif /* COMMENT */
1220 if (!xhlp) xhlp = "";
1221 if (!xdef) xdef = "";
1224 if (cmfldflgs & 1) {
1231 #endif /* COMMENT */
1233 if (radix != 10 && radix != 8) { /* Just do bases 8 and 10 */
1234 printf("cmnum: illegal radix - %d\n",radix);
1236 } /* Easy to add others but there has never been a need for it. */
1237 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1238 debug(F101,"cmnum: cmfld","",x);
1239 if (x < 0) return(x); /* Parse a field */
1242 Edit 192 - Allow any number field to be braced. This lets us include
1243 spaces in expressions, but perhaps more important lets us have user-defined
1244 functions in numeric fields.
1246 zp = brstrip(zp); /* Strip braces */
1247 if (cmfldflgs & 1 && *zp == '(') { /* Parens too.. */
1248 x = (int) strlen(atmbuf);
1250 if (*(atmbuf+x-1) == ')') {
1251 *(atmbuf+x-1) = NUL;
1256 if (chknum(zp)) { /* Check for number */
1257 if (radix == 8) { /* If it's supposed to be octal */
1258 zp = ckradix(zp,8,10); /* convert to decimal */
1259 if (!zp) return(-2);
1260 if (!strcmp(zp,"-1")) return(-2);
1262 errno = 0; /* Got one, we're done. */
1268 debug(F101,"cmnum 1st chknum ok","",*n);
1270 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1278 debug(F101,"cmnum xxesc ok","",*n);
1279 return(*zp ? -2 : 0);
1280 } else if (f) { /* If conversion function given */
1281 zq = atxbuf; /* Try that */
1283 if ((*f)(zp,&zq,&atxn) < 0) /* Convert */
1287 debug(F110,"cmnum zp 1",zp,0);
1288 if (!*zp) zp = xdef; /* Result empty, substitute default */
1289 debug(F110,"cmnum zp 2",zp,0);
1290 if (chknum(zp)) { /* Check again for decimal number */
1291 if (radix == 8) { /* If it's supposed to be octal */
1292 zp = ckradix(zp,8,10); /* convert to decimal */
1293 if (!zp) return(-2);
1294 if (!strcmp(zp,"-1")) return(-2);
1302 debug(F101,"cmnum 2nd chknum ok","",*n);
1305 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1311 debug(F101,"cmnum xxesc 2 ok","",*n);
1312 return(*zp ? -2 : 0);
1313 } else if (f) { /* Not numeric, maybe an expression */
1317 if (radix == 8) { /* If it's supposed to be octal */
1318 zp = ckradix(zp,8,10); /* convert to decimal */
1319 if (!zp) return(-2);
1320 if (!strcmp(zp,"-1")) return(-2);
1328 debug(F101,"cmnum exp eval ok","",*n);
1332 } else { /* Not numeric */
1339 #endif /* CKCHANNELIO */
1341 /* C M O F I -- Parse the name of an output file */
1344 Depends on the external function zchko(); if zchko() not available, use
1345 cmfld() to parse output file names.
1348 -9 like -2, except message already printed,
1349 -3 if no input present when required,
1350 -2 if permission would be denied to create the file,
1351 -1 if reparse needed,
1352 0 or 1 if file can be created, with xp pointing to name.
1353 2 if given the name of an existing directory.
1356 cmofi(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1357 int x; char *s, *zq;
1360 #endif /* DOCHKVAR */
1367 if (!xhlp) xhlp = "";
1368 if (!xdef) xdef = "";
1370 if (*xhlp == NUL) xhlp = "Output file";
1373 debug(F110,"cmofi xdef",xdef,0);
1374 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1375 debug(F111,"cmofi cmfld returns",s,x);
1379 s = brstrip(s); /* Strip enclosing braces */
1380 debug(F110,"cmofi 1.5",s,0);
1387 This is really ugly. If we skip conversion the first time through,
1388 then variable names like \%a will be used as filenames (e.g. creating
1389 a file called %A in the root directory). If we DON'T skip conversion
1390 the first time through, then single backslashes used as directory
1391 separators in filenames will be misinterpreted as variable lead-ins.
1392 So we prescan to see if it has any variable references. But this
1393 module is not supposed to know anything about variables, functions,
1394 etc, so this code does not really belong here, but rather it should
1395 be at the same level as zzstring().
1398 Hmmm, this looks a lot like chkvar() except it that includes \nnn number
1399 escapes. But why? This makes commands like "mkdir c:\123" impossible.
1400 And in fact, "mkdir c:\123" creates a directory called "c:{". What's worse,
1401 rmdir(), which *does* call chkvar(), won't let us remove it. So let's at
1402 least try making cmofi() symmetrical with cmifi()...
1406 while ( (tries == 0) && (p = strchr(p,CMDQ)) ) {
1407 q = *(p+1); /* Char after backslash */
1408 if (!q) /* None, quit */
1410 if (isupper(q)) /* If letter, convert to lowercase */
1412 if (isdigit(q)) { /* If it's a digit, */
1413 tries = 1; /* assume it's a backslash code */
1417 case CMDQ: /* Double backslash */
1418 tries = 1; /* so call the conversion function */
1420 case '%': /* Variable or array reference */
1421 case '&': /* must be followed by letter */
1422 if (isalpha(*(p+2)) || (*(p+2) >= '0' && *(p+2) <= '9'))
1425 case 'm': case 'v': case '$': /* \m(), \v(), \$() */
1427 if (strchr(p+2,')'))
1430 case 'f': /* \Fname() */
1431 if (strchr(p+2,'('))
1432 if (strchr(p+2,')'))
1435 case '{': /* \{...} */
1436 if (strchr(p+2,'}'))
1439 case 'd': case 'o': /* Decimal or Octal number */
1440 if (isdigit(*(p+2)))
1443 case 'x': /* Hex number */
1444 if (isdigit(*(p+2)) ||
1445 ((*(p+2) >= 'a' && *(p+2) <= 'f') ||
1446 ((*(p+2) >= 'A' && *(p+2) <= 'F'))))
1455 if (f) { /* If a conversion function is given */
1456 char *s = p; /* See if there are any variables in */
1457 while (*s) { /* the string and if so, expand them */
1466 #endif /* COMMENT */
1472 #endif /* DOCHKVAR */
1473 if (f) { /* If a conversion function is given */
1474 zq = atxbuf; /* do the conversion. */
1476 if ((x = (*f)(s,&zq,&atxn)) < 0)
1479 if (!*s) /* Result empty, substitute default */
1482 debug(F111,"cmofi 2",s,x);
1485 dirp = tilde_expand(s); /* Expand tilde, if any, */
1486 if (*dirp != '\0') { /* right in the atom buffer. */
1487 if (setatm(dirp,1) < 0) {
1488 printf("?Name too long\n");
1493 debug(F110,"cmofi 3",s,0);
1497 printf("?Wildcards not allowed - %s\n",s);
1500 debug(F110,"cmofi 4",s,0);
1503 /* isdir() function required for this! */
1505 debug(F110,"cmofi 5: is directory",s,0);
1509 #endif /* CK_TMPDIR */
1511 if (strcmp(s,CTTNAM) && (zchko(s) < 0)) { /* OK to write to console */
1515 We don't try again because we already prescanned the string to see if
1516 if it contained anything that could be used by zzstring().
1521 #endif /* COMMENT */
1523 Note: there are certain circumstances where zchko() can give a false
1524 positive, so don't rely on it to catch every conceivable situation in
1525 which the given output file can't be created. In other words, we print
1526 a message and fail here if we KNOW the file can't be created. If we
1527 succeed but the file can't be opened, the code that tries to open the file
1528 has to print a message.
1530 debug(F110,"cmofi 6: failure",s,0);
1533 printf("?Off Limits: %s\n",s);
1536 printf("?Write permission denied - %s\n",s);
1539 #endif /* CKCHANNELIO */
1542 debug(F110,"cmofi 7: ok",s,0);
1548 /* C M I F I -- Parse the name of an existing file */
1551 This function depends on the external functions:
1552 zchki() - Check if input file exists and is readable.
1553 zxpand() - Expand a wild file specification into a list.
1554 znext() - Return next file name from list.
1555 If these functions aren't available, then use cmfld() to parse filenames.
1560 -3 if no input present when required,
1561 -2 if file does not exist or is not readable,
1562 -1 if reparse needed,
1563 0 or 1 otherwise, with:
1564 xp pointing to name,
1565 wild = 1 if name contains '*' or '?', 0 otherwise.
1569 C M I O F I -- Parse an input file OR the name of a nonexistent file.
1571 Use this when an existing file is wanted (so we get help, completion, etc),
1572 but if a file of the given name does not exist, the name of a new file is
1573 accepted. For example, with the EDIT command (edit an existing file, or
1574 create a new file). Returns -9 if file does not exist. It is up to the
1575 caller to check creatability.
1577 static int nomsg = 0;
1579 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1583 x = cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0);
1589 cmifi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1590 return(cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0));
1593 cmifip() is called when we want to supply a path or path list to search
1594 in case the filename that the user gives is (a) not absolute, and (b) can't
1595 be found as given. The path string can be the name of a single directory,
1596 or a list of directories separated by the PATHSEP character, defined in
1597 ckucmd.h. Look in ckuusr.c and ckuus3.c for examples of usage.
1600 cmifip(xhlp,xdef,xp,wild,d,path,f)
1601 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; {
1602 return(cmifi2(xhlp,xdef,xp,wild,0,path,f,0));
1605 /* C M D I R -- Parse a directory name */
1608 This function depends on the external functions:
1609 isdir(s) - Check if string s is the name of a directory
1610 zchki(s) - Check if input file s exists and what type it is.
1611 If these functions aren't available, then use cmfld() to parse dir names.
1614 -9 For all sorts of reasons, after printing appropriate error message.
1616 -3 if no input present when required,
1617 -2 if out of space or other internal error,
1618 -1 if reparse needed,
1619 0 or 1, with xp pointing to name, if directory specified,
1622 cmdir(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1624 return(cmifi2(xhlp,xdef,xp,&wild,0,NULL,f,1));
1627 /* Like CMDIR but includes PATH search */
1630 cmdirp(xhlp,xdef,xp,path,f) char *xhlp, *xdef, **xp; char * path; xx_strp f; {
1632 return(cmifi2(xhlp,xdef,xp,&wild,0,path,f,1));
1636 cmifi2() is the base filename parser called by cmifi, cmifip, cmdir, etc.
1637 Use it directly when you also want to parse a directory or device
1638 name as an input file, as in the DIRECTORY command. Call with:
1639 xhlp -- help message on ?
1640 xdef -- default response
1641 xp -- pointer to result (in our space, must be copied from here)
1642 wild -- flag set upon return to indicate if filespec was wild
1643 d -- 0 to parse files, 1 to parse files or directories
1644 Add 2 to inhibit following of symlinks.
1645 path -- search path for files
1646 f -- pointer to string processing function (e.g. to evaluate variables)
1647 dirflg -- 1 to parse *only* directories, 0 otherwise
1650 cmifi2(xhlp,xdef,xp,wild,d,path,f,dirflg)
1651 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; int dirflg; {
1652 extern int recursive, diractive, cdactive, dblquo;
1653 int i, x, itsadir, xc, expanded = 0, nfiles = 0, children = -1;
1656 char *sp = NULL, *zq, *np = NULL;
1657 char *sv = NULL, *p = NULL;
1665 /* This large array is dynamic for OS-9 -- should do for others too... */
1666 extern char **mtchs;
1669 /* OK, for UNIX too */
1670 extern char **mtchs;
1673 extern char **mtchs;
1675 extern char *mtchs[];
1680 #endif /* NOPARTIAL */
1682 if (!xhlp) xhlp = "";
1683 if (!xdef) xdef = "";
1685 nzxopts = 0; /* zxpand() options */
1686 debug(F101,"cmifi d","",d);
1687 if (d & 2) { /* d & 2 means don't follow symlinks */
1689 nzxopts = ZX_NOLINKS;
1691 debug(F101,"cmifi nzxopts","",nzxopts);
1696 if (path) { /* Make a copy we can poke */
1698 np = (char *) malloc(x + 1);
1704 debug(F110,"cmifi2 path",path,0);
1706 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
1709 inword = 0; /* Initialize counts & pointers */
1712 *xp = ""; /* Pointer to result string */
1713 if ((x = cmflgs) != 1) { /* Already confirmed? */
1716 x = gtword(0); /* No, get a word */
1719 x = gtword(0); /* No, get a word */
1720 #endif /* BS_DIRSEP */
1721 } else { /* If so, use default, if any. */
1722 if (setatm(xdef,1) < 0) {
1723 printf("?Default name too long\n");
1729 *xp = atmbuf; /* Point to result. */
1732 xc += cc; /* Count this character. */
1733 debug(F111,"cmifi gtword",atmbuf,xc);
1734 debug(F101,"cmifi switch x","",x);
1735 switch (x) { /* x = gtword() return code */
1737 if (gtimer() > timelimit) {
1740 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
1741 doexit(GOOD_EXIT,0);
1744 /* if (!quiet) printf("?Timed out\n"); */
1751 printf("Command or field too long\n");
1753 case -2: /* Out of space. */
1754 case -1: /* Reparse needed */
1759 if (xc == 0) /* If no input... */
1760 *xp = xdef; /* substitute the default */
1761 *xp = brstrip(*xp); /* Strip braces */
1762 if (**xp == NUL) { /* 12 mar 2001 */
1766 debug(F110,"cmifi brstrip",*xp,0);
1768 if (f) { /* If a conversion function is given */
1770 char *s = *xp; /* See if there are any variables in */
1772 while (*s) { /* the string and if so, expand them */
1774 /* debug(F111,"cmifi chkvar",*xp,x); */
1776 #endif /* DOCHKVAR */
1779 if ((*f)(*xp,&zq,&atxn) < 0) {
1791 #endif /* DOCHKVAR */
1794 if (**xp == NUL) { /* 12 mar 2001 */
1800 dirp = tilde_expand(*xp); /* Expand tilde, if any, */
1801 if (*dirp != '\0') { /* in the atom buffer. */
1802 if (setatm(dirp,1) < 0) {
1803 printf("Expanded name too long\n");
1809 debug(F110,"cmifi tilde_expand",*xp,0);
1812 if (!sv) { /* Only do this once */
1813 sv = malloc((int)strlen(*xp)+1); /* Make a safe copy */
1815 printf("?cmifi: malloc error\n");
1820 debug(F110,"cmifi sv",sv,0);
1823 /* This is to get around "cd /" failing because "too many directories match" */
1825 expanded = 0; /* Didn't call zxpand */
1827 debug(F110,"cmifi isdir 1",*xp,0);
1839 debug(F110,"cmifi isdir 2",*xp,0);
1840 #endif /* datageneral */
1844 if (!strcmp(*xp,"..")) { /* For UNIXers... */
1847 } else if (!strcmp(*xp,".")) {
1853 itsadir = isdir(*xp); /* Is it a directory? */
1854 debug(F111,"cmifi itsadir",*xp,itsadir);
1856 /* If they said "blah" where "blah.dir" is a directory... */
1857 /* change it to [.blah]. */
1860 int flag = 0; char c, * p;
1862 while ((c = *p++) && !flag)
1863 if (ckstrchr(".[]:*?<>",c))
1865 debug(F111,"cmifi VMS dirname flag",*xp,flag);
1867 ckmakmsg(tmpbuf,TMPBUFSIZ,"[.",*xp,"]",NULL);
1868 itsadir = isdir(tmpbuf);
1873 debug(F111,"cmifi VMS dirname flag itsadir",*xp,itsadir);
1875 } else if (itsadir == 1 && *(xp[0]) == '.' && *(xp[1])) {
1877 if (p = malloc(cc + 4)) {
1878 ckmakmsg(p,cc+4,"[",*xp,"]",NULL);
1881 debug(F110,"cmdir .foo",*xp,0);
1884 } else if (itsadir == 2 && !diractive) {
1885 int x; /* [FOO]BAR.DIR instead of [FOO.BAR] */
1889 x = cvtdir(*xp,p,ATMBL); /* Convert to [FOO.BAR] */
1893 debug(F110,"cmdir cvtdir",*xp,0);
1900 debug(F101,"cmifi dirflg","",dirflg);
1901 if (dirflg) { /* Parsing a directory name? */
1902 /* Yes, does it contain wildcards? */
1904 (diractive && (!strcmp(*xp,".") || !strcmp(*xp,"..")))
1906 nzxopts |= ZX_DIRONLY; /* Match only directory names */
1907 if (matchdot) nzxopts |= ZX_MATCHDOT;
1908 if (recursive) nzxopts |= ZX_RECURSE;
1909 debug(F111,"cmifi nzxopts 2",*xp,nzxopts);
1910 y = nzxpand(*xp,nzxopts);
1911 debug(F111,"cmifi nzxpand 2",*xp,y);
1917 This is to allow (e.g.) "cd foo", where FOO.DIR;1 is in the
1920 debug(F111,"cmdir itsadir",*xp,itsadir);
1928 *s != '[' && s[n-1] != ']' &&
1929 *s != '<' && s[n-1] != '>' &&
1931 ckindex("[",s,0,0,1) == 0 &&
1932 ckindex("<",s,0,0,1) == 0 &&
1933 #endif /* COMMENT */
1935 char * dirbuf = NULL;
1936 dirbuf = (char *)malloc(n+4);
1939 ckmakmsg(dirbuf,n+4,"[",s,"]",NULL);
1941 ckmakmsg(dirbuf,n+4,"[.",s,"]",NULL);
1942 itsadir = isdir(dirbuf);
1943 debug(F111,"cmdir dirbuf",dirbuf,itsadir);
1947 debug(F110,"cmdir new *xp",*xp,0);
1952 /* This is to allow CDPATH to work in VMS... */
1955 char * p; int i, j, k, d;
1957 if (p = malloc(x + 8)) {
1958 ckstrncpy(p,*xp,x+8);
1959 i = ckindex(".",p,-1,1,1);
1960 d = ckindex(".dir",p,0,0,0);
1961 j = ckindex("]",p,-1,1,1);
1963 j = ckindex(">",p,-1,1,1);
1966 k = ckindex(":",p,-1,1,1);
1967 if (i < j || i < k) i = 0;
1968 if (d < j || d < k) d = 0;
1969 /* Change [FOO]BAR or [FOO]BAR.DIR */
1971 if (j > 0 && j < n) {
1973 if (d > 0) p[d-1] = NUL;
1974 ckstrncat(p,rb,x+8);
1975 debug(F110,"cmdir xxx",p,0);
1978 debug(F111,"cmdir p",p,itsadir);
1982 debug(F110,"cmdir new *xp",*xp,0);
1989 y = (!itsadir) ? 0 : 1;
1990 debug(F111,"cmifi y itsadir",*xp,y);
1992 } else { /* Parsing a filename. */
1993 debug(F110,"cmifi *xp pre-zxpand",*xp,0);
1995 nzxopts |= (d == 0) ? ZX_FILONLY : 0; /* So always expand. */
1996 if (matchdot) nzxopts |= ZX_MATCHDOT;
1997 if (recursive) nzxopts |= ZX_RECURSE;
1998 y = nzxpand(*xp,nzxopts);
2000 /* Here we're trying to fix a problem in which a directory name is accepted */
2001 /* as a filename, but this breaks too many other things. */
2004 if (itsadir & !iswild(*xp)) {
2005 debug(F100,"cmifi dir when filonly","",0);
2006 printf("?Not a regular file: \"%s\"\n",*xp);
2011 nzxopts |= ZX_FILONLY;
2012 if (matchdot) nzxopts |= ZX_MATCHDOT;
2013 if (recursive) nzxopts |= ZX_RECURSE;
2014 y = nzxpand(*xp,nzxopts);
2017 #endif /* COMMENT */
2019 debug(F111,"cmifi y nzxpand",*xp,y);
2020 debug(F111,"cmifi y atmbuf",atmbuf,itsadir);
2023 /* domydir() calls zxrewind() so we MUST call nzxpand() here */
2024 if (!expanded && diractive) {
2025 debug(F110,"cmifi diractive catch-all zxpand",*xp,0);
2026 nzxopts |= (d == 0) ? ZX_FILONLY : (dirflg ? ZX_DIRONLY : 0);
2027 if (matchdot) nzxopts |= ZX_MATCHDOT;
2028 if (recursive) nzxopts |= ZX_RECURSE;
2029 y = nzxpand(*xp,nzxopts);
2030 debug(F111,"cmifi diractive nzxpand",*xp,y);
2034 *wild = (iswild(sv) || (y > 1)) && (itsadir == 0);
2037 if (!*wild) *wild = recursive;
2038 #endif /* RECURSIVE */
2040 debug(F111,"cmifi sv wild",sv,*wild);
2041 debug(F101,"cmifi y","",y);
2042 if (dirflg && *wild && cdactive) {
2044 printf("?Wildcard matches more than one directory\n");
2052 if (itsadir && d && !dirflg) { /* It's a directory and not wild */
2053 if (sv) free(sv); /* and it's ok to parse directories */
2057 if (y == 0) { /* File was not found */
2059 dosearch = (path != NULL); /* A search path was given */
2061 dosearch = hasnopath(sv); /* Filename includes no path */
2062 debug(F111,"cmifip hasnopath",sv,dosearch);
2064 if (dosearch) { /* Search the path... */
2069 if (c == PATHSEP || c == NUL) {
2076 /* By definition of CDPATH, an empty member denotes the current directory */
2078 ckstrncpy(atmbuf,".",ATMBL);
2081 ckstrncpy(atmbuf,path,ATMBL);
2083 atmbuf[ATMBL] = NUL;
2084 /* If we have a logical name, evaluate it recursively */
2085 if (*(ptr-1) == ':') { /* Logical name ends in : */
2087 while (((n = strlen(atmbuf)) > 0) &&
2088 atmbuf[n-1] == ':') {
2090 for (p = atmbuf; *p; p++)
2091 if (islower(*p)) *p = toupper(*p);
2092 debug(F111,"cmdir CDPATH LN 1",atmbuf,n);
2094 debug(F110,"cmdir CDPATH LN 2",p,0);
2097 strncpy(atmbuf,p,ATMBL);
2098 atmbuf[ATMBL] = NUL;
2103 if (*(ptr-1) != '\\' && *(ptr-1) != '/')
2104 ckstrncat(atmbuf,"\\",ATMBL);
2107 if (*(ptr-1) != '/')
2108 ckstrncat(atmbuf,"/",ATMBL);
2111 if (*(ptr-1) != ':')
2112 ckstrncat(atmbuf,":",ATMBL);
2113 #endif /* datageneral */
2117 ckstrncat(atmbuf,sv,ATMBL);
2118 debug(F110,"cmifip add path",atmbuf,0);
2119 if (c == PATHSEP) ptr++;
2128 xc = (int) strlen(atmbuf);
2140 printf("?Off Limits: %s\n",sv);
2143 printf("?No %s match - %s\n",
2144 dirflg ? "directories" : "files", sv);
2153 printf("?Off Limits: %s\n",sv);
2156 printf("?Too many %s match - %s\n",
2157 dirflg ? "directories" : "files", sv);
2161 } else if (*wild || y > 1) {
2167 /* If not wild, see if it exists and is readable. */
2169 debug(F111,"cmifi sv not wild",sv,*wild);
2171 znext(*xp); /* Get first (only?) matching file */
2172 if (dirflg) /* Maybe wild and expanded */
2173 itsadir = isdir(*xp); /* so do this again. */
2174 y = dirflg ? itsadir : zchki(*xp); /* Now check accessibility */
2177 nfiles = zxrewind(); /* Rewind so next znext() gets 1st */
2180 nzxopts |= dirflg ? ZX_DIRONLY : 0;
2181 if (matchdot) nzxopts |= ZX_MATCHDOT;
2182 if (recursive) nzxopts |= ZX_RECURSE;
2183 nfiles = nzxpand(*xp,nzxopts);
2184 #endif /* ZXREWIND */
2186 debug(F111,"cmifi nfiles",*xp,nfiles);
2187 free(sv); /* done with this */
2189 if (dirflg && y == 0) {
2190 printf("?Not a directory - %s\n",*xp);
2193 #endif /* CKCHANNELIO */
2195 } else if (y == -3) {
2198 /* Don't show filename if we're not allowed to see it */
2199 printf("?Read permission denied\n");
2201 printf("?Read permission denied - %s\n",*xp);
2206 #endif /* CKCHANNELIO */
2207 return(xcmfdb ? -6 : -9);
2208 } else if (y == -2) {
2213 printf("?File not readable - %s\n",*xp);
2216 #endif /* CKCHANNELIO */
2217 return(xcmfdb ? -6 : -9);
2221 if (!nomsg && !xcmfdb)
2222 printf("?File not found - %s\n",*xp);
2225 #endif /* CKCHANNELIO */
2226 return(xcmfdb ? -6 : -9);
2233 debug(F101,"cmifi esc, xc","",xc);
2236 printf("%s ",xdef); /* If at beginning of field */
2240 inword = cmflgs = 0;
2241 addbuf(xdef); /* Supply default. */
2242 if (setatm(xdef,0) < 0) {
2243 printf("Default name too long\n");
2247 } else { /* No default */
2252 if (**xp == '{') { /* Did user type opening brace... */
2257 } else if (dblquo && **xp == '"') { /* or doublequote? */
2258 *xp = *xp + 1; /* If so ignore it and space past it */
2264 if (f) { /* If a conversion function is given */
2266 char *s = *xp; /* See if there are any variables in */
2267 while (*s) { /* the string and if so, expand it. */
2269 #endif /* DOCHKVAR */
2272 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2277 /* reduce cc by number of \\ consumed by conversion */
2278 /* function (needed for OS/2, where \ is path separator) */
2279 cc -= (strlen(*xp) - strlen(atxbuf));
2280 #endif /* DOCHKVAR */
2282 if (!atxbuf[0]) { /* Result empty, use default */
2291 #endif /* DOCHKVAR */
2296 if (dirflg && *(*xp) == '~') {
2297 debug(F111,"cmifi tilde_expand A",*xp,cc);
2298 dirp = tilde_expand(*xp); /* Expand tilde, if any... */
2299 if (!dirp) dirp = "";
2303 xc = cc; /* Length of ~thing */
2304 xx = setatm(dirp,0); /* Copy expansion to atom buffer */
2305 debug(F111,"cmifi tilde_expand B",atmbuf,cc);
2307 printf("Expanded name too long\n");
2311 debug(F111,"cmifi tilde_expand xc","",xc);
2312 for (i = 0; i < xc; i++) {
2313 cmdchardel(); /* Back up over ~thing */
2316 xc = cc; /* How many new ones we just got */
2318 printf("%s",sp); /* Print them */
2319 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2320 bp--; /* Back up over NUL */
2329 if (!strcmp(atmbuf,"..")) {
2331 ckstrncat(cmdbuf," ",CMDBL);
2337 } else if (!strcmp(atmbuf,".")) {
2342 /* This patches a glitch when user types "./foo<ESC>" */
2343 /* in which the next two chars are omitted from the */
2344 /* expansion. There should be a better fix, however, */
2345 /* since there is no problem with "../foo<ESC>". */
2347 if (*p == '.' && *(p+1) == '/')
2350 #endif /* UNIXOROSK */
2353 *sp++ = '+'; /* Data General AOS wildcard */
2355 *sp++ = '*'; /* Others */
2356 #endif /* datageneral */
2359 if (!strchr(*xp, '.')) /* abde.e -> abcde.e* */
2360 strcat(*xp, ".*"); /* abc -> abc*.* */
2362 /* Add wildcard and expand list. */
2364 /* This kills partial completion when ESC given in path segment */
2365 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2368 #endif /* COMMENT */
2369 if (matchdot) nzxopts |= ZX_MATCHDOT;
2370 if (recursive) nzxopts |= ZX_RECURSE;
2371 y = nzxpand(*xp,nzxopts);
2373 debug(F111,"cmifi nzxpand",*xp,y);
2376 znext(filbuf); /* Get first */
2378 zxrewind(); /* Must "rewind" */
2380 nzxpand(*xp,nxzopts);
2381 #endif /* ZXREWIND */
2383 ckstrncpy(filbuf,mtchs[0],CKMAXPATH);
2387 filbuf[CKMAXPATH] = NUL;
2388 *sp = '\0'; /* Remove wildcard. */
2389 debug(F111,"cmifi filbuf",filbuf,y);
2390 debug(F111,"cmifi *xp",*xp,cc);
2397 printf("?Off Limits: %s\n",atmbuf);
2400 printf("?No %s match - %s\n",
2401 dirflg ? "directories" : "files", atmbuf);
2412 printf("?Off Limits: %s\n",atmbuf);
2415 printf("?Too many %s match - %s\n",
2416 dirflg ? "directories" : "files", atmbuf);
2419 } else if (y > 1 /* Not unique */
2421 || (y == 1 && isdir(filbuf)) /* Unique directory */
2425 /* Partial filename completion */
2428 debug(F111,"cmifi partial",filbuf,cc);
2434 min = strlen(filbuf),
2436 char localfn[CKMAXPATH+1];
2439 for (j = 1; j <= y; j++) {
2441 if (dirflg && !isdir(localfn))
2444 len2 = strlen(localfn);
2446 cur < len && cur < len2 && cur <= min;
2449 /* OS/2 or Windows, case doesn't matter */
2450 if (tolower(filbuf[cur]) != tolower(localfn[cur]))
2463 for (i = cc; (c = filbuf[i]); i++) {
2464 for (j = 1; j < y; j++)
2465 if (mtchs[j][i] != c) break;
2467 else filbuf[i] = filbuf[i+1] = NUL;
2473 /* isdir() function required for this! */
2474 if (y == 1 && isdir(filbuf)) { /* Dont we already know this? */
2476 len = strlen(filbuf);
2477 if (len > 0 && len < ATMBL - 1) {
2478 if (filbuf[len-1] != dirsep) {
2479 filbuf[len] = dirsep;
2480 filbuf[len+1] = NUL;
2484 At this point, before just doing partial completion, we should look first to
2485 see if the given directory does indeed have any subdirectories (dirflg) or
2486 files (!dirflg); if it doesn't we should do full completion. Otherwise, the
2487 result looks funny to the user and "?" blows up the command for no good
2492 filbuf[len+1] = '*';
2493 filbuf[len+2] = NUL;
2494 if (dirflg) flags = ZX_DIRONLY;
2495 children = nzxpand(filbuf,flags);
2496 debug(F111,"cmifi children",filbuf,children);
2497 filbuf[len+1] = NUL;
2498 nzxpand(filbuf,flags); /* Restore previous list */
2505 /* Add doublequotes if there are spaces in the name */
2509 x = (qflag == '}'); /* (or braces) */
2513 if (filbuf[0] != '"' && filbuf[0] != '{')
2514 k = dquote(filbuf,ATMBL,x);
2517 debug(F111,"cmifi REPAINT filbuf",filbuf,k);
2518 if (k > 0) { /* Got more characters */
2519 debug(F101,"cmifi REPAINT cc","",cc);
2520 debug(F101,"cmifi REPAINT xc","",xc);
2521 debug(F110,"cmifi REPAINT bp-cc",bp-cc,0);
2522 debug(F110,"cmifi REPAINT bp-xc",bp-xc,0);
2523 sp = filbuf + cc; /* Point to new ones */
2524 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2528 for (i = 0; i < x; i++) {
2529 cmdchardel(); /* Back up over old partial spec */
2532 sp = filbuf; /* Point to new word start */
2533 debug(F110,"cmifi erase ok",sp,0);
2535 cc = k; /* How many new ones we just got */
2536 printf("%s",sp); /* Print them */
2537 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2538 bp--; /* Back up over NUL */
2539 debug(F110,"cmifi partial cmdbuf",cmdbuf,0);
2540 if (setatm(filbuf,0) < 0) {
2541 printf("?Partial name too long\n");
2545 debug(F111,"cmifi partial atmbuf",atmbuf,cc);
2548 #endif /* NOPARTIAL */
2550 } else { /* Unique, complete it. */
2553 /* isdir() function required for this! */
2555 debug(F111,"cmifi unique",filbuf,children);
2556 if (isdir(filbuf) && children > 0) {
2558 len = strlen(filbuf);
2559 if (len > 0 && len < ATMBL - 1) {
2560 if (filbuf[len-1] != dirsep) {
2561 filbuf[len] = dirsep;
2562 filbuf[len+1] = NUL;
2569 while ((*bp++ = *sp++)) ;
2571 if (setatm(filbuf,0) < 0) {
2572 printf("?Directory name too long\n");
2576 debug(F111,"cmifi directory atmbuf",atmbuf,cc);
2578 } else { /* Not a directory or dirflg */
2579 #endif /* CK_TMPDIR */
2581 #ifndef VMS /* VMS dir names are special */
2582 #ifndef datageneral /* VS dirnames must not end in ":" */
2585 len = strlen(filbuf);
2586 if (len > 0 && len < ATMBL - 1) {
2587 if (filbuf[len-1] != dirsep) {
2588 filbuf[len] = dirsep;
2589 filbuf[len+1] = NUL;
2593 #endif /* datageneral */
2595 sp = filbuf + cc; /* Point past what user typed. */
2603 if (filbuf[0] != '"' && filbuf[0] != '{')
2604 dquote(filbuf,ATMBL,x);
2606 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2610 for (i = 0; i < x; i++) {
2611 cmdchardel(); /* Back up over old partial spec */
2614 sp = filbuf; /* Point to new word start */
2615 debug(F111,"cmifi after erase sp=",sp,cc);
2617 printf("%s ",sp); /* Print the completed name. */
2621 addbuf(sp); /* Add the characters to cmdbuf. */
2622 if (setatm(filbuf,0) < 0) { /* And to atmbuf. */
2623 printf("?Completed name too long\n");
2627 inword = cmflgs = 0;
2628 *xp = brstrip(atmbuf); /* Return pointer to atmbuf. */
2629 if (dirflg && !isdir(*xp)) {
2630 printf("?Not a directory - %s\n", filbuf);
2639 #endif /* CK_TMPDIR */
2644 case 3: /* Question mark - file menu wanted */
2646 printf(dirflg ? " Directory name" : " Input file specification");
2652 /* If user typed an opening quote or brace, just skip past it */
2654 if (**xp == '"' || **xp == '{') {
2660 if (f) { /* If a conversion function is given */
2662 char *s = *xp; /* See if there are any variables in */
2663 while (*s) { /* the string and if so, expand them */
2665 #endif /* DOCHKVAR */
2668 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2673 /* reduce cc by number of \\ consumed by conversion */
2674 /* function (needed for OS/2, where \ is path separator) */
2675 cc -= (strlen(*xp) - strlen(atxbuf));
2676 #endif /* DOCHKVAR */
2683 #endif /* DOCHKVAR */
2686 debug(F111,"cmifi ? *xp, cc",*xp,cc);
2687 sp = *xp + cc; /* Insert "*" at end */
2689 *sp++ = '+'; /* Insert +, the DG wild card */
2692 #endif /* datageneral */
2695 if (! strchr(*xp, '.')) /* abde.e -> abcde.e* */
2696 strcat(*xp, ".*"); /* abc -> abc*.* */
2698 debug(F110,"cmifi ? wild",*xp,0);
2700 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2702 debug(F101,"cmifi matchdot","",matchdot);
2703 if (matchdot) nzxopts |= ZX_MATCHDOT;
2704 if (recursive) nzxopts |= ZX_RECURSE;
2705 y = nzxpand(*xp,nzxopts);
2710 printf(": %s\n",atmbuf);
2711 printf("%s%s",cmprom,cmdbuf);
2718 printf("?Off Limits: %s\n",atmbuf);
2721 printf("?No %s match - %s\n",
2722 dirflg ? "directories" : "files", atmbuf);
2729 printf("?Off Limits: %s\n",atmbuf);
2732 printf("?Too many %s match - %s\n",
2733 dirflg ? "directories" : "files", atmbuf);
2737 printf(", one of the following:\n");
2738 if (filhelp((int)y,"","",1,dirflg) < 0) {
2743 printf("%s%s",cmprom,cmdbuf);
2750 x = gtword(0); /* No, get a word */
2753 x = gtword(0); /* No, get a word */
2754 #endif /* BS_DIRSEP */
2759 /* C M F L D -- Parse an arbitrary field */
2762 -3 if no input present when required,
2763 -2 if field too big for buffer,
2764 -1 if reparse needed,
2765 0 otherwise, xp pointing to string result.
2767 NOTE: Global flag keepallchars says whether this routine should break on CR
2768 or LF: needed for MINPUT targets and DECLARE initializers, where we want to
2769 keep control characters if the user specifies them (March 2003). It might
2770 have been better to change the calling sequence but that was not practical.
2773 cmfld(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
2777 inword = 0; /* Initialize counts & pointers */
2782 debug(F110,"cmfld xdef 1",xdef,0);
2784 if (!xhlp) xhlp = "";
2785 if (!xdef) xdef = "";
2786 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2789 debug(F111,"cmfld xdef 2",xdef,cmflgs);
2790 debug(F111,"cmfld atmbuf 1",atmbuf,xc);
2792 if ((x = cmflgs) != 1) { /* Already confirmed? */
2793 x = gtword(0); /* No, get a word */
2795 if (setatm(xdef,0) < 0) { /* If so, use default, if any. */
2796 printf("?Default too long\n");
2800 *xp = atmbuf; /* Point to result. */
2801 debug(F111,"cmfld atmbuf 2",atmbuf,cmflgs);
2804 xc += cc; /* Count the characters. */
2805 debug(F111,"cmfld gtword",atmbuf,xc);
2806 debug(F101,"cmfld x","",x);
2809 printf("Command or field too long\n");
2811 case -3: /* Empty. */
2812 case -2: /* Out of space. */
2813 case -1: /* Reparse needed */
2817 debug(F111,"cmfld 1",atmbuf,xc);
2818 if (xc == 0) { /* If no input, return default. */
2819 if (setatm(xdef,0) < 0) {
2820 printf("?Default too long\n");
2824 *xp = atmbuf; /* Point to what we got. */
2825 debug(F111,"cmfld 2",atmbuf,(f) ? 1 : 0);
2826 if (f) { /* If a conversion function is given */
2827 zq = atxbuf; /* employ it now. */
2829 if ((*f)(*xp,&zq,&atxn) < 0)
2831 debug(F111,"cmfld 3",atxbuf,xc);
2832 /* Replace by new value -- for MINPUT only keep all chars */
2833 if (setatm(atxbuf,keepallchars ? 3:1) < 0) { /* 16 Mar 2003 */
2834 printf("Value too long\n");
2839 debug(F111,"cmfld 4",atmbuf,xc);
2840 if (**xp == NUL) { /* If variable evaluates to null */
2841 if (setatm(xdef,0) < 0) {
2842 printf("?Default too long\n");
2845 if (**xp == NUL) x = -3; /* If still empty, return -3. */
2847 debug(F111,"cmfld returns",*xp,x);
2850 if (xc == 0 && *xdef) {
2851 printf("%s ",xdef); /* If at beginning of field, */
2855 addbuf(xdef); /* Supply default. */
2856 inword = cmflgs = 0;
2857 if (setatm(xdef,0) < 0) {
2858 printf("?Default too long\n");
2860 } else /* Return as if whole field */
2861 return(0); /* typed, followed by space. */
2866 case 3: /* Question mark */
2867 debug(F110,"cmfld QUESTIONMARK",cmdbuf,0);
2869 printf(" Please complete this field");
2872 printf("\n%s%s",cmprom,cmdbuf);
2876 debug(F111,"cmfld gtword A x",cmdbuf,x);
2878 debug(F111,"cmfld gtword B x",cmdbuf,x);
2883 /* C M T X T -- Get a text string, including confirmation */
2886 Print help message 'xhlp' if ? typed, supply default 'xdef' if null
2887 string typed. Returns:
2889 -1 if reparse needed or buffer overflows.
2892 with cmflgs set to return code, and xp pointing to result string.
2895 cmtxt(xhlp,xdef,xp,f) char *xhlp; char *xdef; char **xp; xx_strp f; {
2901 if (!xhlp) xhlp = "";
2902 if (!xdef) xdef = "";
2908 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2911 debug(F101,"cmtxt cmflgs","",cmflgs);
2912 inword = 0; /* Start atmbuf counter off at 0 */
2914 if (cmflgs == -1) { /* If reparsing, */
2916 xc = (int)strlen(*xp); /* get back the total text length, */
2917 bp = *xp; /* and back up the pointers. */
2920 } else { /* otherwise, */
2921 /* debug(F100,"cmtxt: fresh start","",0); */
2922 *xp = ""; /* start fresh. */
2925 *atmbuf = NUL; /* And empty the atom buffer. */
2926 rtimer(); /* Reset timer */
2927 if ((x = cmflgs) != 1) {
2930 x = gtword(0); /* Get first word. */
2931 *xp = pp; /* Save pointer to it. */
2932 /* debug(F111,"cmtxt:",*xp,cc); */
2934 if (gtimer() > timelimit) {
2935 /* if (!quiet) printf("?Timed out\n"); */
2942 while (1) { /* Loop for each word in text. */
2943 xc += cc; /* Char count for all words. */
2944 /* debug(F111,"cmtxt gtword",atmbuf,xc); */
2945 /* debug(F101,"cmtxt x","",x); */
2948 if (gtimer() > timelimit) {
2950 extern int inserver;
2952 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
2953 doexit(GOOD_EXIT,0);
2956 /* if (!quiet) printf("?Timed out\n"); */
2962 case -9: /* Buffer overflow */
2963 printf("Command or field too long\n");
2966 case -3: /* Quit/Timeout */
2968 case -2: /* Overflow */
2969 case -1: /* Deletion */
2972 xc++; /* Just count it */
2974 case 1: /* CR or LF */
2975 if (xc == 0) *xp = xdef;
2976 if (f) { /* If a conversion function is given */
2978 zq = atxbuf; /* Point to the expansion buffer */
2979 atxn = CMDBL; /* specify its length */
2980 debug(F111,"cmtxt calling (*f)",*xp,atxbuf);
2981 if ((x = (*f)(*xp,&zq,&atxn)) < 0) return(-2);
2985 while (*sx++) cc++; /* (faster than calling strlen) */
2987 cc = (int)strlen(atxbuf);
2988 #endif /* COMMENT */
2989 /* Should be equal to (CMDBL - atxn) but isn't always. */
2991 if (cc < 1) { /* Nothing in expansion buffer? */
2992 *xp = xdef; /* Point to default string instead. */
2995 while (*sx++) cc++; /* (faster than calling strlen) */
2998 #endif /* COMMENT */
2999 } else { /* Expansion function got something */
3000 *xp = atxbuf; /* return pointer to it. */
3002 debug(F111,"cmtxt (*f)",*xp,cc);
3003 } else { /* No expansion function */
3005 /* Avoid a strlen() call */
3010 /* NO! xc is apparently not always set appropriately */
3012 #endif /* COMMENT */
3016 /* strlen() no longer needed */
3017 for (i = (int)strlen(xx) - 1; i > 0; i--)
3019 for (i = cc - 1; i > 0; i--)
3020 #endif /* COMMENT */
3021 if (xx[i] != SP) /* Trim trailing blanks */
3027 if (xc == 0) { /* Nothing typed yet */
3028 if (*xdef) { /* Have a default for this field? */
3029 printf("%s ",xdef); /* Yes, supply it */
3030 inword = cmflgs = 0;
3035 } else bleep(BP_WARN); /* No default */
3036 } else { /* Already in field */
3039 if (ckstrcmp(atmbuf,xdef,x,0)) { /* Matches default? */
3040 bleep(BP_WARN); /* No */
3041 } else if ((int)strlen(xdef) > x) { /* Yes */
3048 inword = cmflgs = 0;
3049 debug(F110,"cmtxt: addbuf",cmdbuf,0);
3055 case 3: /* Question Mark */
3057 printf(" Text string");
3060 printf("\n%s%s",cmprom,cmdbuf);
3064 printf("?Unexpected return code from gtword() - %d\n",x);
3071 /* C M K E Y -- Parse a keyword */
3075 table -- keyword table, in 'struct keytab' format;
3076 n -- number of entries in table;
3077 xhlp -- pointer to help string;
3078 xdef -- pointer to default keyword;
3079 f -- processing function (e.g. to evaluate variables)
3080 pmsg -- 0 = don't print error messages
3081 1 = print error messages
3082 2 = include CM_HLP keywords even if invisible
3084 4 = parse a switch (keyword possibly ending in : or =)
3086 -3 -- no input supplied and no default available
3087 -2 -- input doesn't uniquely match a keyword in the table
3088 -1 -- user deleted too much, command reparse required
3089 n >= 0 -- value associated with keyword
3092 cmkey(table,n,xhlp,xdef,f)
3093 /* cmkey */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3094 return(cmkey2(table,n,xhlp,xdef,"",f,1));
3097 cmkeyx(table,n,xhlp,xdef,f)
3098 /* cmkeyx */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3099 return(cmkey2(table,n,xhlp,xdef,"",f,0));
3102 cmswi(table,n,xhlp,xdef,f)
3103 /* cmswi */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3104 return(cmkey2(table,n,xhlp,xdef,"",f,4));
3108 cmkey2(table,n,xhlp,xdef,tok,f,pmsg)
3109 struct keytab table[];
3116 extern int havetoken;
3117 int i, tl, y, z = 0, zz, xc, wordlen = 0, cmswitch;
3120 if (!xhlp) xhlp = "";
3121 if (!xdef) xdef = "";
3125 printf("?Keyword table missing\n");
3128 tl = (int)strlen(tok);
3130 inword = xc = cc = 0; /* Clear character counters. */
3131 cmswitch = pmsg & 4; /* Flag for parsing a switch */
3133 debug(F101,"cmkey: pmsg","",pmsg);
3134 debug(F101,"cmkey: cmflgs","",cmflgs);
3135 debug(F101,"cmkey: cmswitch","",cmswitch);
3136 /* debug(F101,"cmkey: cmdbuf","",cmdbuf);*/
3140 if ((zz = cmflgs) == 1) { /* Command already entered? */
3141 if (setatm(xdef,0) < 0) { /* Yes, copy default into atom buf */
3142 printf("?Default too long\n");
3145 rtimer(); /* Reset timer */
3147 rtimer(); /* Reset timer */
3148 zz = gtword((pmsg == 4) ? 1 : 0);/* Otherwise get a command word */
3151 debug(F101,"cmkey table length","",n);
3152 debug(F101,"cmkey cmflgs","",cmflgs);
3153 debug(F101,"cmkey cc","",cc);
3157 debug(F111,"cmkey gtword xc",atmbuf,xc);
3158 debug(F101,"cmkey gtword zz","",zz);
3161 case -10: /* Timeout */
3162 if (gtimer() < timelimit) {
3163 zz = gtword((pmsg == 4) ? 1 : 0);
3167 extern int inserver;
3169 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3170 doexit(GOOD_EXIT,0);
3178 printf("Command or field too long\n");
3180 case -3: /* Null Command/Quit/Timeout */
3181 case -2: /* Buffer overflow */
3182 case -1: /* Or user did some deleting. */
3183 return(cmflgs = zz);
3187 case 0: /* User terminated word with space */
3188 case 4: /* or switch ending in : or = */
3189 wordlen = cc; /* Length if no conversion */
3190 if (cc == 0) { /* Supply default if we got nothing */
3191 if ((wordlen = setatm(xdef,(zz == 4) ? 2 : 0)) < 0) {
3192 printf("?Default too long\n");
3196 if (zz == 1 && cc == 0) /* Required field missing */
3199 if (f) { /* If a conversion function is given */
3201 zq = atxbuf; /* apply it */
3204 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3205 debug(F110,"cmkey atxbuf after *f",atxbuf,0);
3206 if (!*p2) /* Supply default if we got nothing */
3208 ckstrncpy(ppvnambuf,atmbuf,PPVLEN);
3209 if ((wordlen = setatm(p2,(zz == 4) ? 2 : 0)) < 0) {
3210 printf("Evaluated keyword too long\n");
3215 This bit lets us save more than one "word".
3216 For example, "define \%x echo one two three", "\%x".
3217 It works too, but it breaks labels, and therefore
3218 WHILE and FOR loops, etc.
3220 if (p2[wordlen] >= SP) {
3222 while (*p2 == SP) p2++;
3230 if (cmswitch && *atmbuf != '/') {
3233 printf("?Not a switch - %s\n",atmbuf);
3240 for (i = 0; i < wordlen; i++) {
3241 if (atmbuf[i] == ':' || atmbuf[i] == '=') {
3242 brkchar = atmbuf[i];
3250 /* This was an effective optimization but it breaks sometimes on labels. */
3251 if (tl && !isalpha(atmbuf[0])) { /* Precheck for token */
3252 for (i = 0; i < tl; i++) { /* Save function call to ckstrchr */
3253 if (tok[i] == atmbuf[0]) {
3254 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3255 ungword(); /* Put back the following word */
3256 return(-5); /* Special return code for token */
3260 #endif /* TOKPRECHECK */
3262 y = lookup(table,atmbuf,n,&z); /* Look up word in the table */
3263 debug(F111,"cmkey lookup",atmbuf,y);
3264 debug(F101,"cmkey zz","",zz);
3265 debug(F101,"cmkey cmflgs","",cmflgs);
3266 debug(F101,"cmkey crflag","",crflag);
3268 case -3: /* Nothing to look up */
3270 case -2: /* Ambiguous */
3274 printf("?Ambiguous - %s\n",atmbuf);
3278 case -1: /* Not found at all */
3281 for (i = 0; i < tl; i++) /* Check for token */
3282 if (tok[i] == *atmbuf) { /* Got one */
3283 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3284 ungword(); /* Put back the following word */
3285 return(-5); /* Special return code for token */
3288 #endif /* TOKPRECHECK */
3290 if (tl == 0) { /* No tokens were included */
3292 /* In OS/2 and Windows, allow for a disk letter like DOS */
3293 if (isalpha(*atmbuf) && *(atmbuf+1) == ':')
3296 if ((pmsg & 1) && !quiet) {
3298 printf("?No keywords match - %s\n",atmbuf); /* cmkey */
3300 return(cmflgs = -9);
3302 if (cmflgs == 1 || cmswitch) /* cmkey2 or cmswi */
3303 return(cmflgs = -6);
3305 return(cmflgs = -2);
3306 /* The -6 code is to let caller try another table */
3311 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3312 #endif /* CK_RECALL */
3315 cmkwflgs = table[z].flgs;
3320 case 2: /* User terminated word with ESC */
3321 debug(F101,"cmkey Esc cc","",cc);
3323 if (*xdef != NUL) { /* Nothing in atmbuf */
3324 printf("%s ",xdef); /* Supply default if any */
3329 if (setatm(xdef,0) < 0) {
3330 printf("?Default too long\n");
3333 inword = cmflgs = 0;
3334 debug(F111,"cmkey: default",atmbuf,cc);
3336 debug(F101,"cmkey Esc pmsg","",0);
3339 Chained FDBs... The idea is that this function might not have a default,
3340 but the next one might. But if it doesn't, there is no way to come back to
3341 this one. To be revisited later...
3343 if (xcmfdb) /* Chained fdb -- try next one */
3345 #endif /* COMMENT */
3346 if (pmsg & (1|4)) { /* So for now just beep */
3352 if (f) { /* If a conversion function is given */
3354 zq = atxbuf; /* apply it */
3357 if ((*f)(atmbuf,&zq,&atxn) < 0)
3361 if (setatm(pp,0) < 0) {
3362 printf("Evaluated keyword too long\n");
3366 y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
3367 debug(F111,"cmkey lookup y",atmbuf,y);
3368 debug(F111,"cmkey lookup z",atmbuf,z);
3369 if (y == -2 && z >= 0 && z < n) { /* Ambiguous */
3371 int j, k, len = 9999; /* Do partial completion */
3372 /* Skip past any abbreviations in the table */
3373 for ( ; z < n; z++) {
3374 if ((table[z].flgs & CM_ABR) == 0)
3376 if (!(table[z].flgs & CM_HLP) || (pmsg & 2))
3379 debug(F111,"cmkey partial z",atmbuf,z);
3380 debug(F111,"cmkey partial n",atmbuf,n);
3381 for (j = z+1; j < n; j++) {
3382 debug(F111,"cmkey partial j",table[j].kwd,j);
3383 if (ckstrcmp(atmbuf,table[j].kwd,cc,0))
3385 if (table[j].flgs & CM_ABR)
3387 if ((table[j].flgs & CM_HLP) && !(pmsg & 2))
3389 k = ckstrpre(table[z].kwd,table[j].kwd);
3390 debug(F111,"cmkey partial k",table[z].kwd,k);
3392 len = k; /* Length of longest common prefix */
3394 debug(F111,"cmkey partial len",table[z].kwd,len);
3395 if (len != 9999 && len > cc) {
3396 ckstrncat(atmbuf,table[z].kwd+cc,ATMBL);
3398 printf("%s",atmbuf+cc);
3399 ckstrncat(cmdbuf,atmbuf+cc,CMDBL);
3403 #endif /* NOPARTIAL */
3406 } else if (y == -3) {
3409 } else if (y == -1) { /* Not found */
3410 if ((pmsg & 1) && !quiet) {
3412 printf("?No keywords match - \"%s\"\n",atmbuf);
3418 If we found it, but it's a help-only keyword and the "help" bit is not
3419 set in pmsg, then not found.
3421 debug(F101,"cmkey flgs","",table[z].flgs);
3422 if (test(table[z].flgs,CM_HLP) && ((pmsg & 2) == 0)) {
3423 if ((pmsg & 1) && !quiet) {
3425 printf("?No keywords match - %s\n",atmbuf);
3431 See if the keyword just found has the CM_ABR bit set in its flgs field, and
3432 if so, search forwards in the table for a keyword that has the same kwval
3433 but does not have CM_ABR (or CM_INV?) set, and then expand using the full
3434 keyword. WARNING: This assumes that (a) keywords are in alphabetical order,
3435 and (b) the CM_ABR bit is set only if the the abbreviated keyword is a true
3436 abbreviation (left substring) of the full keyword.
3438 if (test(table[z].flgs,CM_ABR)) {
3440 for (zz = z+1; zz < n; zz++)
3441 if ((table[zz].kwval == table[z].kwval) &&
3442 (!test(table[zz].flgs,CM_ABR)) &&
3443 (!test(table[zz].flgs,CM_INV))) {
3448 xp = table[z].kwd + cc;
3449 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3462 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3463 #endif /* CK_RECALL */
3464 cmkwflgs = table[z].flgs;
3469 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3470 bp--; /* Replace trailing space with : */
3482 debug(F110,"cmkey: addbuf",cmdbuf,0);
3485 case 3: /* User typed "?" */
3486 if (f) { /* If a conversion function is given */
3488 zq = atxbuf; /* do the conversion now. */
3491 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3492 if (setatm(pp,0) < 0) {
3493 printf("?Evaluated keyword too long\n");
3497 y = lookup(table,atmbuf,n,&z); /* Look up what we have so far. */
3500 Strictly speaking if the main keyword table search fails,
3501 then we should look in the token table if one is given.
3502 But in practice, tokens are also included in the main
3506 if ((pmsg & 1) && !quiet) {
3508 printf(" No keywords match\n");
3514 /* This is to allow ?-help to work immediately after a token */
3515 /* without having to type an intermediate space */
3517 for (i = 0; i < tl; i++) /* Check for token */
3518 if (tok[i] == *atmbuf) { /* Got one */
3519 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3520 ungword(); /* Put back the following word */
3521 cmflgs = 3; /* Force help next time around */
3522 return(-5); /* Special return code for token */
3525 #endif /* COMMENT */
3528 printf(" One of the following:\n");
3530 printf(" %s, one of the following:\n",xhlp);
3533 x = pmsg & (2|4); /* See kwdhelp() comments */
3534 if (atmbuf[0]) /* If not at beginning of field */
3535 x |= 1; /* also show invisibles */
3536 kwdhelp(table,n,atmbuf,"","",1,x);
3541 if (tl > 0 && topcmd != XXHLP) /* This is bad... */
3542 printf("or a macro name (\"do ?\" for a list) ");
3545 if (*atmbuf == NUL && !havetoken) {
3547 printf("or the token %c\n",*tok);
3549 printf("or one of the tokens: %s\n",ckspread(tok));
3551 printf("%s%s", cmprom, cmdbuf);
3556 printf("\n%d - Unexpected return code from gtword\n",zz);
3557 return(cmflgs = -2);
3559 zz = gtword((pmsg == 4) ? 1 : 0);
3560 debug(F111,"cmkey gtword zz",atmbuf,zz);
3565 chktok(tlist) char *tlist; {
3568 while (*p != NUL && *p != *atmbuf) p++;
3569 return((*p) ? (int) *p : 0);
3572 /* Routines for parsing and converting dates and times */
3574 #define isdatesep(c) (ckstrchr(" -/._",c))
3576 #define CMDATEBUF 1024
3577 char cmdatebuf[CMDATEBUF+4] = { NUL, NUL };
3578 static char * cmdatebp = cmdatebuf;
3579 char * cmdatemsg = NULL;
3581 static struct keytab timeunits[] = {
3582 { "days", TU_DAYS, 0 },
3583 { "months", TU_MONTHS, 0 },
3584 { "weeks", TU_WEEKS, 0 },
3585 { "wks", TU_WEEKS, 0 },
3586 { "years", TU_YEARS, 0 },
3587 { "yrs", TU_YEARS, 0 }
3589 static int nunits = (sizeof(timeunits) / sizeof(struct keytab));
3596 static struct keytab symdaytab[] = {
3597 { "now", SYM_NOW, 0 },
3598 { "today", SYM_TODA, 0 },
3599 { "tomorrow", SYM_TOMO, 0 },
3600 { "yesterday", SYM_YEST, 0 }
3602 static int nsymdays = (sizeof(symdaytab) / sizeof(struct keytab));
3604 static struct keytab daysofweek[] = {
3607 { "Saturday", 6, 0 },
3609 { "Thursday", 4, 0 },
3610 { "Tuesday", 2, 0 },
3611 { "Wednesday", 3, 0 }
3614 static struct keytab usatz[] = { /* RFC 822 timezones */
3615 { "cdt", 5, 0 }, /* Values are GMT offsets */
3627 static int nusatz = (sizeof(usatz) / sizeof(struct keytab));
3630 /* C M C V T D A T E -- Converts free-form date to standard form. */
3634 s = pointer to free-format date, time, or date and time.
3635 t = 0: return time only if time was given in s.
3636 t = 1: always return time (00:00:00 if no time given in s).
3637 t = 2: allow time to be > 24:00:00.
3640 Pointer to "yyyymmdd hh:mm:ss" (local date-time) on success.
3644 Before final release the following long lines should be wrapped.
3645 Until then we leave them long since wrapping them wrecks EMACS's
3649 /* asctime pattern */
3650 static char * atp1 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9]";
3652 /* asctime pattern with timezone */
3653 static char * atp2 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [A-Z][A-Z][A-Z] [0-9][0-9][0-9][0-9]";
3655 #define DATEBUFLEN 127
3658 #define isleap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
3659 static int mdays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
3667 #define DELTABUF 256
3668 static char deltabuf[DELTABUF];
3669 static char * deltabp = deltabuf;
3672 cmdelta(yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss)
3673 int yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss;
3675 int zyy, zmo, zdd, zhh, zmm, zss;
3676 long t1, t2, t3, t4;
3677 long d1 = 0, d2, d3;
3678 char datebuf[DATEBUFLEN+1];
3682 debug(F101,"cmdelta yy","",yy);
3683 debug(F101,"cmdelta mo","",mo);
3684 debug(F101,"cmdelta dd","",dd);
3685 debug(F101,"cmdelta hh","",hh);
3686 debug(F101,"cmdelta mm","",mm);
3687 debug(F101,"cmdelta ss","",ss);
3688 debug(F101,"cmdelta sin","",sign);
3689 debug(F101,"cmdelta dyy","",dyy);
3690 debug(F101,"cmdelta dmo","",dmo);
3691 debug(F101,"cmdelta ddd","",ddd);
3692 debug(F101,"cmdelta dhh","",dhh);
3693 debug(F101,"cmdelta dmm","",dmm);
3694 debug(F101,"cmdelta dss","",dss);
3698 if (yy < 0 || yy > 9999) {
3699 makestr(&cmdatemsg,"Base year out of range");
3700 debug(F111,"cmdelta",cmdatemsg,-1);
3703 if (mo < 1 || mo > 12) {
3704 makestr(&cmdatemsg,"Base month out of range");
3705 debug(F111,"cmdelta",cmdatemsg,-1);
3708 if (dd < 1 || dd > mdays[mo]) {
3709 makestr(&cmdatemsg,"Base day out of range");
3710 debug(F111,"cmdelta",cmdatemsg,-1);
3713 if (hh < 0 || hh > 23) {
3714 makestr(&cmdatemsg,"Base hour out of range");
3715 debug(F111,"cmdelta",cmdatemsg,-1);
3718 if (mm < 0 || mm > 59) {
3719 makestr(&cmdatemsg,"Base minute out of range");
3720 debug(F111,"cmdelta",cmdatemsg,-1);
3723 if (ss < 0 || ss > 60) {
3724 makestr(&cmdatemsg,"Base second out of range");
3725 debug(F111,"cmdelta",cmdatemsg,-1);
3728 sign = (sign < 0) ? -1 : 1;
3731 if (mo > 12 || mo < 0) {
3738 if (yy > 9999 || yy < 0) {
3739 makestr(&cmdatemsg,"Result year out of range");
3740 debug(F111,"cmdelta",cmdatemsg,-1);
3744 sprintf(datebuf,"%04d%02d%02d %02d:%02d:%02d",yy,mo,dd,hh,mm,ss);
3746 debug(F111,"cmdelta mjd",datebuf,d1);
3748 t1 = hh * 3600 + mm * 60 + ss; /* Base time to secs since midnight */
3749 t2 = dhh * 3600 + dmm * 60 + dss; /* Delta time, ditto */
3750 t3 = t1 + (sign * t2); /* Get sum (or difference) */
3752 d2 = (sign * ddd); /* Delta days */
3755 t4 = t3 % 86400L; /* Fractional part of day */
3756 if (t4 < 0) { /* If negative */
3757 d2--; /* one less delta day */
3758 t4 += 86400L; /* get positive seconds */
3760 hh = (int) (t4 / 3600L);
3761 mm = (int) (t4 % 3600L) / 60;
3762 ss = (int) (t4 % 3600L) % 60;
3764 sprintf(datebuf,"%s %02d:%02d:%02d", mjd2date(d1+d2),hh,mm,ss);
3768 len = strlen(datebuf);
3769 k = deltabp - (char *)deltabuf; /* Space used */
3770 n = DELTABUF - k - 1; /* Space left */
3771 if (n < len) { /* Not enough? */
3772 deltabp = deltabuf; /* Wrap around */
3775 ckstrncpy(deltabp,datebuf,n);
3783 /* Convert Delta Time to Seconds */
3786 delta2sec(s,result) char * s; long * result; {
3787 long ddays = 0L, zz;
3788 int dsign = 1, dhours = 0, dmins = 0, dsecs = 0, units;
3789 int state = NEED_DAYS;
3790 char *p, *p2, *p3, c = 0;
3796 if ((int)strlen(s) > 63)
3798 ckstrncpy(buf,s,64);
3801 if (*p != '+' && *p != '-')
3806 while (*p == SP) /* Skip intervening spaces */
3809 while (state) { /* FSA to parse delta time */
3810 if (state < 0 || !isdigit(*p))
3812 p2 = p; /* Get next numeric field */
3813 while (isdigit(*p2))
3815 c = *p2; /* And break character */
3816 *p2 = NUL; /* Terminate the number */
3817 switch (state) { /* Interpret according to state */
3818 case NEED_DAYS: /* Initial */
3819 if ((c == '-') || /* VMS format */
3820 ((c == 'd' || c == 'D')
3821 && !isalpha(*(p2+1)))) { /* Days */
3825 else /* if anything is left */
3826 state = NEED_HRS; /* now we want hours. */
3827 } else if (c == ':') { /* delimiter is colon */
3828 dhours = atoi(p); /* so it's hours */
3829 state = NEED_MINS; /* now we want minutes */
3830 } else if (!c) { /* end of string */
3831 dhours = atoi(p); /* it's still hours */
3832 state = 0; /* and we're done */
3833 } else if (isalpha(c) || c == SP) {
3834 if (c == SP) { /* It's a keyword? */
3835 p2++; /* Skip spaces */
3838 } else { /* or replace first letter */
3841 p3 = p2; /* p2 points to beginning of keyword */
3842 while (isalpha(*p3)) /* Find end of keyword */
3844 c = *p3; /* NUL it out so we can look it up */
3845 if (*p3) /* p3 points to keyword terminator */
3847 if ((units = lookup(timeunits,p2,nunits,NULL)) < 0)
3849 *p2 = NUL; /* Re-terminate the number */
3851 while (*p3 == SP) /* Point at field after units */
3866 } else { /* Anything else */
3867 state = -1; /* is an error */
3870 case NEED_HRS: /* Looking for hours */
3881 case NEED_MINS: /* Looking for minutes */
3892 case NEED_SECS: /* Looking for seconds */
3903 case NEED_FRAC: /* Fraction of second */
3904 if (!c && rdigits(p)) {
3913 if (c) /* next field if any */
3919 /* if days > 24854 and sizeof(long) == 32 we overflow */
3921 zz = ddays * 86400L;
3922 if (zz < 0L) /* This catches it */
3924 zz += dhours * 3600L + dmins * 60L + dsecs;
3932 cmcvtdate(s,t) char * s; int t; {
3933 int x, i, j, k, hh, mm, ss, ff, pmflag = 0, nodate = 0, len, dow;
3934 int units, isgmt = 0, gmtsign = 0, d = 0, state = 0, nday;
3935 int kn = 0, ft[8], isletter = 0, f2len = 0;
3937 int zhh = 0; /* Timezone adjustments */
3941 int dsign = 1; /* Delta-time adjustments */
3950 char * fld[8], * p = "", * p2, * p3; /* Assorted buffers and pointers */
3952 char * year = NULL, * month = NULL, * day = NULL;
3953 char * hour = "00", * min = "00", * sec = "00";
3956 char xbuf[DATEBUFLEN+1];
3957 char ybuf[DATEBUFLEN+1];
3958 char zbuf[DATEBUFLEN+1];
3959 char yyyymmdd[YYYYMMDD];
3964 char timbuf[16], *tb, cc;
3965 char * dp = NULL; /* Result pointer */
3970 while (*s == SP) s++; /* Gobble any leading blanks */
3971 if (isalpha(*s)) /* Remember if 1st char is a letter */
3975 debug(F110,"cmcvtdate",s,len);
3976 if (len == 0) { /* No arg - return current date-time */
3980 if (len > DATEBUFLEN) { /* Check length of arg */
3981 makestr(&cmdatemsg,"Date-time string too long");
3982 debug(F111,"cmcvtdate",cmdatemsg,-1);
3985 hh = 0; /* Init time to 00:00:00.0 */
3992 if (*p) { /* Init time to current time */
3993 x = ckstrncpy(dbuf,p,26);
3995 hh = atoi(&dbuf[11]);
3996 mm = atoi(&dbuf[14]);
3997 ss = atoi(&dbuf[17]);
4000 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD); /* Init date to current date */
4001 ckstrncpy(yearbuf,yyyymmdd,5);
4002 ckstrncpy(monbuf,&yyyymmdd[4],3);
4003 ckstrncpy(daybuf,&yyyymmdd[6],3);
4007 nday = atoi(daybuf);
4008 ckstrncpy(xbuf,s,DATEBUFLEN); /* Make a local copy we can poke */
4009 s = xbuf; /* Point to it */
4015 /* Special preset formats... */
4017 if (len >= 14) { /* FTP MDTM all-numeric date */
4019 c = s[14]; /* e.g. 19980615100045.014 */
4024 ckstrncpy(yyyymmdd,s,8+1);
4030 x = 0; /* Becomes > 0 for asctime format */
4031 if (isalpha(s[0])) {
4032 if (len == 24) { /* Asctime format? */
4033 /* Sat Jul 14 15:57:32 2001 */
4034 x = ckmatch(atp1,s,0,0);
4035 debug(F111,"cmcvtdate asctime",s,x);
4036 } else if (len == 28) { /* Or Asctime plus timezone? */
4037 /* Sat Jul 14 15:15:39 EDT 2001 */
4038 x = ckmatch(atp2,s,0,0);
4039 debug(F111,"cmcvtdate asctime+timezone",s,x);
4042 if (x > 0) { /* Asctime format */
4044 strncpy(yearbuf,s + len - 4,4);
4046 for (i = 0; i < 3; i++)
4049 if ((xx = lookup(cmonths,tmpbuf,12,NULL)) < 0) {
4050 makestr(&cmdatemsg,"Invalid month");
4051 debug(F111,"cmcvtdate",cmdatemsg,-1);
4054 debug(F101,"cmcvtdate asctime month","",xx);
4055 monbuf[0] = (xx / 10) + '0';
4056 monbuf[1] = (xx % 10) + '0';
4058 daybuf[0] = (s[8] == ' ' ? '0' : s[8]);
4062 for (i = 11; i < 19; i++)
4065 ckmakmsg(zbuf,18,yearbuf,monbuf,daybuf,xbuf);
4066 debug(F110,"cmcvtdate asctime ok",zbuf,0);
4072 n = ckmakmsg(ybuf,DATEBUFLEN-4,zbuf," ",NULL,NULL);
4077 ckstrncpy(xbuf,ybuf,DATEBUFLEN);
4083 /* Check for day of week */
4086 while (*p == SP) p++;
4092 if (*p2 == ',' || *p2 == SP || !*p2) {
4093 cc = *p2; /* Save break char */
4094 *p2 = NUL; /* NUL it out */
4095 p3 = p2; /* Remember this spot */
4096 if ((dow = lookup(daysofweek,p,7,NULL)) > -1) {
4097 debug(F111,"cmcvtdate dow",p,dow);
4099 if (cc == ',' || cc == SP) { /* Point to next field */
4101 while (*s == SP) s++;
4104 debug(F111,"cmcvtdate dow new p",p,dow);
4106 } else if (isalpha(*p) && cc == ',') {
4107 makestr(&cmdatemsg,"Unrecognized day of week");
4108 debug(F111,"cmcvtdate",cmdatemsg,-1);
4118 len = strlen(s); /* Update length */
4119 debug(F111,"cmcvtdate s",s,len);
4121 debug(F111,"cmcvtdate dow",s,dow);
4122 if (dow > -1) { /* Have a day-of-week number */
4124 zz = mjd(zzndate()); /* Get today's MJD */
4125 debug(F111,"cmcvtdate zz","",zz);
4126 j = (((int)(zz % 7L)) + 3) % 7; /* Today's day-of-week number */
4127 debug(F111,"cmcvtdate j","",j);
4128 hh = 0; /* Init time to midnight */
4132 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD);
4135 n = dow - j; /* Days from now */
4138 if (n < 0) n += 7; /* Add to MJD */
4140 ckstrncpy(yyyymmdd,mjd2date(zz),YYYYMMDD); /* New date */
4143 debug(F111,"cmcvtdate A",yyyymmdd,len);
4144 if (len == 0) { /* No more fields after this */
4145 ckmakmsg(zbuf,18,yyyymmdd," 00:00:00",NULL,NULL);
4150 if (rdigits(p) && len < 8) /* Next field is time? */
4151 goto dotime; /* If so go straight to time section */
4155 else if (isdigit(*(p+1)) && (*(p+2) == ':'))
4159 debug(F111,"cmcvtdate B s",s,dow);
4160 debug(F111,"cmcvtdate B p",p,dow);
4162 if (*s == '+' || *s == '-') { /* Delta time only - skip ahead. */
4167 /* Day of week given followed by something that is not a time */
4168 /* or a delta so it can't be valid */
4169 makestr(&cmdatemsg,"Invalid tokens after day of week");
4170 debug(F111,"cmcvtdate fail",cmdatemsg,-1);
4174 /* Handle "today", "yesterday", "tomorrow", and +/- n units */
4176 if (ckstrchr("TtYyNn",s[0])) {
4177 int i, k, n, minus = 0;
4181 debug(F111,"cmcvtdate mjd",s,jd);
4183 /* Symbolic date: TODAY, TOMORROW, etc...? */
4185 s2 = s; /* Find end of keyword */
4187 while (isalpha(*s2)) { /* and get its length */
4191 c = *s2; /* Zap but save delimiter */
4193 k = lookup(symdaytab,s,nsymdays,NULL); /* Look up keyword */
4194 *s2 = c; /* Replace delimiter */
4195 if (k < 0) /* Keyword not found */
4198 while (*s3 == SP) /* Skip whitespace */
4200 if (*s3 == '_' || *s3 == ':')
4203 switch (k) { /* Have keyword */
4204 case SYM_NOW: /* NOW */
4205 ckstrncpy(ybuf,ckdate(),DATEBUFLEN);
4206 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4208 if (*s3) { /* No overwriting current time. */
4209 ckstrncat(ybuf," ",DATEBUFLEN);
4210 ckstrncat(ybuf,s3,DATEBUFLEN);
4213 default: /* Yesterday, Today, and Tomorrow */
4214 if (k == SYM_TOMO) { /* TOMORROW */
4215 strncpy(ybuf,mjd2date(jd+1),8);
4216 } else if (k == SYM_YEST) { /* YESTERDAY */
4217 strncpy(ybuf,mjd2date(jd-1),8);
4218 } else { /* TODAY */
4219 strncpy(ybuf,ckdate(),8);
4221 strncpy(ybuf+8," 00:00:00",DATEBUFLEN-8); /* Default time is 0 */
4222 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4224 if (*s3) { /* If something follows keyword... */
4225 if (isdigit(*s3)) { /* Time - overwrite default time */
4226 strncpy(ybuf+8,s+i,DATEBUFLEN-8);
4227 } else { /* Something else, keep default time */
4228 ckstrncat(ybuf," ",DATEBUFLEN); /* and append */
4229 ckstrncat(ybuf,s3,DATEBUFLEN); /* whatever we have */
4233 s = ybuf; /* Point to rewritten date-time */
4234 len = strlen(s); /* Update length */
4235 isletter = 0; /* Cancel this */
4238 /* Regular free-format non-symbolic date */
4242 debug(F111,"cmcvtdate NORMAL",s,len);
4243 debug(F111,"cmcvtdate dow",s,dow);
4244 if (yyyymmdd[0] && !year) {
4245 ckstrncpy(yearbuf,yyyymmdd,5);
4246 ckstrncpy(monbuf,&yyyymmdd[4],3);
4247 ckstrncpy(daybuf,&yyyymmdd[6],3);
4251 nday = atoi(daybuf);
4253 if (isdigit(s[0])) { /* Time without date? */
4256 debug(F111,"cmcvtdate NORMAL X1",s,len);
4258 } else if (len > 1 && isdigit(s[1]) && s[2] == ':') {
4259 debug(F111,"cmcvtdate NORMAL X2",s,len);
4261 } else if (rdigits(s) && len < 8) {
4262 debug(F111,"cmcvtdate NORMAL X3",s,len);
4266 if (len >= 8 && isdigit(*s)) { /* Check first for yyyymmdd* */
4267 debug(F111,"cmcvtdate NORMAL A",s,len);
4269 s[8] = NUL; /* Isolate first 8 characters */
4271 /* Have valid time separator? */
4272 p2 = cc ? ckstrchr(" Tt_-:",cc) : NULL;
4274 ckstrncpy(yyyymmdd,s,YYYYMMDD); /* Valid separator */
4276 s += 8; /* or time not given */
4277 if (cc) s++; /* Keep date */
4278 p = s; /* and go handle time */
4282 makestr(&cmdatemsg,"Numeric date too long");
4284 makestr(&cmdatemsg,"Invalid date-time separator");
4285 debug(F111,"cmcvtdate",cmdatemsg,-1);
4289 s[8] = cc; /* Put this back! */
4291 debug(F111,"cmcvtdate NORMAL non-yyyymmdd",s,len);
4293 /* Free-format date -- figure it out */
4296 if (*s && !isdigit(*s)) {
4297 makestr(&cmdatemsg,"Unrecognized word in date");
4298 debug(F111,"cmcvtdate",cmdatemsg,-1);
4301 #endif /* COMMENT */
4302 for (i = 0; i < 8; i++) /* Field types */
4304 fld[i = 0] = (p = s); /* First field */
4305 while (*p) { /* Get next two fields */
4306 if (isdatesep(*p)) { /* Have a date separator */
4309 } else if (i == 1 && *p != datesep) {
4310 makestr(&cmdatemsg,"Inconsistent date separators");
4311 debug(F111,"cmcvtdate",cmdatemsg,-1);
4314 *p++ = NUL; /* Replace by NUL */
4315 if (*p) { /* Now we're at the next field */
4316 while (*p == SP) p++; /* Skip leading spaces */
4317 if (!*p) break; /* Make sure we still have something */
4318 if (i == 2) /* Last one? */
4320 fld[++i] = p; /* No, record pointer to this one */
4324 } else if ((*p == 'T' || *p == 't') && isdigit(*(p+1))) { /* Time */
4327 } else if (*p == ':') {
4328 if (i == 0 && p == s) {
4331 } else if (i != 0) { /* After a date */
4332 if (i == 2) { /* OK as date-time separator (VMS) */
4337 makestr(&cmdatemsg,"Too few fields in date");
4339 makestr(&cmdatemsg,"Misplaced time separator");
4340 debug(F111,"cmcvtdate",cmdatemsg,-1);
4343 nodate = 1; /* Or without a date */
4348 if (p > s && i == 0) /* Make sure we have a date */
4349 nodate = 1; /* No date. */
4351 if (nodate && dow > -1) { /* Have implied date from DOW? */
4352 goto dotime; /* Use, use that, go do time. */
4354 } else if (nodate) { /* No date and no implied date */
4355 char *tmp = NULL; /* Substitute today's date */
4360 makestr(&cmdatemsg,"Problem supplying current date");
4361 debug(F111,"cmcvtdate",cmdatemsg,-1);
4364 ckstrncpy(dbuf,tmp,26); /* Reformat */
4365 if (dbuf[8] == SP) dbuf[8] = '0';
4366 fld[0] = dbuf+8; /* dd */
4368 fld[1] = dbuf+4; /* mmm */
4370 fld[2] = dbuf+20; /* yyyy */
4372 hh = atoi(&dbuf[11]);
4373 mm = atoi(&dbuf[14]);
4374 ss = atoi(&dbuf[17]);
4375 p = s; /* Back up source pointer to reparse */
4377 makestr(&cmdatemsg,"Too few fields in date");
4378 debug(F111,"cmcvtdate",cmdatemsg,-1);
4381 /* Have three date fields - see what they are */
4383 for (k = 0, j = 0; j < 3; j++) { /* Get number of non-numeric fields */
4384 ft[j] = rdigits(fld[j]);
4385 debug(F111,"cmcvtdate fld",fld[j],j);
4389 kn = k; /* How many numeric fields */
4390 month = NULL; /* Strike out default values */
4394 if (k == 2 && ft[2] > 0) { /* Jul 20, 2001 */
4396 xx = strlen(fld[1]);
4398 if (xx > 0) if (p3[xx-1] == ',') {
4403 } else p3[xx-1] = ',';
4406 if (k > 1) { /* We can have only one non-numeric */
4408 makestr(&cmdatemsg,"Unrecognized word in date");
4409 else if (!ft[2] && isdigit(*(fld[2])))
4410 makestr(&cmdatemsg,"Invalid date-time separator");
4412 makestr(&cmdatemsg,"Too many non-numeric fields in date");
4413 debug(F111,"cmcvtdate",cmdatemsg,-1);
4418 } else if (!ft[1]) {
4420 } else if (!ft[2]) {
4421 makestr(&cmdatemsg,"Non-digit in third date field");
4422 debug(F111,"cmcvtdate",cmdatemsg,-1);
4428 if ((x = lookup(cmonths,fld[k],12,NULL)) < 0) {
4429 makestr(&cmdatemsg,"Unknown month");
4430 debug(F111,"cmcvtdate",cmdatemsg,-1);
4433 sprintf(tmpbuf,"%02d",x);
4436 f2len = strlen(fld[2]); /* Length of 3rd field */
4438 if (k == 0) { /* monthname dd, yyyy */
4441 } else if (((int)strlen(fld[0]) == 4)) { /* yyyy-xx-dd */
4445 month = fld[1]; /* yyyy-mm-dd */
4446 } else if (f2len == 4) { /* xx-xx-yyyy */
4448 if (month) { /* dd-name-yyyy */
4450 } else { /* xx-xx-yyyy */
4454 if (((f0 > 12) && (f1 <= 12)) || (f1 <= 12 && f0 == f1)) {
4455 day = fld[0]; /* mm-dd-yyyy */
4457 } else if ((f0 <= 12) && (f1 > 12)) {
4458 if (!rdigits(fld[1])) {
4459 makestr(&cmdatemsg,"Day not numeric");
4460 debug(F111,"cmcvtdate",cmdatemsg,-1);
4463 day = fld[1]; /* dd-mm-yyyy */
4468 makestr(&cmdatemsg,"Day or month out of range");
4470 makestr(&cmdatemsg,"Day and month are ambiguous");
4471 debug(F111,"cmcvtdate",cmdatemsg,-1);
4475 } else if ((f2len < 4) && /* dd mmm yy (RFC822) */
4476 !rdigits(fld[1]) && /* middle field is monthname */
4481 makestr(&cmdatemsg,"Too few digits in year");
4482 debug(F111,"cmcvtdate",cmdatemsg,-1);
4485 tmpyear = atoi(fld[2]);
4486 if (tmpyear < 50) /* RFC 2822 windowing */
4488 else /* This includes 3-digit years. */
4490 year = ckitoa(tmpyear);
4492 } else if ((f2len < 4) && (k < 0) && ((int)strlen(fld[0]) < 4)) {
4493 makestr(&cmdatemsg,"Ambiguous numeric date");
4494 debug(F111,"cmcvtdate",cmdatemsg,-1);
4496 } else if ((f2len > 4) && ft[2]) {
4497 makestr(&cmdatemsg,"Too many digits in year");
4498 debug(F111,"cmcvtdate",cmdatemsg,-1);
4501 makestr(&cmdatemsg,"Unexpected date format");
4502 debug(F111,"cmcvtdate",cmdatemsg,-1);
4506 sprintf(tmpbuf,"%02d",x); /* 2-digit numeric month */
4512 state = 4 = fractions of seconds
4516 if (isletter && (s == p)) {
4517 makestr(&cmdatemsg,"Unknown date-time word");
4518 debug(F111,"cmcvtdate",cmdatemsg,-1);
4521 if (!year && yyyymmdd[0]) {
4522 debug(F110,"cmcvtdate dotime yyyymmdd",yyyymmdd,0);
4523 for (i = 0; i < 4; i++)
4524 yearbuf[i] = yyyymmdd[i];
4526 monbuf[0] = yyyymmdd[4];
4527 monbuf[1] = yyyymmdd[5];
4529 daybuf[0] = yyyymmdd[6];
4530 daybuf[1] = yyyymmdd[7];
4533 nday = atoi(daybuf);
4538 makestr(&cmdatemsg,"Internal error - date not defaulted");
4539 debug(F111,"cmcvtdate",cmdatemsg,-1);
4542 /* Get here with day, month, and year set */
4543 debug(F110,"cmcvtdate dotime day",day,0);
4544 debug(F110,"cmcvtdate dotime month",month,0);
4545 debug(F110,"cmcvtdate dotime year",year,0);
4546 debug(F110,"cmcvtdate dotime s",s,0);
4547 debug(F110,"cmcvtdate dotime p",p,0);
4549 if (x > 12 || x < 1) {
4550 makestr(&cmdatemsg,"Month out of range");
4551 debug(F111,"cmcvtdate",cmdatemsg,-1);
4556 if (x == 2) if (isleap(atoi(year))) i++;
4557 if (nday > i || nday < 1) {
4558 makestr(&cmdatemsg,"Day out of range");
4559 debug(F111,"cmcvtdate",cmdatemsg,-1);
4562 if (!*p && t == 0) {
4563 sprintf(zbuf,"%04d%02d%02d",atoi(year),atoi(month),nday);
4567 if (*p == '+' || *p == '-') { /* GMT offset without a time */
4568 hh = 0; /* so default time to 00:00:00 */
4571 goto cmtimezone; /* and go do timezone */
4573 if (*p && !isdigit(*p) && *p != ':') {
4574 makestr(&cmdatemsg,"Invalid time");
4575 debug(F111,"cmcvtdate",cmdatemsg,-1);
4578 sprintf(yyyymmdd,"%s%s%02d",year,month,nday); /* for tz calculations... */
4580 state = 1; /* Initialize time-parsing FSA */
4582 mm = 0; /* minutes */
4583 ss = 0; /* seconds */
4584 ff = -1; /* fraction */
4585 d = 0; /* Digit counter */
4586 p2 = p; /* Preliminary digit count... */
4587 while (isdigit(*p2)) {
4592 makestr(&cmdatemsg,"Too many time digits");
4593 debug(F111,"cmcvtdate",cmdatemsg,-1);
4596 d = (d & 1 && *p2 != ':') ? 1 : 0; /* Odd implies leading '0' */
4598 while (*p) { /* Get the time, if any */
4599 if (isdigit(*p)) { /* digit */
4606 hh = hh * 10 + (*p - '0');
4608 case 2: /* Minutes */
4609 mm = mm * 10 + (*p - '0');
4611 case 3: /* Seconds */
4612 ss = ss * 10 + (*p - '0');
4614 case 4: /* Fraction of second */
4616 ff = (*p > '4') ? 1 : 0;
4619 } else if (*p == ':') { /* Colon */
4623 makestr(&cmdatemsg,"Too many time fields");
4624 debug(F111,"cmcvtdate",cmdatemsg,-1);
4627 } else if (*p == '.') {
4632 makestr(&cmdatemsg,"Improper fraction");
4633 debug(F111,"cmcvtdate",cmdatemsg,-1);
4636 } else if (*p == SP) { /* Space */
4637 while (*p && (*p == SP)) /* position to first nonspace */
4640 } else if (isalpha(*p)) { /* AM/PM/Z or timezone */
4642 } else if (*p == '+' || *p == '-') { /* GMT offset */
4645 makestr(&cmdatemsg,"Invalid time characters");
4646 debug(F111,"cmcvtdate",cmdatemsg,-1);
4651 if (!*p) /* If nothing left */
4652 goto xcmdate; /* go finish up */
4654 /* At this point we have HH, MM, SS, and FF */
4655 /* Now handle the rest: AM, PM, and/or timezone info */
4657 if (!ckstrcmp(p,"am",2,0)) { /* AM/PM... */
4660 } else if (!ckstrcmp(p,"a.m.",4,0)) {
4663 } else if (!ckstrcmp(p,"pm",2,0)) {
4666 } else if (!ckstrcmp(p,"p.m.",4,0)) {
4670 if (pmflag && hh < 12) /* If PM was given */
4671 hh += 12; /* add 12 to the hour */
4673 /* Now handle timezone */
4676 debug(F110,"cmcvtdate timezone",p,0);
4678 zhh = 0; /* GMT offset HH */
4679 zmm = 0; /* GMT offset MM */
4680 gmtsign = 0; /* Sign of GMT offset */
4681 isgmt = 0; /* 1 if time is GMT */
4683 while (*p && *p == SP) /* Gobble spaces */
4685 if (!*p) /* If nothing left */
4686 goto xcmdate; /* we're done */
4688 if (isalpha(*p)) { /* Something left */
4689 int zone = 0; /* Alphabetic must be timezone */
4690 p2 = p; /* Isolate timezone */
4697 p = p2; /* Have timezone, look it up */
4698 zone = lookup(usatz,p,nusatz,NULL);
4699 debug(F111,"cmcvtdate timezone alpha",p,zone);
4701 if (zone < 0) { /* Not found */
4702 makestr(&cmdatemsg,"Unknown timezone");
4703 debug(F111,"cmcvtdate",cmdatemsg,-1);
4706 isgmt++; /* All dates are GMT from here down */
4707 if (zone != 0) { /* But not this one so make it GMT */
4708 hh += zone; /* RFC 822 timezone: EST etc */
4709 if (hh > 23) { /* Offset crosses date boundary */
4711 jd = mjd(yyyymmdd); /* Get MJD */
4712 jd += hh / 24; /* Add new day(s) */
4713 hh = hh % 24; /* and convert back to yyyymmdd */
4714 ckstrncpy(yyyymmdd,mjd2date(jd),YYYYMMDD);
4717 p = p3; /* Put back whatever we poked above */
4720 } else if (*p == '+' || *p == '-') { /* GMT/UTC offset */
4722 debug(F110,"cmcvtdate timezone GMT offset",p,0);
4723 gmtsign = (*p == '+') ? -1 : 1;
4726 while (*p == SP) p++;
4729 while (isdigit(*p)) { /* Count digits */
4733 if (d != 4) { /* Strict RFC [2]822 */
4734 isgmt = 0; /* If not exactly 4 digits */
4735 p = p3; /* it's not a GMT offset. */
4736 goto delta; /* So treat it as a delta time. */
4738 d = (d & 1 && *p != ':') ? 1 : 0; /* Odd implies leading '0' */
4740 debug(F111,"cmcvtdate GMT offset sign",p,gmtsign);
4741 debug(F101,"cmcvtdate GMT offset d","",d);
4744 if (isdigit(*p)) { /* digit */
4751 zhh = zhh * 10 + (*p - '0');
4754 zmm = zmm * 10 + (*p - '0');
4756 default: /* Ignore seconds or fractions */
4759 } else if (*p == ':') { /* Colon */
4762 } else if (*p == SP || *p == '(') {
4765 p = p3; /* Maybe it's not a GMT offset. */
4766 goto delta; /* So treat it as a delta time. */
4771 debug(F110,"cmcvtdate after timezone",p,0);
4773 if (*p) { /* Anything left? */
4775 while (*p2 == SP) /* Skip past spaces */
4777 if (*p2 == '(') { /* RFC-822 comment? */
4778 int pc = 1; /* paren counter */
4786 } else if (*p2 == ')') {
4791 while (*p2 == SP) /* Skip past spaces */
4793 if (!*p2) /* Anything left? */
4794 *p = NUL; /* No, erase comment */
4796 if (!*p2) /* Anything left? */
4797 goto xcmdate; /* No, done. */
4801 debug(F110,"cmcvtdate delta yyyymmdd",yyyymmdd,0);
4802 debug(F110,"cmcvtdate delta year",year,0);
4803 debug(F110,"cmcvtdate delta p",p,0);
4805 if (*p == '+' || *p == '-') { /* Delta time */
4806 int state = NEED_DAYS; /* Start off looking for days */
4808 dsign = 1; /* Get sign */
4811 while (*p == SP) /* Skip intervening spaces */
4813 while (state) { /* FSA to parse delta time */
4814 if (state < 0 || !isdigit(*p)) {
4815 makestr(&cmdatemsg,"Invalid delta time");
4816 debug(F111,"cmcvtdate",cmdatemsg,-1);
4819 p2 = p; /* Get next numeric field */
4820 while (isdigit(*p2))
4822 c = *p2; /* And break character */
4823 *p2 = NUL; /* Terminate the number */
4825 switch (state) { /* Interpret according to state */
4826 case NEED_DAYS: /* Initial */
4827 if ((c == '-') || /* VMS format */
4828 ((c == 'd' || c == 'D')
4829 && !isalpha(*(p2+1)))) { /* Days */
4833 else /* if anything is left */
4834 state = NEED_HRS; /* now we want hours. */
4835 } else if ((c == 'W' || c == 'w') && !isalpha(*(p2+1))) {
4836 ddays = atoi(p) * 7; /* weeks... */
4841 } else if ((c == 'M' || c == 'm') && !isalpha(*(p2+1))) {
4842 dmonths = atoi(p); /* months... */
4847 } else if ((c == 'Y' || c == 'y') && !isalpha(*(p2+1))) {
4848 dyears = atoi(p); /* years... */
4853 } else if (c == ':') { /* delimiter is colon */
4854 dhours = atoi(p); /* so it's hours */
4855 state = NEED_MINS; /* now we want minutes */
4856 } else if (!c) { /* end of string */
4857 dhours = atoi(p); /* it's still hours */
4858 state = 0; /* and we're done */
4859 } else if (isalpha(c) || c == SP) {
4860 if (c == SP) { /* It's a keyword? */
4861 p2++; /* Skip spaces */
4864 } else { /* or replace first letter */
4867 p3 = p2; /* p2 points to beginning of keyword */
4868 while (isalpha(*p3)) /* Find end of keyword */
4870 c = *p3; /* NUL it out so we can look it up */
4871 if (*p3) /* p3 points to keyword terminator */
4873 units = lookup(timeunits,p2,nunits,NULL);
4875 makestr(&cmdatemsg,"Invalid units in delta time");
4876 debug(F111,"cmcvtdate",cmdatemsg,-1);
4879 *p2 = NUL; /* Re-terminate the number */
4881 while (*p3 == SP) /* Point at field after units */
4889 ddays = atoi(p) * 7;
4904 } else { /* Anything else */
4905 state = -1; /* is an error */
4908 case NEED_HRS: /* Looking for hours */
4909 debug(F000,"cmcvtdate NEED_HRS",p,c);
4920 case NEED_MINS: /* Looking for minutes */
4931 case NEED_SECS: /* Looking for seconds */
4942 case NEED_FRAC: /* Fraction of second */
4943 if (!c && rdigits(p)) {
4952 if (c) /* next field if any */
4958 makestr(&cmdatemsg,"Extraneous material at end");
4959 debug(F111,"cmcvtdate",cmdatemsg,-1);
4966 if ((t != 2 && hh > 24) || hh < 0) { /* Hour range check */
4967 makestr(&cmdatemsg,"Invalid hours");
4968 debug(F111,"cmcvtdate",cmdatemsg,-1);
4971 if (mm > 59) { /* Minute range check */
4972 makestr(&cmdatemsg,"Invalid minutes");
4973 debug(F111,"cmcvtdate",cmdatemsg,-1);
4976 if (ff > 0) { /* Fraction of second? */
4980 } else if (mm < 59) {
4984 } else if (hh < 24) {
4990 /* Must add a day -- leave ff at 1... */
4991 /* (DO SOMETHING ABOUT THIS LATER) */
4993 if (ss > 60) { /* Seconds range check */
4994 makestr(&cmdatemsg,"Invalid seconds"); /* 60 is ok because of */
4995 debug(F111,"cmcvtdate",cmdatemsg,-1); /* Leap Second. */
4998 if ((mm < 0 || ss < 0) ||
4999 (t != 2 && (ss > 0 || mm > 0) && hh > 23)) {
5000 makestr(&cmdatemsg,"Invalid minutes or seconds");
5001 debug(F111,"cmcvtdate",cmdatemsg,-1);
5004 debug(F110,"cmcvtdate year",year,0);
5005 debug(F110,"cmcvtdate month",month,0);
5006 debug(F101,"cmcvtdate nday","",nday);
5007 debug(F101,"cmcvtdate hh","",hh);
5008 debug(F101,"cmcvtdate mm","",mm);
5009 debug(F101,"cmcvtdate ss","",ss);
5010 debug(F101,"cmcvtdate gmtsign","",gmtsign);
5011 debug(F101,"cmcvtdate zhh","",zhh);
5012 debug(F101,"cmcvtdate zmm","",zmm);
5013 debug(F101,"cmcvtdate isgmt","",isgmt);
5016 /* Handle timezone -- first convert to GMT */
5018 zdd = 0; /* Days changed */
5019 if (isgmt && (zmm || zhh)) { /* If GMT offset given */
5020 long sec1, sec2, zz;
5021 sec1 = ss + 60 * mm + 3600 * hh;
5022 sec2 = gmtsign * (60 * zmm + 3600 * zhh);
5026 zdd = 0L - (sec1 / 86400L);
5027 sec1 = sec1 % 86400L;
5028 } else if (sec1 > 86400L) {
5029 zdd = sec1 / 86400L;
5030 sec1 = sec1 % 86400L;
5036 debug(F101,"cmcvtdate NEW hh","",hh);
5037 debug(F101,"cmcvtdate NEW mm","",mm);
5038 debug(F101,"cmcvtdate NEW dd","",zdd);
5040 /* At this point hh:mm:ss is in GMT and zdd is the calendar adjustment */
5043 #endif /* ZLOCALTIME */
5045 if (yyyymmdd[0] && !year) {
5046 ckstrncpy(yearbuf,yyyymmdd,5);
5047 ckstrncpy(monbuf,&yyyymmdd[4],3);
5048 ckstrncpy(daybuf,&yyyymmdd[6],3);
5052 nday = atoi(daybuf);
5054 sprintf(zbuf,"%04d%02d%02d %02d:%02d:%02d", /* SAFE */
5055 atoi(year),atoi(month),nday,hh,mm,ss
5060 /* Now convert from GMT to local time */
5062 if (isgmt) { /* If GMT convert to local time */
5063 debug(F110,"cmcvtdate GMT 1",dp,0);
5064 if (zdd) { /* Apply any calendar adjustment */
5067 sprintf(zbuf,"%s %02d:%02d:%02d",mjd2date(zz),hh,mm,ss);
5069 debug(F110,"cmcvtdate GMT 2",dp,0);
5070 if ((p = zlocaltime(dp))) {
5071 debug(F110,"cmcvtdate asctime zlocaltime",p,0);
5072 if (p) ckstrncpy(zbuf,p,18);
5074 debug(F110,"cmcvtdate GMT 3",dp,0);
5075 for (i = 0; i < 4; i++)
5085 nday = atoi(daybuf);
5092 #endif /* ZLOCALTIME */
5096 debug(F101,"cmcvtdate hour","",hh);
5097 debug(F101,"cmcvtdate minute","",mm);
5098 debug(F101,"cmcvtdate second","",ss);
5102 makestr(&cmdatemsg,NULL);
5106 debug(F110,"cmcvtdate base ",dp,0);
5107 debug(F101,"cmcvtdate delta sign","",dsign);
5108 debug(F101,"cmcvtdate delta yrs ","",dyears);
5109 debug(F101,"cmcvtdate delta mos ","",dmonths);
5110 debug(F101,"cmcvtdate delta days","",ddays);
5111 debug(F101,"cmcvtdate delta hrs ","",dhours);
5112 debug(F101,"cmcvtdate delta mins","",dmins);
5113 debug(F101,"cmcvtdate delta secs","",dsecs);
5116 if (!(dp = cmdelta(atoi(year),
5119 dsign, dyears, dmonths, ddays, dhours, dmins, dsecs))) {
5120 debug(F111,"cmcvtdate",cmdatemsg,-1);
5125 xcvtdate: /* Exit point for success */
5129 debug(F110,"cmcvtdate xcvtdate dp",dp,0);
5130 if (!dp) dp = ""; /* Shouldn't happen */
5131 if (!*dp) return(NULL); /* ... */
5133 debug(F111,"cmcvtdate result",dp,len);
5134 k = cmdatebp - (char *)cmdatebuf; /* Space used */
5135 n = CMDATEBUF - k - 1; /* Space left */
5136 if (n < len) { /* Not enough? */
5137 cmdatebp = cmdatebuf; /* Wrap around */
5140 ckstrncpy(cmdatebp,dp,n);
5142 cmdatebp += len + 1;
5148 cmvdate(d) char * d; { /* Verify date-time */
5151 if ((int)strlen(d) != 17) return(0);
5152 for (i = 0; i < 8; i++) { if (!isdigit(d[i])) return(0); }
5153 if (!isdigit(d[9]) || !isdigit(d[10]) ||
5154 !isdigit(d[12]) || !isdigit(d[13]) ||
5155 !isdigit(d[15]) || !isdigit(d[16]))
5157 if (!ckstrchr(" Tt_-:",d[8])) return(0);
5158 if (d[11] != ':' && d[14] != ':') return(0);
5162 /* c m d i f f d a t e -- Get difference between two date-times */
5165 cmdiffdate(d1,d2) char * d1, * d2; {
5166 char d1buf[9], d2buf[9];
5167 char x1buf[18], x2buf[18];
5170 int hh1 = 0, mm1 = 0, ss1 = 0;
5171 int hh2 = 0, mm2 = 0, ss2 = 0;
5174 long jd1, jd2, jd, f1, f2, fx;
5175 static char result[24], *rp;
5177 debug(F110,"cmdiffdate d1 A",d1,0);
5178 debug(F110,"cmdiffdate d2 A",d2,0);
5180 if (!(p = cmcvtdate(d1,1))) /* Convert dates to standard format */
5182 ckstrncpy(x1buf,p,18);
5185 if (!(p = cmcvtdate(d2,1)))
5187 ckstrncpy(x2buf,p,18);
5190 debug(F110,"cmdiffdate d1 B",d1,0);
5191 debug(F110,"cmdiffdate d2 B",d2,0);
5192 if (!cmvdate(d1) || !cmvdate(d2))
5195 hh1 = atoi(&d1[9]); /* Get hours, minutes, and seconds */
5196 mm1 = atoi(&d1[12]); /* for first date */
5197 ss1 = atoi(&d1[15]);
5198 ckstrncpy(d1buf,d1,9);
5200 hh2 = atoi(&d2[9]); /* ditto for second date */
5201 mm2 = atoi(&d2[12]);
5202 ss2 = atoi(&d2[15]);
5203 ckstrncpy(d2buf,d2,9);
5205 jd1 = mjd(d1buf); /* Get the two Julian dates */
5207 f1 = ss1 + 60 * mm1 + 3600 * hh1; /* Convert first time to seconds */
5209 f2 = ss2 + 60 * mm2 + 3600 * hh2; /* Ditto for second time */
5210 debug(F101,"cmdiffdate jd1","",jd1);
5211 debug(F101,"cmdiffdate f1","",f1);
5212 debug(F101,"cmdiffdate jd2","",jd2);
5213 debug(F101,"cmdiffdate f2","",f2);
5215 if (jd2 > jd1 || (jd1 == jd2 && f2 > f1)) {
5217 if (f1 > f2) {jd2--; f2 += 86400L;}
5222 if (f2 > f1) {jd1--; f1 += 86400L;}
5226 debug(F111,"cmdiffdate sign jd",sign<0?"-":"+",jd);
5227 debug(F101,"cmdiffdate fx","",fx);
5229 hh = (int) (fx / 3600L); /* Convert seconds to hh:mm:ss */
5231 mm = (int) (fx % 3600L) / 60L;
5232 ss = (int) (fx % 3600L) % 60L;
5234 rp = result; /* Format the result */
5235 *rp++ = (sign < 0) ? '-' : '+';
5236 if (jd != 0 && hh+mm+ss == 0) {
5237 sprintf(rp,"%ldd",jd);
5238 } else if (jd == 0) {
5240 sprintf(rp,"%d:%02d",hh,mm);
5242 sprintf(rp,"%d:%02d:%02d",hh,mm,ss);
5245 sprintf(rp,"%ldd%d:%02d",jd,hh,mm);
5247 sprintf(rp,"%ldd%d:%02d:%02d",jd,hh,mm,ss);
5249 debug(F110,"cmdiffdate result",result,0);
5250 return((char *)result);
5253 /* s h u f f l e d a t e -- Rearrange date string */
5257 A date string in standard format: yyyymmdd hh:mm:ss (time optional).
5259 1: Reformat date to yyyy-mmm-dd (mmm = English month abbreviation).
5260 2: Reformat date to dd-mmm-yyyy (mmm = English month abbreviation).
5261 3: Reformat as numeric yyyymmddhhmmss.
5263 Pointer to result if args valid, otherwise original arg pointer.
5266 shuffledate(p,opt) char * p; int opt; {
5269 static char obuf[48];
5274 if (!*p) p = ckdate();
5275 if (opt < 1 || opt > 3)
5278 if (len < 8 || len > 31) return(p);
5280 ckstrncpy(obuf,p,48);
5281 /* yyyymmdd hh:mm:ss */
5282 /* 01234567890123456 */
5283 /* yyyymmddhhmmss */
5286 obuf[10] = obuf[12];
5287 obuf[11] = obuf[13];
5288 obuf[12] = obuf[15];
5289 obuf[13] = obuf[16];
5291 return((char *)obuf);
5293 ckstrncpy(ibuf,p,32);
5294 c = ibuf[4]; /* Warning: not Y10K compliant */
5299 if (yy < 1 || yy > 9999)
5304 if (!rdigits(&ibuf[4]))
5306 mm = atoi(&ibuf[4]);
5307 if (mm < 1 || mm > 12)
5312 if (!rdigits(&ibuf[6]))
5314 dd = atoi(&ibuf[6]);
5316 if (dd < 1 || mm > 31)
5318 /* IGNORE WARNINGS ABOUT moname[] REFS OUT OF RANGE - it's prechecked. */
5321 sprintf(obuf,"%04d-%s-%02d%s",yy,moname[mm-1],dd,&ibuf[8]);
5324 sprintf(obuf,"%02d-%s-%04d%s",dd,moname[mm-1],yy,&ibuf[8]);
5326 return((char *)obuf);
5329 /* C K C V T D A T E -- Like cmcvtdate(), but returns string. */
5330 /* For use by date-related functions */
5331 /* See calling conventions for cmcvtdate() above. */
5334 ckcvtdate(p,t) char * p; int t; {
5336 if (!(s = cmcvtdate(p,t)))
5337 return("<BAD_DATE_OR_TIME>"); /* \fblah() error message */
5343 /* C M D A T E -- Parse a date and/or time */
5346 Accepts date in various formats. If the date is recognized,
5347 this routine returns 0 or greater with the result string pointer
5348 pointing to a buffer containing the date as "yyyymmdd hh:mm:ss".
5351 cmdate(xhlp,xdef,xp,quiet,f) char *xhlp, *xdef, **xp; int quiet; xx_strp f; {
5353 char *o, *s, *zq, *dp;
5356 if (!xhlp) xhlp = "";
5357 if (!xdef) xdef = "";
5358 if (!*xhlp) xhlp = "Date and/or time";
5361 rc = cmfld(xhlp,xdef,&s,(xx_strp)0);
5362 debug(F101,"cmdate cmfld rc","",rc);
5365 debug(F110,"cmdate 1",s,0);
5366 o = s; /* Remember what they typed. */
5368 debug(F110,"cmdate 2",s,0);
5371 if (f) { /* If a conversion function is given */
5373 zq = atxbuf; /* do the conversion. */
5376 if ((x = (*f)(s,&zq,&atxn)) < 0) return(-2);
5379 if (setatm(pp,0) < 0) {
5380 if (!quiet) printf("?Evaluated date too long\n");
5385 dp = cmcvtdate(s,1);
5387 if (!quiet) printf("?%s\n",cmdatemsg);
5394 #ifdef CK_RECALL /* Command-recall functions */
5396 /* C M R I N I -- Initialize or change size of command recall buffer */
5401 if (recall && in_recall) { /* Free old storage, if any */
5402 for (i = 0; i < cm_recall; i++) {
5411 cm_recall = n; /* Set new size */
5412 rlast = current = -1; /* Initialize pointers */
5414 recall = (char **)malloc((cm_recall + 1) * sizeof(char *));
5417 for (i = 0; i < cm_recall; i++) {
5420 in_recall = 1; /* Recall buffers init'd */
5425 /* C M A D D N E X T -- Force addition of next command */
5429 if (on_recall && in_recall) { /* Even if it doesn't come */
5430 force_add = 1; /* from the keyboard */
5436 /* C M G E T C M D -- Find most recent matching command */
5439 cmgetcmd(s) char * s; {
5441 for (i = current; i >= 0; i--) { /* Search backward thru history list */
5442 if (!recall[i]) continue; /* This one's null, skip it */
5443 if (ckmatch(s,recall[i],0,1)) /* Match? */
5444 return(recall[i]); /* Yes, return pointer */
5446 return(NULL); /* No match, return NULL pointer */
5448 #endif /* CK_RECALL */
5450 /* A D D C M D -- Add a command to the recall buffer */
5453 addcmd(s) char * s; {
5454 int len = 0, nq = 0;
5457 extern int learning;
5458 #endif /* CKLEARN */
5460 if (xcmdsrc) /* Only for interactive commands */
5463 if (!newcmd) /* The command has been here already */
5464 return; /* so ignore it. */
5465 newcmd = 0; /* It's new but do this only once. */
5471 if (len < 1) /* Don't save empty commands */
5475 while (*p) { if (*p++ == '?') nq++; } /* Count question marks */
5478 if (learning) /* If a learned script is active */
5479 learncmd(s); /* record this command. */
5480 #endif /* CKLEARN */
5482 debug(F010,"CMD(P)",s,0); /* Maybe record it in the debug log */
5485 if (ckxlogging) { /* Maybe record it in syslog */
5486 if (ckxsyslog >= SYSLG_CX || ckxsyslog >= SYSLG_CM)
5487 cksyslog(SYSLG_CX, 1, "command", s, NULL);
5489 #endif /* CKSYSLOG */
5494 if (on_recall && /* Command recall is on? */
5495 cm_recall > 0 && /* Recall buffer size is > 0? */
5496 !no_recall) { /* Not not saving this command? */
5498 if (!force_add && rlast > -1) /* If previous command was identical */
5499 if (!strcmp(s,recall[rlast])) /* don't add another copy */
5502 force_add = 0; /* Reset now in case it was set */
5504 if (rlast >= cm_recall - 1) { /* Recall buffer full? */
5506 if (recall[0]) { /* Discard oldest command */
5510 for (i = 0; i < rlast; i++) { /* The rest */
5511 recall[i] = recall[i+1]; /* move back */
5513 rlast--; /* Now we have one less */
5515 rlast++; /* Index of last command in buffer */
5516 current = rlast; /* Also now the current command */
5517 if (current >= cm_recall) { /* Shouldn't happen */
5518 printf("?Command history error\n"); /* but if it does */
5519 on_recall = 0; /* turn off command saving */
5521 } else if (nq > 0) { /* Have at least one question mark */
5522 recall[current] = malloc(len+nq+1);
5523 if (recall[current]) {
5524 p = recall[current];
5532 #endif /* COMMENT */
5533 } else { /* Normal case, just copy */
5534 recall[current] = malloc(len+1);
5535 if (recall[current])
5536 ckstrncpy(recall[current],s,len+1);
5539 #endif /* CK_RECALL */
5545 /* C M H I S T O R Y */
5550 for (i = 0; i <= current; i++) {
5551 printf(" %s\n", recall[i]);
5552 if (++lc > (cmd_rows - 2)) { /* Screen full? */
5553 if (!askmore()) /* Do more-prompting... */
5562 savhistory(s,disp) char *s; int disp; {
5566 fp = fopen(s, disp ? "a" : "w");
5571 for (i = 0; i <= current; i++)
5572 fprintf(fp,"%s\n", recall[i]);
5576 #endif /* CK_RECALL */
5579 /* apparently not used */
5581 cmgetlc(s) char * s; { /* Get leading char */
5583 while ((c = *s++) <= SP) {
5589 #endif /* COMMENT */
5592 /* C M C F M -- Parse command confirmation (end of line) */
5596 -2: User typed anything but whitespace or newline
5598 0: Confirmation was received
5603 debug(F101,"cmcfm: cmflgs","",cmflgs);
5604 debug(F110,"cmcfm: atmbuf",atmbuf,0);
5605 inword = xc = cc = 0;
5607 setatm("",0); /* (Probably unnecessary) */
5609 while (cmflgs != 1) {
5615 printf("Command or field too long\n");
5620 case 1: /* End of line */
5625 printf("?Not confirmed - %s\n",atmbuf);
5629 break; /* Finish up below */
5633 continue; /* or fall thru. */
5636 if (xc == 0) /* If no chars typed, continue, */
5637 continue; /* else fall thru. */
5638 /* else fall thru... */
5640 case 3: /* Question mark */
5645 printf("?Not confirmed - %s\n",atmbuf);
5650 "\n Press the Return or Enter key to confirm the command\n");
5651 printf("%s%s",cmprom,cmdbuf);
5661 /* The following material supports chained parsing functions. */
5662 /* See ckucmd.h for FDB and OFDB definitions. */
5664 struct OFDB cmresult = { /* Universal cmfdb result holder */
5672 cmfdbi(p,fc,s1,s2,s3,n1,n2,f,k,nxt) /* Initialize an FDB */
5675 char * s1, * s2, * s3;
5692 /* C M F D B -- Parse a field with several possible functions */
5695 cmfdb(fdbin) struct FDB * fdbin; {
5697 extern int x_ifnum; /* IF NUMERIC - disables warnings */
5699 struct FDB * in = fdbin;
5700 struct OFDB * out = &cmresult;
5702 char *s, *xp, *m = NULL;
5707 out->fcode = -1; /* Initialize output struct */
5708 out->fdbaddr = NULL;
5709 out->sresult = NULL;
5712 Currently we make one trip through the FDBs. So if the user types Esc or
5713 Tab at the beginning of a field, only the first FDB is examined for a
5714 default. If the user types ?, help is given only for one FDB. We should
5715 search through the FDBs for all matching possibilities -- and in particular
5716 display the pertinent context-sensitive help for each function, rather than
5717 the only the first one that works, and then rewind the FDB pointer so we
5718 are not locked out of the earlier ones.
5721 while (1) { /* Loop through the chain of FDBs */
5726 debug(F101,"cmfdb in->fcode","",in->fcode);
5727 switch (in->fcode) { /* Current parsing function code */
5730 if (r != 10 && r != 8) r = 10;
5732 x_ifnum = 1; /* Disables warning messages */
5734 x = cmnum(in->hlpmsg,in->dflt,r,&n,in->spf);
5738 debug(F101,"cmfdb cmnum","",x);
5739 if (x < 0) errbits |= 1;
5742 x = cmofi(in->hlpmsg,in->dflt,&s,in->spf);
5743 debug(F101,"cmfdb cmofi","",x);
5744 if (x < 0) errbits |= 2;
5747 x = cmifi2(in->hlpmsg,
5756 debug(F101,"cmfdb cmifi2 x","",x);
5757 debug(F101,"cmfdb cmifi2 n","",n);
5758 if (x < 0) errbits |= 4;
5761 cmfldflgs = in->ndata1;
5762 x = cmfld(in->hlpmsg,in->dflt,&s,in->spf);
5763 debug(F101,"cmfdb cmfld","",x);
5764 if (x < 0) errbits |= 8;
5767 x = cmtxt(in->hlpmsg,in->dflt,&s,in->spf);
5768 debug(F101,"cmfdb cmtxt","",x);
5769 if (x < 0) errbits |= 16;
5772 x = cmkey2(in->kwdtbl,
5774 in->hlpmsg,in->dflt,in->sdata,in->spf,in->ndata2);
5775 debug(F101,"cmfdb cmkey","",x);
5776 if (x < 0) errbits |= ((in->ndata2 & 4) ? 32 : 64);
5780 debug(F101,"cmfdb cmcfm","",x);
5781 if (x < 0) errbits |= 128;
5784 debug(F101,"cmfdb - unexpected function code","",in->fcode);
5785 printf("?cmfdb - unexpected function code: %d\n",in->fcode);
5787 debug(F101,"cmfdb x","",x);
5788 debug(F101,"cmfdb cmflgs","",cmflgs);
5789 debug(F101,"cmfdb crflag","",crflag);
5790 debug(F101,"cmfdb qmflag","",qmflag);
5791 debug(F101,"cmfdb esflag","",esflag);
5793 if (x > -1) { /* Success */
5794 out->fcode = in->fcode; /* Fill in output struct */
5797 out->nresult = (in->fcode == _CMKEY) ? x : n;
5798 out->kflags = (in->fcode == _CMKEY) ? cmkwflgs : 0;
5799 debug(F111,"cmfdb out->nresult",out->sresult,out->nresult);
5802 /* debug(F111,"cmfdb cmdbuf & crflag",cmdbuf,crflag); */
5806 return(x); /* and return */
5808 in = in->nxtfdb; /* Failed, get next parsing function */
5811 if (!in) { /* No more */
5812 debug(F101,"cmfdb failure x","",x);
5813 debug(F101,"cmfdb failure errbits","",errbits);
5822 /* Make informative messages for a few common cases */
5824 case 4+32: m = "Does not match filename or switch"; break;
5825 case 4+64: m = "Does not match filename or keyword"; break;
5826 case 1+32: m = "Not a number or valid keyword"; break;
5827 case 1+64: m = "Not a number or valid switch"; break;
5828 default: m = "Not valid in this position";
5830 printf("?%s: \"%s\"\n",m, atmbuf);
5834 if (x != -2 && x != -6 && x != -9 && x != -3) /* Editing or somesuch */
5835 return(x); /* Go back and reparse */
5836 pp = np = bp = xp; /* Back up pointers */
5837 cmflgs = -1; /* Force a reparse */
5841 if (!askflag) { /* If not executing ASK-class cmd... */
5843 if (crflag) { /* If CR was typed, put it back */
5844 pushc = LF; /* But as a linefeed */
5845 } else if (qmflag) { /* Ditto for Question mark */
5847 } else if (esflag) { /* and Escape or Tab */
5857 /* G T W O R D -- Gets a "word" from the command input stream */
5860 Usage: retcode = gtword(brk);
5861 brk = 0 for normal word breaks (space, CR, Esc, ?)
5862 brk = 1 to add ':' and '=' (for parsing switches). These characters
5863 act as break characters only if the first character of the field
5864 is slash ('/'), i.e. switch introducer.
5867 -10 Timelimit set and timed out
5868 -9 if input was too long
5869 -4 if end of file (e.g. pipe broken)
5871 -2 if command buffer overflows
5872 -1 if user did some deleting
5873 0 if word terminates with SP or tab
5876 3 if ... ? (question mark)
5877 4 if ... : or = and called with brk != 0
5880 pp pointing to beginning of word in buffer
5881 bp pointing to after current position
5882 atmbuf containing a copy of the word
5883 cc containing the number of characters in the word copied to atmbuf
5887 ungword() { /* Unget a word */
5888 debug(F101,"ungword cmflgs","",cmflgs);
5889 if (ungw) return(0);
5896 /* Un-un-get word. Undo ungword() if it has been done. */
5900 debug(F010,"unungw atmbuf",atmbuf,0);
5909 gtword(brk) int brk; {
5910 int c; /* Current char */
5911 int quote = 0; /* Flag for quote character */
5912 int echof = 0; /* Flag for whether to echo */
5913 int comment = 0; /* Flag for in comment */
5914 char *cp = NULL; /* Comment pointer */
5915 int eintr = 0; /* Flag for syscall interrupted */
5916 int bracelvl = 0; /* nested brace counter [jrs] */
5917 int iscontd = 0; /* Flag for continuation */
5918 int realtty = 0; /* Stdin is really a tty */
5920 char lastchar = NUL;
5921 char prevchar = NUL;
5922 char lbrace, rbrace;
5923 int dq = 0; /* Doublequote flag */
5924 int dqn = 0; /* and count */
5932 extern int inserver;
5934 extern int kstartactive;
5937 extern int termtype; /* DG terminal type flag */
5938 extern int con_reads_mt; /* Console read asynch is active */
5939 if (con_reads_mt) connoi_mt(); /* Task would interfere w/cons read */
5940 #endif /* datageneral */
5946 debug(F101,"gtword brk","",brk);
5947 debug(F101,"gtword cmfldflgs","",cmfldflgs);
5948 debug(F101,"gtword swarg","",swarg);
5949 debug(F101,"gtword dpx","",dpx);
5950 debug(F101,"gtword echof","",echof);
5952 debug(F101,"gtword askflag","",askflag);
5953 debug(F101,"gtword timelimit","",timelimit);
5957 debug(F101,"gtword cmdadl","",cmdadl);
5958 #endif /* CK_AUTODL */
5960 #endif /* NOLOCAL */
5964 #endif /* COMMENT */
5966 realtty = is_a_tty(0); /* Stdin is really a tty? */
5968 if (cmfldflgs & 1) {
5979 if (swarg) { /* No leading space for switch args */
5983 if (ungw) { /* Have a word saved? */
5985 /* Experimental code to allow ungetting multiple words. */
5986 /* See comments in ckmkey2() above. */
5988 if (np > pp) pp = np;
5989 while (*pp == SP) pp++;
5994 if ((x = setatm(pp,2)) < 0) {
5995 printf("?Saved word too long\n");
6002 while (*p2 == SP) p2++;
6010 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6016 You would think the following should be:
6017 while (*pp == SP) pp++;
6018 but you would be wrong -- making this change breaks GOTO.
6020 while (*pp++ == SP) ;
6021 if (setatm(pp,2) < 0) {
6022 printf("?Saved word too long\n");
6027 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6031 pp = np; /* Start of current field */
6036 debug(F110,"gtword cmdbuf",cmdbuf,0);
6037 debug(F110,"gtword bp",bp,0);
6038 debug(F110,"gtword pp",pp,0);
6041 #endif /* COMMENT */
6043 /* If we are reparsing we have to recount any braces or doublequotes */
6051 else if (c == rbrace)
6053 else if (dq && c == '"')
6056 while (bp < cmdbuf+CMDBL) { /* Big get-a-character loop */
6057 echof = 0; /* Assume we don't echo because */
6058 chsrc = 0; /* character came from reparse buf. */
6061 #endif /* BS_DIRSEP */
6064 if (!c) { /* If no char waiting in reparse buf */
6069 )) /* Get from tty, set echo flag */
6071 c = cmdgetc(timelimit); /* Read a command character. */
6073 debug(F101,"gtword c","",c);
6076 if (timelimit && c < -1) { /* Timed out */
6082 The following allows packet recognition in the command parser.
6083 Presently it works only for Kermit packets, and if our current protocol
6084 happens to be anything besides Kermit, we simply force it to Kermit.
6085 We don't use the APC mechanism here for mechanical reasons, and also
6086 because this way, it works even with minimally configured interactive
6087 versions. Add Zmodem later...
6090 if ((!local && cmdadl) /* Autodownload enabled? */
6092 || TELOPT_SB(TELOPT_KERMIT).kermit.me_start
6093 #endif /* IKS_OPTION */
6096 k = kstart((CHAR)c); /* Kermit S or I packet? */
6099 if (k < 0) { /* Minus-Protocol? */
6101 goto noserver; /* Need server mode for this */
6103 ksign = 1; /* Remember */
6104 k = 0 - k; /* Convert to actual protocol */
6105 justone = 1; /* Flag for protocol module */
6106 #endif /* NOSERVER */
6109 k--; /* Adjust kstart's return value */
6111 extern int protocol, g_proto;
6114 protocol = PROTO_K; /* Crude... */
6115 sstate = ksign ? 'x' : 'v';
6123 #endif /* NOSERVER */
6124 #endif /* CK_AUTODL */
6127 chsrc = 1; /* Remember character source is tty. */
6131 if (inserver && c < 0) { /* End of session? */
6132 debug(F111,"gtword c < 0","exiting",c);
6133 return(-4); /* Cleanup and terminate */
6138 if (c < 0) { /* Error */
6139 if (c == -3) { /* Empty word? */
6140 if (blocklvl > 0) /* In a block */
6141 continue; /* so keep looking for block end */
6143 return(-3); /* Otherwise say we got nothing */
6144 } else { /* Not empty word */
6145 return(-4); /* So some kind of i/o error */
6150 if (c == -3) /* Empty word... */
6157 if (c == EOF) { /* This can happen if stdin not tty. */
6160 Some operating and/or C runtime systems return EINTR for no good reason,
6161 when the end of the standard input "file" is encountered. In cases like
6162 this, we get into an infinite loop; hence the eintr counter, which is reset
6163 to 0 upon each call to this routine.
6165 debug(F101,"gtword EOF","",errno);
6166 if (errno == EINTR && ++eintr < 4) /* When bg'd process is */
6167 continue; /* fg'd again. */
6171 c &= cmdmsk; /* Strip any parity bit */
6174 /* Now we have the next character */
6176 isesc = (c == ESC); /* A real ESC? */
6178 if (!firstnb && c > SP) { /* First nonblank */
6180 if (c == '"') /* Starts with doublequote */
6183 if (c == '"') /* Count doublequotes */
6186 if (quote && (c == CR || c == LF)) { /* Enter key following quote */
6187 *bp++ = CMDQ; /* Double it */
6191 if (quote == 0) { /* If this is not a quoted character */
6193 case CMDQ: /* Got the quote character itself */
6194 if (!comment && quoting)
6195 quote = 1; /* Flag it if not in a comment */
6197 case FF: /* Formfeed. */
6198 c = NL; /* Replace with newline */
6199 cmdclrscn(); /* Clear the screen */
6201 case HT: /* Horizontal Tab */
6202 if (comment) /* If in comment, */
6203 c = SP; /* substitute space */
6204 else /* otherwise */
6205 c = ESC; /* substitute ESC (for completion) */
6207 case ';': /* Trailing comment */
6209 if (inword == 0 && quoting) { /* If not in a word */
6210 comment = 1; /* start a comment. */
6211 cp = bp; /* remember where it starts. */
6215 if (!kstartactive && /* Not in possible Kermit packet */
6216 !comment && c == SP) { /* Space not in comment */
6217 *bp++ = (char) c; /* deposit in buffer if not already */
6218 /* debug(F101,"gtword echof 2","",echof); */
6221 putchar(c); /* echo it. */
6226 if (echof) { /* echo it. */
6232 if (inword == 0) { /* If leading, gobble it. */
6235 } else { /* If terminating, return. */
6236 if ((!dq && ((*pp != lbrace) || (bracelvl == 0))) ||
6237 (dq && dqn > 1 && *(bp-2) == '"')) {
6240 if (setatm(pp,0) < 0) {
6241 printf("?Field too long error 1\n");
6242 debug(F111,"gtword too long #1",pp,strlen(pp));
6246 inword = cmflgs = 0;
6254 /* debug(F101,"gtword bracelvl++","",bracelvl); */
6256 if (c == rbrace && bracelvl > 0) {
6258 /* debug(F101,"gtword bracelvl--","",bracelvl); */
6262 if ((c == '=' || c == ':') &&
6263 !kstartactive && !comment && brk && (firstnb == '/')
6265 *bp++ = (char) c; /* Switch argument separator */
6266 /* debug(F111,"gtword switch argsep",cmdbuf,brk); */
6269 putchar(c); /* Echo it. */
6280 if ((*pp != lbrace) || (bracelvl == 0)) {
6283 if (setatm(pp,0) < 0) {
6284 printf("?Field too long error 1\n");
6285 debug(F111,"gtword too long #1",pp,strlen(pp));
6288 inword = cmflgs = 0;
6293 if (c == LF || c == CR) { /* CR or LF. */
6295 cmdnewl((char)c); /* echo it. */
6302 /* Trim trailing comment and whitespace */
6304 if (comment) { /* Erase comment */
6305 while (bp >= cp) /* Back to comment pointer */
6308 pp = bp; /* Adjust other pointers */
6309 inword = 0; /* and flags */
6313 qq = inword ? pp : (char *)cmdbuf;
6314 /* Erase trailing whitespace */
6315 while (bp > qq && (*(bp-1) == SP || *(bp-1) == HT)) {
6317 /* debug(F000,"erasing","",*bp); */
6320 lastchar = (bp > qq) ? *(bp-1) : NUL;
6321 prevchar = (bp > qq+1) ? *(bp-2) : NUL;
6323 if (linebegin && blocklvl > 0) /* Blank line in {...} block */
6326 linebegin = 1; /* At beginning of next line */
6327 iscontd = prevchar != CMDQ &&
6328 (lastchar == '-' || lastchar == lbrace);
6329 debug(F101,"gtword iscontd","",iscontd);
6331 if (iscontd) { /* If line is continued... */
6332 if (chsrc) { /* If reading from tty, */
6333 if (*(bp-1) == lbrace) { /* Check for "begin block" */
6334 *bp++ = SP; /* Insert a space for neatness */
6335 blocklvl++; /* Count block nesting level */
6336 } else { /* Or hyphen */
6337 bp--; /* Overwrite the hyphen */
6339 *bp = NUL; /* erase the dash, */
6340 continue; /* and go back for next char now. */
6342 } else if (blocklvl > 0) { /* No continuation character */
6343 if (chsrc) { /* But we're in a "block" */
6344 *bp++ = ','; /* Add comma */
6348 } else { /* No continuation, end of command. */
6349 *bp = NUL; /* Terminate the command string. */
6350 if (comment) { /* If we're in a comment, */
6351 comment = 0; /* Say we're not any more, */
6352 *cp = NUL; /* cut it off. */
6354 np = bp; /* Where to start next field. */
6356 if (setatm(pp,0) < 0) { /* Copy field to atom buffer */
6357 debug(F111,"gtword too long #2",pp,strlen(pp));
6358 printf("?Field too long error 2\n");
6361 inword = 0; /* Not in a word any more. */
6363 /* debug(F110,"gtword","crflag is set",0); */
6366 #endif /* CK_RECALL */
6371 #endif /* CK_RECALL */
6378 This section handles interactive help, completion, editing, and history.
6379 Rearranged as a switch statement executed only if we're at top level since
6380 there is no need for any of this within command files and macros: Aug 2000.
6381 Jun 2001: Even if at top level, skip this if the character was fetched from
6382 the reparse or recall buffer, or if stdin is redirected.
6384 if ((xcmdsrc == 0 /* Only at top level */
6386 || askflag /* or user is typing ASK response */
6388 ) && chsrc != 0 && realtty) { /* from the real keyboard */
6390 /* Use ANSI / VT100 up and down arrow keys for command recall. */
6398 #ifdef USE_ARROWKEYS
6400 #endif /* USE_ARROWKEYS */
6402 ) { /* A real ESC was typed */
6404 msleep(200); /* Wait 1/5 sec */
6405 x = cmdconchk(); /* Was it followed by anything? */
6406 debug(F101,"Arrowkey ESC cmdconchk","",x);
6408 if (x > 1) { /* If followed by at least 2 chars */
6410 c2 = cmdgetc(0); /* Get the first one */
6411 debug(F101,"Arrowkey ESC c2","",c2);
6413 if (c2 != '[' && c2 != 'O') { /* If not [ or O */
6414 pushc = c2; /* Push it and take the ESC solo */
6416 c2 = cmdgetc(0); /* Get the second one */
6417 debug(F101,"Arrowkey ESC c3","",c2);
6424 case 'B': /* Down */
6428 case 'C': /* Right */
6429 case 'D': /* Left */
6432 #endif /* NORECALL */
6433 c = BEL; /* We don't use these yet */
6441 case '?': /* ?-Help */
6443 if (askflag) /* No help in ASK response */
6452 if (setatm(pp,0) < 0) {
6453 debug(F111,"gtword too long ?",pp,strlen(pp));
6454 printf("?Too long\n");
6461 case ESC: /* Esc or Tab completion */
6464 if (setatm(pp,0) < 0) {
6465 debug(F111,"gtword too long Esc",pp,strlen(pp));
6466 printf("?Too long\n");
6476 case BS: /* Character deletion */
6478 if (bp > cmdbuf) { /* If still in buffer... */
6479 cmdchardel(); /* erase it. */
6480 bp--; /* point behind it, */
6481 if (*bp == lbrace) bracelvl--; /* Adjust brace count */
6482 if (*bp == rbrace) bracelvl++;
6483 if ((*bp == SP) && /* Flag if current field gone */
6484 (*pp != lbrace || bracelvl == 0))
6486 *bp = NUL; /* Erase character from buffer. */
6487 } else { /* Otherwise, */
6489 cmres(); /* and start parsing a new command. */
6490 *bp = *atmbuf = NUL;
6495 return(cmflgs = -1);
6497 case LDEL: /* ^U, line deletion */
6498 while ((bp--) > cmdbuf) {
6502 cmres(); /* Restart the command. */
6503 *bp = *atmbuf = NUL;
6505 return(cmflgs = -1);
6507 case WDEL: /* ^W, word deletion */
6508 if (bp <= cmdbuf) { /* Beep if nothing to delete */
6511 *bp = *atmbuf = NUL;
6512 return(cmflgs = -1);
6515 /* Back up over any trailing nonalphanums */
6516 /* This is dependent on ASCII collating sequence */
6517 /* but isalphanum() is not available everywhere. */
6521 ((*bp > '9') && (*bp < '@')) ||
6522 ((*bp > 'Z') && (*bp < 'a')) ||
6529 /* Now delete back to rightmost remaining nonalphanum */
6530 for ( ; (bp >= cmdbuf) && (*bp) ; bp--) {
6532 (*bp > '9' && *bp < '@') ||
6533 (*bp > 'Z' && *bp < 'a') ||
6541 return(cmflgs = -1);
6543 case RDIS: { /* ^R, redisplay */
6546 printf("\n%s",cmprom);
6548 while ((cx = *cpx++)) {
6550 putchar((CHAR) (isprint(cx) ? cx : '^'));
6552 putchar((CHAR) ((cx >= SP && cx < DEL) ? cx : '^'));
6553 #endif /* isprint */
6562 if (on_recall && /* Reading commands from keyboard? */
6563 (cm_recall > 0) && /* Saving commands? */
6564 (c == C_UP || c == C_UP2)) { /* Go up one */
6565 if (last_recall == 2 && current > 0)
6567 if (current < 0) { /* Nowhere to go, */
6571 if (recall[current]) { /* We have a previous command */
6572 while ((bp--) > cmdbuf) { /* Erase current line */
6576 ckstrncpy(cmdbuf,recall[current],CMDBL);
6579 write(fileno(stdout), "\r", 1);
6580 printf("%s%s",cmprom,cmdbuf);
6582 printf("\r%s%s",cmprom,cmdbuf);
6587 return(cmflgs = -1); /* Force a reparse */
6589 if (on_recall && /* Reading commands from keyboard? */
6590 (cm_recall > 0) && /* Saving commands? */
6591 (c == C_DN)) { /* Down one */
6593 if (last_recall == 1)
6595 if (current + x > rlast) { /* Already at bottom, beep */
6599 current += x; /* OK to go down */
6600 if (recall[current]) {
6601 while ((bp--) > cmdbuf) { /* Erase current line */
6605 ckstrncpy(cmdbuf,recall[current],CMDBL);
6608 write(fileno(stdout), "\r", 1);
6609 printf("%s%s",cmprom,cmdbuf);
6611 printf("\r%s%s",cmprom,cmdbuf);
6614 return(cmflgs = -1); /* Force reparse */
6617 #endif /* CK_RECALL */
6620 if (c < SP && quote == 0) { /* Any other unquoted control char */
6621 if (!chsrc) { /* If cmd file, point past it */
6626 continue; /* continue, don't put in buffer */
6628 linebegin = 0; /* Not at beginning of line */
6631 cmdecho((char) c, 0); /* Echo what was typed. */
6636 if (echof) cmdecho((char) c, 0); /* Echo what was typed. */
6638 } else { /* This character was quoted. */
6640 quote = 0; /* Unset the quote flag. */
6641 /* debug(F000,"gtword quote 0","",c); */
6642 /* Quote character at this level is only for SP, ?, and controls */
6643 /* If anything else was quoted, leave quote in, and let */
6644 /* the command-specific parsing routines handle it, e.g. \007 */
6645 if (c > 32 && c != '?' && c != RUB && chsrc != 0) {
6646 /* debug(F000,"gtword quote 1","",c); */
6647 *bp++ = CMDQ; /* Deposit \ if it came from tty */
6648 qf = 0; /* and don't erase it from screen */
6649 linebegin = 0; /* Not at beginning of line */
6652 This is a hack to handle "cd \" or "cd foo\" on OS/2 and similar systems.
6653 If we were called from cmdir() and the previous character was the quote
6654 character, i.e. backslash, and this character is the command terminator,
6655 then we stuff an extra backslash into the buffer without echoing, then
6656 we stuff the carriage return back in again, and go back and process it,
6657 this time with the quote flag off.
6659 } else if (dirnamflg && (c == CR || c == LF || c == SP)) {
6660 /* debug(F000,"gtword quote 2","",c); */
6662 linebegin = 0; /* Not at beginning of line */
6663 *bp = (c == SP ? SP : CR);
6665 #endif /* BS_DIRSEP */
6669 cmdecho((char) c, qf); /* Echo what was typed. */
6674 if (echof) cmdecho((char) c, qf); /* Now echo quoted character */
6676 /* debug(F111,"gtword quote",cmdbuf,c); */
6679 if (echof) cmdecho((char) c,quote); /* Echo what was typed. */
6680 #endif /* COMMENT */
6681 if (!comment) inword = 1; /* Flag we're in a word. */
6682 if (quote) continue; /* Don't deposit quote character. */
6683 if (c != NL) { /* Deposit command character. */
6684 *bp++ = (char) c; /* and make sure there is a NUL */
6686 *bp = NUL; /* after it */
6687 #endif /* COMMENT */
6689 } /* End of big while */
6691 printf("?Command too long, maximum length: %d.\n",CMDBL);
6696 /* Utility functions */
6698 /* A D D B U F -- Add the string pointed to by cp to the command buffer */
6701 addbuf(cp) char *cp; {
6703 while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
6704 *bp++ = *cp++; /* Copy and */
6705 len++; /* count the characters. */
6707 *bp++ = SP; /* Put a space at the end */
6708 *bp = NUL; /* Terminate with a null */
6709 np = bp; /* Update the next-field pointer */
6711 return(len); /* Return the length */
6714 /* S E T A T M -- Deposit a token in the atom buffer. */
6716 Break on space, newline, carriage return, or NUL.
6718 cp = Pointer to string to copy to atom buffer.
6719 fcode = 0 means break on whitespace or EOL.
6720 fcode = 1 means don't break on space.
6721 fcode = 2 means break on space, ':', or '='.
6722 fcode = 3 means copy the whole string.
6723 Null-terminate the result.
6724 Return length of token, and also set global "cc" to this length.
6725 Return -1 if token was too long.
6728 setatm(cp,fcode) char *cp; int fcode; {
6729 char *ap, *xp, *dqp = NULL, lbrace, rbrace;
6730 int bracelvl = 0, dq = 0;
6735 if (cmfldflgs & 1) { /* Handle grouping */
6742 cc = 0; /* Character counter */
6743 ap = atmbuf; /* Address of atom buffer */
6747 while (*s++) n++; /* Save a call to strlen */
6750 printf("?Command buffer overflow\n");
6753 /* debug(F111,"setatm",cp,n); */
6754 if (cp == ap) { /* In case source is atom buffer */
6755 xp = atybuf; /* make a copy */
6757 strncpy(xp,ap,ATMBL); /* so we can copy it back, edited. */
6761 while ((*xp++ = *s++)) ; /* We already know it's big enough */
6763 #endif /* COMMENT */
6765 *ap = NUL; /* Zero the atom buffer */
6766 if (fcode == 1) { /* Trim trailing blanks */
6767 while (--n >= 0 && cp[n] == SP)
6771 while (*cp == SP) { /* Trim leading spaces */
6775 if (*cp == '"') { /* Starts with doublequote? */
6782 else if (*cp == rbrace)
6786 if (bracelvl == 0) {
6788 if (*cp == SP || *cp == HT) {
6790 if (*(cp-1) == '"' && *(cp-2) != CMDQ) {
6795 } else if ((*cp == SP || *cp == HT) && fcode != 1 && fcode != 3)
6797 if ((fcode == 2) && (*cp == '=' || *cp == ':')) break;
6798 if ((fcode != 3) && (*cp == LF || *cp == CR)) break;
6803 *ap = NUL; /* Terminate the string. */
6804 /* debug(F111,"setatm result",atmbuf,cc); */
6805 return(cc); /* Return length. */
6809 These functions attempt to hide system dependencies from the mainline
6810 code in gtword(). Dummy arg for cmdgetc() needed for compatibility with
6811 coninc(), ttinc(), etc, since a pointer to this routine can be passed in
6812 place of those to tn_doop().
6814 No longer static. Used by askmore(). Fri Aug 20 15:03:34 1999.
6816 #define CMD_CONINC /* How we get keyboard chars */
6819 cmdgetc(timelimit) int timelimit; { /* Get a character from the tty. */
6822 extern int inserver;
6825 extern int x_logged;
6826 #endif /* CK_LOGIN */
6828 static int got_cr = 0;
6830 int tx = 0, is_tn = 0;
6838 debug(F111,"cmdgetc()","pushc",pushc);
6841 if (xcmfdb && c == '?') /* Don't echo ? twice if chaining. */
6848 c = dgncinb(0,&ch,1); /* -1 is EOF, -2 TO,
6849 * -c is AOS/VS error */
6850 if (c == -2) { /* timeout was enabled? */
6851 resto(channel(0)); /* reset timeouts */
6852 c = dgncinb(0,&ch,1); /* retry this now! */
6854 if (c < 0) return(-4); /* EOF or some error */
6855 else c = (int) ch & 0177; /* Get char without parity */
6858 #else /* Not datageneral */
6862 (!local && inserver) ||
6867 is_tn = !pushc && !local && sstelnet;
6870 c = coninc(timelimit > 0 ? 1 : 0);
6872 /* This is likely to break the asktimeout... */
6873 c = coninc(timelimit);
6874 #endif /* COMMENT */
6875 /* debug(F101,"cmdgetc coninc","",c); */
6877 if (c >= 0 && is_tn) { /* Server-side Telnet */
6880 /* debug(F111,"gtword IAC","c",c); */
6882 if ((tx = tn_doop((CHAR)(c & 0xff),ckxech,coninc)) == 0) {
6884 } else if (tx <= -1) { /* I/O error */
6885 /* If there was a fatal I/O error then ttclos() */
6886 /* has been called and the next GETNEXTCH attempt */
6887 /* will be !is_tn since ttclos() sets sstelnet = 0 */
6888 doexit(BAD_EXIT,-1); /* (or return(-4)? */
6889 } else if (tx == 1) { /* ECHO change */
6890 ckxech = dpx = 1; /* Get next char */
6892 } else if (tx == 2) { /* ECHO change */
6893 ckxech = dpx = 0; /* Get next char */
6895 } else if (tx == 3) { /* Quoted IAC */
6896 c = 255; /* proceeed with it. */
6899 else if (tx == 4) { /* IKS State Change */
6902 #endif /* IKS_OPTION */
6903 else if (tx == 6) { /* Remote Logout */
6904 doexit(GOOD_EXIT,0);
6906 goto GETNEXTCH; /* Unknown, get next char */
6911 if (!TELOPT_U(TELOPT_BINARY)) {
6913 /* This means the sender is violating Telnet */
6914 /* protocol because we received two CRs in a */
6915 /* row without getting either LF or NUL. */
6916 /* This will not solve the problem but it */
6917 /* will at least allow two CRs to do something */
6918 /* whereas before the user would have to guess */
6919 /* to send LF or NUL after the CR. */
6920 debug(F100,"gtword CR telnet error","",0);
6923 debug(F100,"gtword skipping CR","",0);
6924 got_cr = 1; /* Remember a CR was received */
6928 debug(F100,"gtword CR to LF","",0);
6933 if (!TELOPT_U(TELOPT_BINARY)) {
6935 debug(F100,"gtword LF","",0);
6939 debug(F100,"gtword skipping LF","",0);
6945 if (!TELOPT_U(TELOPT_BINARY) && got_cr) {
6947 debug(F100,"gtword NUL to LF","",0);
6949 debug(F100,"gtword NUL","",0);
6955 if ( !TELOPT_U(TELOPT_BINARY) && got_cr ) {
6956 /* This means the sender is violating Telnet */
6957 /* protocol because we received two CRs in a */
6958 /* row without getting either LF or NUL. */
6959 /* This will not solve the problem but it */
6960 /* will at least allow two CRs to do something */
6961 /* whereas before the user would have to guess */
6962 /* to send LF or NUL after the CR. */
6963 debug(F100,"gtword CR telnet error","",0);
6965 got_cr = 1; /* Remember a CR was received */
6967 /* debug(F100,"gtword CR to LF","",0); */
6973 /* debug(F100,"gtword skipping LF","",0); */
6980 /* debug(F100,"gtword skipping NUL","",0); */
6984 debug(F100,"gtword NUL","",0);
6985 #endif /* COMMENT */
6988 #endif /* COMMENT */
6990 case ETX: /* Ctrl-C... */
6991 case EOT: /* EOT = EOF */
6995 #endif /* CK_LOGIN */
7011 #endif /* CMD_CONINC */
7019 #endif /* CMD_CONINC */
7021 /* debug(F101,"cmdgetc getc","",c); */
7027 #endif /* CMD_CONINC */
7028 c = getchar(); /* RTU doesn't discard the ^Z */
7032 #endif /* datageneral */
7033 return(c); /* Return what we got */
7036 /* #ifdef USE_ARROWKEYS */
7038 /* Mechanism to use for peeking into stdin buffer */
7040 #ifndef USE_FILE_CNT /* stdin->__cnt */
7041 #ifndef USE_FILE__CNT /* Note: two underscores */
7042 #ifdef HPUX /* HPUX 7-11 */
7045 #define USE_FILE__CNT
7049 #ifdef ANYSCO /* SCO UNIX, OSR5, Unixware, etc */
7050 #ifndef OLD_UNIXWARE /* But not Unixware 1.x or 2.0 */
7051 #ifndef UNIXWARE2 /* or 2.1.0 */
7052 #define USE_FILE__CNT
7053 #endif /* UNIXWARE2 */
7054 #endif /* OLD_UNIXWARE */
7057 #endif /* USE_FILE__CNT */
7058 #endif /* USE_FILE_CNT */
7060 #ifndef USE_FILE_R /* stdin->_r */
7061 #ifndef USE_FILE_CNT
7062 #ifndef USE_FILE__CNT
7063 #ifdef BSD44 /* {Free,Open,Net}BSD, BSDI */
7066 #endif /* USE_FILE__CNT */
7067 #endif /* USE_FILE_CNT */
7068 #endif /* USE_FILE_R */
7070 #ifndef USE_FILE_R /* stdin->_cnt */
7071 #ifndef USE_FILE_CNT
7072 #ifndef USE_FILE__CNT
7073 #define USE_FILE_CNT /* Everybody else (but Linux) */
7074 #endif /* USE_FILE__CNT */
7075 #endif /* USE_FILE_CNT */
7076 #endif /* USE_FILE_R */
7082 How many characters are waiting to be read at the console? Normally
7083 conchk() would tell us, but in Unix and VMS cmdgetc() uses stdio getchar(),
7084 thus bypassing coninc()/conchk(), so we have to peek into the stdin buffer,
7085 which is totally nonportable. Which is why this routine is, at least for
7086 now, used only for checking for arrow-key sequences from the keyboard after
7087 an ESC was read. Wouldn't it be nice if the stdio package had a function
7088 that returned the number of bytes waiting to be read from its buffer?
7089 Returns 0 or greater always.
7094 y = pushc ? 1 : 0; /* Have command character pushed? */
7096 x = conchk(); /* Check device-driver buffer */
7099 #ifdef CMD_CONINC /* See cmdgetc() */
7100 x = conchk(); /* Check device-driver buffer */
7102 #else /* CMD_CONINC */
7104 /* Here we must look inside the stdin buffer - highly platform dependent */
7106 #ifdef _IO_file_flags /* Linux */
7107 x = (int) ((stdin->_IO_read_end) - (stdin->_IO_read_ptr));
7108 debug(F101,"cmdconchk _IO_file_flags","",x);
7109 #else /* _IO_file_flags */
7110 #ifdef USE_FILE_CNT /* Traditional */
7112 debug(F101,"cmdconchk (*stdin)->_cnt","",(*stdin)->_cnt);
7116 debug(F101,"cmdconchk NOARROWKEYS x","",0);
7118 debug(F101,"cmdconchk stdin->_cnt","",stdin->_cnt);
7120 #endif /* NOARROWKEYS */
7122 if (x == 0) x = conchk();
7124 #else /* USE_FILE_CNT */
7125 #ifdef USE_FILE__CNT /* HP-UX */
7126 debug(F101,"cmdconchk stdin->__cnt","",stdin->__cnt);
7128 if (x == 0) x = conchk();
7130 #else /* USE_FILE_CNT */
7131 #ifdef USE_FILE_R /* FreeBSD, OpenBSD, etc */
7132 debug(F101,"cmdconchk stdin->_r","",stdin->_r);
7134 if (x == 0) x = conchk();
7137 /* Fill in any others here... */
7139 #endif /* USE_FILE_R */
7140 #endif /* USE_FILE__CNT */
7141 #endif /* USE_FILE_CNT */
7142 #endif /* _IO_file_flags */
7143 #endif /* CMD_CONINC */
7147 /* #endif */ /* USE_ARROWKEYS */
7151 cmdclrscn() { /* Clear the screen */
7155 static VOID /* What to echo at end of command */
7160 #endif /* CK_ANSIC */
7164 extern int inserver;
7165 if (inserver && c == LF)
7170 putchar(c); /* c is the terminating character */
7172 #ifdef WINTCP /* what is this doing here? */
7173 if (c == CR) putchar(NL);
7177 A.A. Chernov, who sent in changes for FreeBSD, said we also needed this
7178 for SVORPOSIX because "setup terminal by termios and curses does
7179 not convert \r to \n, so additional \n needed in newline function." But
7180 it is also very likely to result in unwanted blank lines.
7183 if (c == CR) putchar(NL);
7187 /* OS2 no longer needs this as all CR are converted to NL in coninc() */
7188 /* This eliminates the ugly extra blank lines discussed above. */
7190 if (c == CR) putchar(NL);
7192 #endif /* COMMENT */
7194 if (c == CR) putchar(NL);
7197 if (c == CR) putchar(NL);
7200 if (c == CR) putchar(NL);
7201 #endif /* datageneral */
7203 if (c == CR) putchar(NL);
7206 if (c == CR) putchar(NL);
7207 #endif /* STRATUS */
7211 cmdchardel() { /* Erase a character from the screen */
7214 /* DG '\b' is EM (^y or \031) */
7216 /* Erase a character from non-DG screen, */
7217 dgncoub(1,"\010 \010",3);
7219 #endif /* datageneral */
7232 cmdecho(char c, int quote)
7234 cmdecho(c,quote) char c; int quote;
7235 #endif /* CK_ANSIC */
7238 /* Echo tty input character c */
7244 putchar((CHAR) (isprint(c) ? c : '^' ));
7246 putchar((CHAR) ((c >= SP && c < DEL) ? c : '^'));
7247 #endif /* isprint */
7250 if (quote==1 && c==CR) putchar((CHAR) NL);
7256 /* Return pointer to current position in command buffer. */
7271 /* X X E S C -- Interprets backslash codes */
7272 /* Returns the int value of the backslash code if it is > -1 and < 256 */
7273 /* and updates the string pointer to first character after backslash code. */
7274 /* If the argument is invalid, leaves pointer unchanged and returns -1. */
7277 xxesc(s) char **s; { /* Expand backslash escapes */
7278 int x, y, brace, radix; /* Returns the int value */
7279 char hd = '9'; /* Highest digit in radix */
7282 p = *s; /* pointer to beginning */
7283 if (!p) return(-1); /* watch out for null pointer */
7284 x = *p++; /* character at beginning */
7285 if (x != CMDQ) return(-1); /* make sure it's a backslash code */
7287 x = *p; /* it is, get the next character */
7288 if (x == '{') { /* bracketed quantity? */
7289 p++; /* begin past bracket */
7293 switch (x) { /* Start interpreting */
7294 case 'd': /* Decimal radix indicator */
7296 p++; /* Just point past it and fall thru */
7297 case '0': /* Starts with digit */
7299 case '2': case '3': case '4': case '5':
7300 case '6': case '7': case '8': case '9':
7301 radix = 10; /* Decimal */
7302 hd = '9'; /* highest valid digit */
7304 case 'o': /* Starts with o or O */
7306 radix = 8; /* Octal */
7307 hd = '7'; /* highest valid digit */
7308 p++; /* point past radix indicator */
7310 case 'x': /* Starts with x or X */
7312 radix = 16; /* Hexadecimal */
7313 p++; /* point past radix indicator */
7315 default: /* All others */
7317 *s = p+1; /* Treat as quote of next char */
7321 #endif /* COMMENT */
7323 /* For OS/2, there are "wide" characters required for the keyboard
7324 * binding, i.e \644 and similar codes larger than 255 (byte).
7325 * For this purpose, give up checking for < 256. If someone means
7326 * \266 should result in \26 followed by a "6" character, he should
7327 * always write \{26}6 anyway. Now, return only the lower byte of
7328 * the result, i.e. 10, but eat up the whole \266 sequence and
7329 * put the wide result 266 into a global variable. Yes, that's not
7330 * the most beautiful programming style but requires the least
7331 * amount of changes to other routines.
7333 if (*p == '{') { /* Sun May 11 20:00:40 2003 */
7334 brace = 1; /* Allow {} after radix indicator */
7337 if (radix <= 10) { /* Number in radix 8 or 10 */
7339 (*p) && (*p >= '0') && (*p <= hd)
7341 && (y < 5) && (x*radix < KMSIZE);
7342 /* the maximum needed value \8196 is 4 digits long */
7343 /* while as octal it requires \1377, i.e. 5 digits */
7345 && (y < 3) && (x*radix < 256);
7348 x = x * radix + (int) *p - 48;
7351 wideresult = x; /* Remember wide result */
7354 if (y == 0 || x > 255) { /* No valid digits? */
7355 *s = p; /* point after it */
7356 return(-1); /* return failure. */
7358 } else if (radix == 16) { /* Special case for hex */
7359 if ((x = unhex(*p++)) < 0) { *s = p - 1; return(-1); }
7360 if ((y = unhex(*p++)) < 0) { *s = p - 2; return(-1); }
7361 x = ((x << 4) & 0xF0) | (y & 0x0F);
7364 if ((y = unhex(*p)) >= 0) {
7366 wideresult = ((x << 4) & 0xFF0) | (y & 0x0F);
7367 x = wideresult & 255;
7371 if (brace && *p == '}' && x > -1) /* Point past closing brace, if any */
7373 *s = p; /* Point to next char after sequence */
7374 return(x); /* Return value of sequence */
7377 int /* Convert hex string to int */
7382 #endif /* CK_ANSIC */
7385 if (x >= '0' && x <= '9') /* 0-9 is offset by hex 30 */
7387 else if (x >= 'A' && x <= 'F') /* A-F offset by hex 37 */
7389 else if (x >= 'a' && x <= 'f') /* a-f offset by hex 57 */
7390 return(x - 0x57); /* (obviously ASCII dependent) */
7394 /* L O O K U P -- Lookup the string in the given array of strings */
7397 Call this way: v = lookup(table,word,n,&x);
7399 table - a 'struct keytab' table.
7400 word - the target string to look up in the table.
7401 n - the number of elements in the table.
7402 x - address of an integer for returning the table array index,
7403 or NULL if you don't need a table index.
7405 The keyword table must be arranged in ascending alphabetical order;
7406 alphabetic case doesn't matter but letters are treated as lowercase
7407 for purposes of ordering; thus "^" and "_" come *before* the letters,
7410 Returns the keyword's associated value (zero or greater) if found,
7411 with the variable x set to the keyword-table index. If is lookup()
7412 is not successful, it returns:
7414 -3 if nothing to look up (target was null),
7418 A match is successful if the target matches a keyword exactly, or if
7419 the target is a prefix of exactly one keyword. It is ambiguous if the
7420 target matches two or more keywords from the table.
7422 Lookup() is the critical routine in scripts and so is optimized with a
7423 simple static cache plus some other tricks. Maybe it could be improved
7424 further with binary search or hash techniques but I doubt it since most
7425 keyword tables are fairly short.
7428 #ifdef USE_LUCACHE /* Lookup cache */
7429 extern int lusize; /* (initialized in ckuus5.c) */
7430 extern char * lucmd[];
7433 extern struct keytab * lutab[];
7438 #endif /* USE_LUCACHE */
7441 lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {
7444 int v, len, cmdlen = 0;
7445 char c = NUL, c1, *s;
7447 /* Get 1st char of search object, if it's null return -3. */
7449 if (!cmd || n < 1) /* Defense de nullarg */
7451 c1 = *cmd; /* First character */
7452 if (!c1) /* Make sure there is one */
7454 if (isupper(c1)) /* If letter make it lowercase */
7457 #ifdef USE_LUCACHE /* lookup() cache */
7459 lucalls++; /* Count this lookup() call */
7460 for (i = 0; i < m; i++) { /* Loop thru cache */
7461 if (*(lucmd[i]) == c1) { /* Same as 1st char of search item? */
7462 if (lutab[i] == table) { /* Yes - same table too? */
7463 if (!strcmp(cmd,lucmd[i])) { /* Yes - compare */
7464 if (x) *x = luidx[i]; /* Match - return index */
7465 luhits++; /* Count cache hit */
7466 return(luval[i]); /* Return associated value */
7471 #endif /* USE_LUCACHE */
7473 /* Not null, not in cache, look it up */
7476 while (*s++) cmdlen++; /* Length of target */
7478 Quick binary search to find last table entry whose first character is
7479 lexically less than the first character of the search object. This is
7480 the starting point of the next loop, which must go in sequence since it
7481 compares adjacent table entries.
7483 if (n < 5) { /* Not worth it for small tables */
7489 while (lo+2 < hi && ++count < 12) {
7490 i = lo + ((hi - lo) / 2);
7491 c = *(table[i].kwd);
7492 if (isupper(c)) c = tolower(c);
7499 i = (c < c1) ? lo+1 : lo;
7501 if (i > 0) xxhits++;
7502 #endif /* USE_LUCACHE */
7504 for ( ; i < n-1; i++) {
7507 #endif /* USE_LUCACHE */
7509 c = *(table[i].kwd);
7511 if (isupper(c)) c = tolower(c);
7513 /* The following is a big performance booster but makes it */
7514 /* absolutely essential that all lookup() tables are in order. */
7516 if (c > c1) /* Leave early if past our mark */
7520 /* Use LOG DEBUG to check */
7523 if (ckstrcmp(table[i].kwd,table[i+1].kwd,0,0) > 0) {
7524 printf("TABLE OUT OF ORDER [%s] [%s]\n",
7525 table[i].kwd,table[i+1].kwd);
7535 if ((len == cmdlen && !ckstrcmp(table[i].kwd,cmd,len,0)) ||
7536 ((v = !ckstrcmp(table[i].kwd,cmd,cmdlen,0)) &&
7537 ckstrcmp(table[i+1].kwd,cmd,cmdlen,0))) {
7539 return(table[i].kwval);
7543 if (v) { /* Ambiguous */
7544 if (x) *x = i; /* Set index of first match */
7549 /* Last (or only) element */
7551 if (!ckstrcmp(table[n-1].kwd,cmd,cmdlen,0)) {
7553 debug(F111,"lookup",table[i].kwd,table);
7554 return(table[n-1].kwval);
7561 Like lookup, but requires a full (but case-independent) match
7562 and does NOT require the table to be in order.
7565 xlookup(table,cmd,n,x) struct keytab table[]; char *cmd; int n, *x; {
7567 int len, cmdlen, one = 0;
7568 register char c, c2, * s, * s2;
7570 if (!cmd) cmd = ""; /* Check args */
7571 if (!*cmd || n < 1) return(-3);
7573 c = *cmd; /* First char of string to look up */
7574 if (!*(cmd+1)) { /* Special handling for 1-char names */
7582 while (*s++) cmdlen++;
7590 for (i = 0; i < n; i++) {
7591 s = table[i].kwd; /* This entry */
7593 if (!*s) continue; /* Empty table entry */
7595 if (isupper(c2)) c2 = tolower(c2);
7596 if (c != c2) continue; /* First char doesn't match */
7597 if (one) { /* Name is one char long */
7601 return(table[i].kwval); /* So is table entry */
7603 } else { /* Otherwise do string comparison */
7606 while (*s2++) len++;
7607 if (len == cmdlen && !ckstrcmp(s,cmd,-1,0)) {
7609 return(table[i].kwval);
7616 /* Reverse lookup */
7619 rlookup(table,n,x) struct keytab table[]; int n, x; {
7621 for (i = 0; i < n; i++) {
7622 if (table[i].kwval == x)
7623 return(table[i].kwd);