3 char *cmdv = "Command package 9.0.168, 12 March 2010";
5 /* C K U C M D -- Interactive command package for Unix */
7 /* (In reality, it's for all platforms, not just Unix) */
10 Author: Frank da Cruz (fdc@columbia.edu),
11 Columbia University Academic Information Systems, New York City.
13 Copyright (C) 1985, 2010,
14 Trustees of Columbia University in the City of New York.
15 All rights reserved. See the C-Kermit COPYING.TXT file or the
16 copyright text in the ckcmai.c module for disclaimer and permissions.
23 /* Command-terminal-to-C-Kermit character mask */
26 int cmdmsk = 255; /* (always was 255) */
27 #else /* All others... */
28 int cmdmsk = 255; /* 31 Dec 2000 (was 127) */
31 #ifdef BS_DIRSEP /* Directory separator is backslash */
33 #endif /* BS_DIRSEP */
37 #endif /* BS_DIRSEP */
41 #include "ckcdeb.h" /* Formats for debug(), etc. */
42 #include "ckcker.h" /* Needed for BIGBUFOK definition */
43 #include "ckcnet.h" /* Needed for server-side Telnet */
44 #include "ckucmd.h" /* Needed for everything */
45 #include "ckuusr.h" /* Needed for prompt length */
50 #define USE_ARROWKEYS /* Use arrow keys for command recall */
51 #endif /* VMSORUNIX */
53 #endif /* NOARROWKEYS */
57 _PROTOTYP( int unhex, (char) );
58 _PROTOTYP( static VOID cmdclrscn, (void) );
61 _PROTOTYP( VOID learncmd, (char *) );
64 static char *moname[] = {
65 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
66 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
69 struct keytab cmonths[] = {
72 { "december", 12, 0 },
79 { "november", 11, 0 },
84 #ifndef NOICP /* The rest only if interactive command parsing selected */
87 _PROTOTYP( int chkvar, (char *) );
88 extern int askflag, echostars;
99 int cmfldflgs = 0; /* Flags for cmfld() */
100 int cmkwflgs = 0; /* Flags from last keyword parse */
101 static int nomsg = 0;
102 static int blocklvl = 0; /* Block nesting level */
103 static int linebegin = 0; /* Flag for at start of a line */
104 static int quoting = 1; /* Quoting is allowed */
105 static int swarg = 0; /* Parsing a switch argument */
106 static int xcmfdb = 0; /* Flag for parsing chained fdbs... */
107 static int chsrc = 0; /* Source of character, 1 = tty */
108 static int newcmd = 0; /* See addcmd() */
111 static int dirnamflg = 0;
112 #endif /* BS_DIRSEP */
115 Modeled after the DECSYSTEM-20 command parser (the COMND JSYS), RIP. Features:
117 . parses and verifies keywords, filenames, text strings, numbers, other data
118 . displays appropriate menu or help message when user types "?"
119 . does keyword and filename completion when user types ESC or TAB
120 . does partial keyword and filename completion
121 . accepts any unique abbreviation for a keyword
122 . allows keywords to have attributes, like "invisible" and "abbreviation"
123 . can supply defaults for fields omitted by user
124 . provides command retry and recall
125 . provides character, word, and line deletion (but only from the end)
126 . accepts input from keyboard, command files, macros, or redirected stdin
127 . allows for full or half duplex operation, character or line input
128 . allows \-escapes for special characters
129 . allows specification of a user exit to expand variables, etc.
130 . settable prompt, protected from deletion, dynamically re-evaluated each time
131 . allows chained parse functions.
134 cmsetp - Set prompt (cmprom is prompt string)
135 cmsavp - Save current prompt
136 cmgetp = Get current prompt
137 prompt - Issue prompt
138 cmini - Clear the command buffer (before parsing a new command)
139 cmres - Reset command buffer pointers (before reparsing)
140 cmkey - Parse a keyword or token (also cmkey2)
141 cmswi - Parse a switch
142 cmnum - Parse a number
143 cmifi - Parse an input file name
144 cmofi - Parse an output file name (also cmifip, cmifi2, ...)
145 cmdir - Parse a directory name (also cmdirp)
146 cmfld - Parse an arbitrary field
147 cmtxt - Parse a text string
148 cmdate - Parse a date-time string
149 cmcfm - Parse command confirmation (end of line)
150 cmfdb - Parse any of a list of the foregoing (chained parse functions)
153 -9: like -2 except this module already printed the error message
154 -3: no input provided when required
155 -2: input was invalid (e.g. not a number when a number was required)
156 -1: reparse required (user deleted into a preceding field)
157 0 or greater: success
158 See individual functions for greater detail.
160 Before using these routines, the caller should #include "ckucmd.h" and set the
161 program's prompt by calling cmsetp(). If the file parsing functions cmifi,
162 cmofi, or cmdir are to be used, this module must be linked with a ck?fio file
163 system support module for the appropriate system, e.g. ckufio for Unix. If
164 the caller puts the terminal in character wakeup ("cbreak") mode with no echo,
165 then these functions will provide line editing -- character, word, and line
166 deletion, as well as keyword and filename completion upon ESC and help
167 strings, keyword, or file menus upon '?'. If the caller puts the terminal
168 into character wakeup/noecho mode, care should be taken to restore it before
169 exit from or interruption of the program. If the character wakeup mode is not
170 set, the system's own line editor may be used.
172 NOTE: Contrary to expectations, many #ifdef's have been added to this module.
173 Any operation requiring an #ifdef (like clear screen, get character from
174 keyboard, erase character from screen, etc) should eventually be turned into a
175 call to a function that is defined in ck?tio.c, but then all the ck?tio.c
176 modules would have to be changed...
182 #include "ckcasc.h" /* ASCII character symbols */
183 #include "ckucmd.h" /* Command parsing definitions */
189 #endif /* _NO_PROTO */
190 #endif /* CK_ANSIC */
194 #include <errno.h> /* Error number symbols */
196 #ifndef ERRNO_INCLUDED
197 #include <errno.h> /* Error number symbols */
198 #endif /* ERRNO_INCLUDED */
199 #endif /* HPUXPRE65 */
204 #define INCL_VIO /* Needed for ckocon.h */
216 #define cc ccount /* OS-9/68K compiler bug */
219 #ifdef GEMDOS /* Atari ST */
223 #define putchar(x) conoc(x)
227 extern int cmdadl, justone;
228 #endif /* CK_AUTODL */
230 extern int timelimit, nzxopts, nopush, nolocal, xcmdsrc, keepallchars;
234 #ifdef CKXPRINTF /* Our printf macro conflicts with */
235 #undef printf /* use of "printf" in syslog.h */
236 #endif /* CKXPRINTF */
238 #include <sys/syslog.h>
243 #define printf ckxprintf
244 #endif /* CKXPRINTF */
246 #endif /* CKSYSLOG */
248 /* Local variables */
251 int psetf = 0, /* Flag that prompt has been set */
252 cc = 0, /* Character count */
253 dpx = 0, /* Duplex (0 = full) */
254 inword = 0; /* In the middle of getting a word */
256 char *dfprom = "Command? "; /* Default prompt */
258 char *lastfile = NULL; /* Last filespec */
259 static char *tmplastfile = NULL; /* Last filespec candidate */
260 #endif /* NOLASTFILE */
262 int cmflgs; /* Command flags */
263 int cmfsav; /* A saved version of them */
265 static char pushc = NUL;
266 static char brkchar = NUL;
268 #define CMDEFAULT 1023
269 static char cmdefault[CMDEFAULT+1];
272 char *cmdbuf = NULL; /* Command buffer */
273 char *savbuf = NULL; /* Buffer to save copy of command */
274 char *atmbuf = NULL; /* Atom buffer - for current field */
275 char *atxbuf = NULL; /* For expanding the atom buffer */
276 char *prevcmd = NULL;
277 static char *atybuf = NULL; /* For copying atom buffer */
278 static char *filbuf = NULL; /* File name buffer */
279 static char *cmprom = NULL; /* Program's prompt */
280 static char *cmprxx = NULL; /* Program's prompt, unevaluated */
284 Command recall is available only if we can make profligate use of malloc().
286 #define R_MAX 10 /* How many commands to save */
287 int cm_recall = R_MAX; /* Size of command recall buffer */
288 int on_recall = 1; /* Recall feature is ON */
289 static int no_recall = 0; /* Recall OFF for this cmd only */
290 static int force_add = 0; /* Force cmd into recall buffer */
291 static int last_recall = -1; /* Last recall-related action */
294 0 = CR (a command was entered)
298 int in_recall = 0; /* Recall buffers are init'd */
300 current = -1, /* Pointer to current command */
301 rlast = -1; /* Index of last command in buffer */
302 static char **recall = NULL; /* Array of recall buffer pointers */
303 #endif /* CK_RECALL */
305 char cmdbuf[CMDBL+4]; /* Command buffer */
306 char savbuf[CMDBL+4]; /* Buffer to save copy of command */
307 char atmbuf[ATMBL+4]; /* Atom buffer */
308 char atxbuf[CMDBL+4]; /* For expanding the atom buffer */
309 char prevcmd[CMDBL+4]; /* For displaying the last command */
310 static char atybuf[ATMBL+4]; /* For copying atom buffer */
311 static char filbuf[ATMBL+4]; /* File name buffer */
312 static char cmprom[PROMPTL+1]; /* Program's prompt */
313 static char cmprxx[PROMPTL+1]; /* Program's prompt, unevaluated */
316 /* Command buffer pointers */
318 #define PPVLEN VNAML /* 20080305 Wolfram Sang (was 24) */
319 char ppvnambuf[PPVLEN+1] = { NUL, NUL };
321 char * cmbptr = NULL; /* Current position (for export) */
323 static char *bp, /* Current command buffer position */
324 *pp, /* Start of current field */
325 *np; /* Start of next field */
327 static int ungw, /* For ungetting words */
328 atxn; /* Expansion buffer (atxbuf) length */
331 extern int wideresult;
334 extern int cmd_cols, cmd_rows, local, quiet;
343 _PROTOTYP( static int gtword, (int) );
344 _PROTOTYP( static int addbuf, (char *) );
345 _PROTOTYP( static int setatm, (char *, int) );
346 _PROTOTYP( static VOID cmdnewl, (char) );
347 _PROTOTYP( static VOID cmdchardel, (void) );
348 _PROTOTYP( static VOID cmdecho, (char, int) );
349 _PROTOTYP( static int test, (int, int) );
351 _PROTOTYP( extern char *strchr, (char *, int) );
356 /* The following are for use with chained FDB's */
358 static int crflag = 0; /* Carriage return was typed */
359 static int qmflag = 0; /* Question mark was typed */
360 static int esflag = 0; /* Escape was typed */
362 /* Directory separator */
365 static char dirsep = '\\';
368 static char dirsep = ':';
371 static char dirsep = ':';
374 static char dirsep = '.';
377 static char dirsep = '>';
379 static char dirsep = '/'; /* UNIX, OS/2, OS-9, Amiga, etc. */
383 #endif /* datageneral */
386 /* H A S N O P A T H */
388 /* Returns 0 if filespec s includes any path segments; 1 if it doesn't. */
391 hasnopath(s) char * s; {
396 return(ckstrcmp(s,p,CKMAXPATH,filecase) == 0 ? 1 : 0);
399 /* C K S P R E A D -- Print string double-spaced */
401 static char * sprptr = NULL;
404 ckspread(s) char * s; {
410 sprptr = malloc(n + n + 3);
419 return(sprptr ? sprptr : "");
422 /* T E S T -- Bit test */
425 test(x,m) int x, m; { /* Returns 1 if any bits from m are on in x, else 0 */
426 return((x & m) ? 1 : 0);
429 /* K W D H E L P -- Given a keyword table, print keywords in columns. */
433 n - number of entries
434 pat - pattern (left substring) that must match for each keyword
435 pre - prefix to add to each keyword
436 post - suffix to add to each keyword
437 off - offset on first screenful, allowing room for introductory text
438 xhlp - 1 to print any CM_INV keywords that are not also abbreviations.
439 2 to print CM_INV keywords if CM_HLP also set
440 4 if it's a switch table (to show ':' if CM_ARG)
442 Arranges keywords in columns with width based on longest keyword.
443 Does "more?" prompting at end of screen.
444 Uses global cmd_rows and cmd_cols for screen size.
447 kwdhelp(s,n,pat,pre,post,off,xhlp)
448 struct keytab s[]; int n, off, xhlp; char *pat, *pre, *post;
453 int cols, height, i, j, k, lc, n2 = 0;
454 char *b = NULL, *p, *q;
461 if (!s) return; /* Nothing to do */
462 if (n < 1) return; /* Ditto */
463 if (off < 0) off = 0; /* Offset for first page */
464 if (!pre) pre = ""; /* Handle null string pointers */
465 if (!post) post = "";
466 lc = off; /* Screen-line counter */
468 if (xhlp & 4) /* For switches */
469 tmpbuf = (char *)malloc(TMPBUFSIZ+1);
471 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
472 for (i = 0; i < n; i++) { /* Find longest keyword */
474 if (ckstrcmp(s[i].kwd,pat,cc,0))
477 if (s[i].flgs & CM_PSH /* NOPUSH or nopush screening */
483 if (s[i].flgs & CM_LOC /* NOLOCAL or nolocal screening */
490 if (s[i].flgs & CM_INV) {
492 /* This code does not show invisible keywords at all except for "help ?" */
493 /* and then only help topics (CM_HLP) in the top-level keyword list. */
497 else if ((s[i].flgs & CM_HLP) == 0)
500 /* This code shows invisible keywords that are not also abbreviations when */
501 /* ? was typed AFTER the beginning of the field so the user can find out */
502 /* what they are and (for example) why completion doesn't work at this point */
504 if (s[i].flgs & CM_ABR)
506 else if ((xhlp & 3) == 0)
508 else if ((xhlp & 2) && ((s[i].flgs & CM_HLP) == 0))
512 j = strlen(s[i].kwd);
513 if (!(xhlp & 4) || !tmpbuf) { /* Regular keyword table */
514 s2[n2++] = s[i].kwd; /* Copy pointers to visible ones */
515 } else { /* Switches */
516 ckmakmsg(tmpbuf, /* Make a copy that shows ":" if */
517 TMPBUFSIZ, /* the switch takes an argument. */
519 (s[i].flgs & CM_ARG) ? ":" : "",
523 makestr(&(s2[n2]),tmpbuf);
524 if (s[i].flgs & CM_ARG) j++;
533 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
536 width += (int)strlen(pre) + (int)strlen(post) + 2;
537 cols = cmd_cols / width; /* How many columns? */
538 if (cols < 1) cols = 1;
539 height = n / cols; /* How long is each column? */
540 if (n % cols) height++; /* Add one for remainder, if any */
542 for (i = 0; i < height; i++) { /* Loop for each row */
543 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
545 for (j = 0; j < cols; j++) { /* Loop for each column in row */
546 k = i + (j * height); /* Index of next keyword */
547 if (k < n) { /* In range? */
550 p = s2[k]; /* Point to verb name */
551 q = b + (j * width) + 1; /* Where to copy it to */
552 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
553 q--; /* Back up over NUL */
554 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
555 q--; /* Back up over NUL */
556 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
559 *q = SP; /* Replace the space */
563 p = b + cmd_cols - 1; /* Last char in line */
564 while (*p-- == SP) ; /* Trim */
566 printf("%s\n",b); /* Print the line */
567 if (++lc > (cmd_rows - 2)) { /* Screen full? */
568 if (!askmore()) /* Do more-prompting... */
574 /* printf("\n"); */ /* Blank line at end of report */
575 } else { /* Malloc failure, no columns */
576 for (i = 0; i < n; i++) {
577 if (s[i].flgs & CM_INV) /* Use original keyword table */
578 continue; /* skipping invisible entries */
579 printf("%s%s%s\n",pre,s[i].kwd,post);
580 if (++lc > (cmd_rows - 2)) { /* Screen full? */
581 if (!askmore()) /* Do more-prompting... */
590 if (tmpbuf) free((char *)tmpbuf);
591 for (i = 0; i < n; i++)
592 if (s2[i]) free(s2[i]);
594 if (s2) free(s2); /* Free array copy */
595 if (b) free(b); /* Free line buffer */
599 /* X F I L H E L P -- Given a file list, print names in columns. */
602 n - number of entries
603 pre - prefix to add to each filename
604 post - suffix to add to each filename
605 off - offset on first screenful, allowing room for introductory text
606 cmdirflg - 1 if only directory names should be listed, 0 to list all files
607 fs - call fileselect() to decide whether to include each file.
608 The rest of the args are the same as for fileselect().
610 Arranges filenames in columns with width based on longest filename.
611 Does "more?" prompting at end of screen.
612 Uses global cmd_rows and cmd_cols for screen size.
618 int n, char *pre, char *post, int off, int cmdirflag,
619 int fs, char *sa, char *sb, char *sna, char *snb,
620 CK_OFF_T minsiz, CK_OFF_T maxsiz,
625 xfilhelp(n,pre,post,off,cmdirflg,
626 fs,sa,sb,sna,snb,minsiz,maxsiz,nbu,nxlist,xlist)
627 int n, off; char *pre, *post; int cmdirflg;
628 int fs; char *sa,*sb,*sna,*snb; CK_OFF_T minsiz,maxsiz;
629 int nbu,nxlist; char ** xlist;
630 #endif /* CK_ANSIC */
632 char filbuf[CKMAXPATH + 1]; /* Temp buffer for one filename */
634 int cols, height, i, j, k, lc, n2 = 0, rc = 0, itsadir = 0;
635 char *b = NULL, *p, *q;
639 char * cdp = zgtdir();
642 if (n < 1) return(0);
643 if (off < 0) off = 0; /* Offset for first page */
644 if (!pre) pre = ""; /* Handle null string pointers */
645 if (!post) post = "";
647 lc = off; /* Screen-line counter */
649 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
650 for (i = 0; i < n; i++) { /* Loop through filenames */
652 s2[i] = NULL; /* Initialize each pointer to NULL */
653 znext(filbuf); /* Get next filename */
654 if (!filbuf[0]) /* Shouldn't happen */
657 itsadir = isdir(filbuf); /* Is it a directory? */
658 if (cmdirflg && !itsadir) /* No, listing directories only? */
659 continue; /* So skip this one. */
661 if (fs) if (fileselect(filbuf,
663 minsiz,maxsiz,nbu,nxlist,xlist) < 1) {
667 ckstrncpy(filbuf,zrelname(filbuf,cdp),CKMAXPATH);
671 if (itsadir && j < CKMAXPATH - 1 && j > 0) {
672 if (filbuf[j-1] != dirsep) {
673 filbuf[j++] = dirsep;
678 if (!(s2[n2] = malloc(j+1))) {
679 printf("?Memory allocation failure\n");
683 if (j <= CKMAXPATH) {
684 strcpy(s2[n2],filbuf);
687 printf("?Name too long - %s\n", filbuf);
691 if (j > width) /* Get width of widest one */
694 n = n2; /* How many we actually got */
696 sh_sort(s2,NULL,n,0,0,filecase); /* Alphabetize the list */
699 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
702 width += (int)strlen(pre) + (int)strlen(post) + 2;
703 cols = cmd_cols / width; /* How many columns? */
704 if (cols < 1) cols = 1;
705 height = n / cols; /* How long is each column? */
706 if (n % cols) height++; /* Add one for remainder, if any */
708 for (i = 0; i < height; i++) { /* Loop for each row */
709 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
711 for (j = 0; j < cols; j++) { /* Loop for each column in row */
712 k = i + (j * height); /* Index of next filename */
713 if (k < n) { /* In range? */
716 p = s2[k]; /* Point to filename */
717 q = b + (j * width) + 1; /* and destination */
718 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
719 q--; /* Back up over NUL */
720 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
721 q--; /* Back up over NUL */
722 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
725 *q = SP; /* Replace the space */
729 p = b + cmd_cols - 1; /* Last char in line */
730 while (*p-- == SP) ; /* Trim */
732 printf("%s\n",b); /* Print the line */
733 if (++lc > (cmd_rows - 2)) { /* Screen full? */
734 if (!askmore()) { /* Do more-prompting... */
741 printf("\n"); /* Blank line at end of report */
743 } else { /* Malloc failure, no columns */
744 for (i = 0; i < n; i++) {
746 if (!filbuf[0]) break;
747 printf("%s%s%s\n",pre,filbuf,post);
748 if (++lc > (cmd_rows - 2)) { /* Screen full? */
749 if (!askmore()) { /* Do more-prompting... */
757 for (i = 0; i < n2; i++)
758 if (s2[i]) free(s2[i]);
759 if (s2) free((char *)s2);
765 Simpler front end for xfilhelp() with shorter arg list when no
766 file selection is needed.
769 filhelp(n,pre,post,off,cmdirflg) int n, off; char *pre, *post; int cmdirflg; {
770 return(xfilhelp(n,pre,post,off,cmdirflg,
771 0,NULL,NULL,NULL,NULL,
772 (CK_OFF_T)0,(CK_OFF_T)0,0,0,(char **)NULL));
775 /* C M S E T U P -- Set up command buffers */
780 if (!(cmdbuf = malloc(CMDBL + 4))) return(-1);
781 if (!(savbuf = malloc(CMDBL + 4))) return(-1);
783 if (!(prevcmd = malloc(CMDBL + 4))) return(-1);
785 if (!(atmbuf = malloc(ATMBL + 4))) return(-1);
786 if (!(atxbuf = malloc(CMDBL + 4))) return(-1);
787 if (!(atybuf = malloc(ATMBL + 4))) return(-1);
788 if (!(filbuf = malloc(ATMBL + 4))) return(-1);
789 if (!(cmprom = malloc(PROMPTL + 4))) return(-1);
790 if (!(cmprxx = malloc(PROMPTL + 4))) return(-1);
793 #endif /* CK_RECALL */
798 /* C M S E T P -- Set the program prompt. */
803 ckstrncpy(cmprxx,s,PROMPTL);
804 psetf = 1; /* Flag that prompt has been set. */
807 /* C M S A V P -- Save a copy of the current prompt. */
811 cmsavp(char s[], int n)
813 cmsavp(s,n) char s[]; int n;
814 #endif /* CK_ANSIC */
816 if (psetf) /* But not if no prompt is set. */
817 ckstrncpy(s,cmprxx,n);
835 /* P R O M P T -- Issue the program prompt. */
838 prompt(f) xx_strp f; {
839 char *sx, *sy; int n;
841 extern int ssl_active_flag, tls_active_flag;
844 extern int display_demo;
846 /* If there is a demo screen to be displayed, display it */
847 if (display_demo && xcmdsrc == 0) {
853 if (psetf == 0) /* If no prompt set, set default. */
856 sx = cmprxx; /* Unevaluated copy */
857 if (f) { /* If conversion function given */
858 sy = cmprom; /* Evaluate it */
860 debug(F101,"prompt sx","",sx);
861 debug(F101,"prompt sy","",sy);
864 if ((*f)(sx,&sy,&n) < 0) /* If evaluation failed */
865 sx = cmprxx; /* revert to unevaluated copy */
866 else if (!*cmprom) /* ditto if it came up empty */
871 ckstrncpy(cmprom,sx,PROMPTL);
872 cmprom[PROMPTL-1] = NUL;
873 if (!*sx) /* Don't print if empty */
883 if (inserver) { /* Print the prompt. */
884 ttoc(CR); /* If TELNET Server */
885 ttoc(NUL); /* must folloW CR by NUL */
891 if (!(ssl_active_flag || tls_active_flag))
893 fflush(stdout); /* Now! */
900 pushcmd(s) char * s; { /* For use with IF command. */
902 ckstrncpy(savbuf,s,CMDBL); /* Save the dependent clause, */
903 cmres(); /* and clear the command buffer. */
904 debug(F110, "pushcmd savbuf", savbuf, 0);
908 pushqcmd(s) char * s; { /* For use with ELSE command. */
909 char c, * p = savbuf; /* Dest */
910 if (!s) s = np; /* Source */
911 while (*s) { /* Get first nonwhitespace char */
917 if (*s != '{') { /* If it's not "{" */
918 pushcmd(s); /* do regular pushcmd */
921 while ((c = *s++)) { /* Otherwise insert quotes */
926 cmres(); /* and clear the command buffer. */
927 debug(F110, "pushqcmd savbuf", savbuf, 0);
932 /* no longer used... */
935 ckstrncpy(cmdbuf,savbuf,CMDBL); /* Put back the saved material */
936 *savbuf = '\0'; /* and clear the save buffer */
941 /* C M R E S -- Reset pointers to beginning of command buffer. */
945 inword = 0; /* We're not in a word */
946 cc = 0; /* Character count is zero */
948 /* Initialize pointers */
950 pp = cmdbuf; /* Beginning of current field */
951 bp = cmdbuf; /* Current position within buffer */
952 np = cmdbuf; /* Where to start next field */
955 cmflgs = -5; /* Parse not yet started. */
956 ungw = 0; /* Don't need to unget a word. */
959 /* C M I N I -- Clear the command and atom buffers, reset pointers. */
962 The argument specifies who is to echo the user's typein --
963 1 means the cmd package echoes
964 0 somebody else (system, front end, terminal) echoes
971 fatal("fatal error: unable to allocate command buffers");
974 memset(cmdbuf,0,CMDBL);
975 memset(atmbuf,0,ATMBL);
977 for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
978 for (bp = atmbuf; bp < atmbuf+ATMBL; bp++) *bp = NUL;
979 #endif /* USE_MEMCPY */
981 *atmbuf = *savbuf = *atxbuf = *atybuf = *filbuf = NUL;
982 blocklvl = 0; /* Block level is 0 */
983 linebegin = 1; /* At the beginning of a line */
984 dpx = d; /* Global copy of the echo flag */
985 debug(F101,"cmini dpx","",dpx);
986 crflag = 0; /* Reset flags */
990 no_recall = 0; /* Start out with recall enabled */
991 #endif /* CK_RECALL */
992 cmres(); /* Sets bp etc */
993 newcmd = 1; /* See addcmd() */
998 The following bits are to allow the command package to call itself
999 in the middle of a parse. To do this, begin by calling cmpush, and
1000 end by calling cmpop. As you can see, this is rather expensive.
1004 int i[5]; /* stack for integers */
1005 char *c[3]; /* stack for pointers */
1006 char *b[8]; /* stack for buffer contents */
1008 struct cmp *cmp = 0;
1010 int cmp_i[CMDDEP+1][5]; /* Stack for integers */
1011 char *cmp_c[CMDDEP+1][5]; /* for misc pointers */
1012 char *cmp_b[CMDDEP+1][7]; /* for buffer contents pointers */
1013 #endif /* DCMDBUF */
1015 int cmddep = -1; /* Current stack depth */
1018 cmpush() { /* Save the command environment */
1019 char *cp; /* Character pointer */
1021 if (cmddep >= CMDDEP) /* Enter a new command depth */
1024 debug(F101,"&cmpush to depth","",cmddep);
1027 /* allocate memory for cmp if not already done */
1028 if (!cmp && !(cmp = (struct cmp *) malloc(sizeof(struct cmp)*(CMDDEP+1))))
1029 fatal("cmpush: no memory for cmp");
1030 cmp[cmddep].i[0] = cmflgs; /* First do the global ints */
1031 cmp[cmddep].i[1] = cmfsav;
1032 cmp[cmddep].i[2] = atxn;
1033 cmp[cmddep].i[3] = ungw;
1035 cmp[cmddep].c[0] = bp; /* Then the global pointers */
1036 cmp[cmddep].c[1] = pp;
1037 cmp[cmddep].c[2] = np;
1039 cmp_i[cmddep][0] = cmflgs; /* First do the global ints */
1040 cmp_i[cmddep][1] = cmfsav;
1041 cmp_i[cmddep][2] = atxn;
1042 cmp_i[cmddep][3] = ungw;
1044 cmp_c[cmddep][0] = bp; /* Then the global pointers */
1045 cmp_c[cmddep][1] = pp;
1046 cmp_c[cmddep][2] = np;
1047 #endif /* DCMDBUF */
1049 /* Now the buffers themselves. A lot of repititious code... */
1052 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1053 if (cp) strcpy(cp,cmdbuf);
1054 cmp[cmddep].b[0] = cp;
1055 if (cp == NULL) return(-1);
1057 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1058 if (cp) strcpy(cp,savbuf);
1059 cmp[cmddep].b[1] = cp;
1060 if (cp == NULL) return(-1);
1062 cmp[cmddep].b[2] = NULL;
1064 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1065 if (cp) strcpy(cp,atmbuf);
1066 cmp[cmddep].b[3] = cp;
1067 if (cp == NULL) return(-1);
1069 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1070 if (cp) strcpy(cp,atxbuf);
1071 cmp[cmddep].b[4] = cp;
1072 if (cp == NULL) return(-1);
1074 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1075 if (cp) strcpy(cp,atybuf);
1076 cmp[cmddep].b[5] = cp;
1077 if (cp == NULL) return(-1);
1079 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1080 if (cp) strcpy(cp,filbuf);
1081 cmp[cmddep].b[6] = cp;
1082 if (cp == NULL) return(-1);
1084 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1085 if (cp) strcpy(cp,cmdbuf);
1086 cmp_b[cmddep][0] = cp;
1087 if (cp == NULL) return(-1);
1089 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1090 if (cp) strcpy(cp,savbuf);
1091 cmp_b[cmddep][1] = cp;
1092 if (cp == NULL) return(-1);
1094 cmp_b[cmddep][2] = NULL;
1096 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1097 if (cp) strcpy(cp,atmbuf);
1098 cmp_b[cmddep][3] = cp;
1099 if (cp == NULL) return(-1);
1101 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1102 if (cp) strcpy(cp,atxbuf);
1103 cmp_b[cmddep][4] = cp;
1104 if (cp == NULL) return(-1);
1106 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1107 if (cp) strcpy(cp,atybuf);
1108 cmp_b[cmddep][5] = cp;
1109 if (cp == NULL) return(-1);
1111 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1112 if (cp) strcpy(cp,filbuf);
1113 cmp_b[cmddep][6] = cp;
1114 if (cp == NULL) return(-1);
1115 #endif /* DCMDBUF */
1117 cmini(dpx); /* Initize the command parser */
1122 cmpop() { /* Restore the command environment */
1124 debug(F100,"&cmpop called from top level","",0);
1125 return(-1); /* Don't pop too much! */
1128 cmflgs = cmp[cmddep].i[0]; /* First do the global ints */
1129 cmfsav = cmp[cmddep].i[1];
1130 atxn = cmp[cmddep].i[2];
1131 ungw = cmp[cmddep].i[3];
1133 bp = cmp[cmddep].c[0]; /* Then the global pointers */
1134 pp = cmp[cmddep].c[1];
1135 np = cmp[cmddep].c[2];
1137 cmflgs = cmp_i[cmddep][0]; /* First do the global ints */
1138 cmfsav = cmp_i[cmddep][1];
1139 atxn = cmp_i[cmddep][2];
1140 ungw = cmp_i[cmddep][3];
1142 bp = cmp_c[cmddep][0]; /* Then the global pointers */
1143 pp = cmp_c[cmddep][1];
1144 np = cmp_c[cmddep][2];
1145 #endif /* DCMDBUF */
1147 /* Now the buffers themselves. */
1148 /* Note: strncpy(), not ckstrncpy() -- Here we WANT the NUL padding... */
1151 if (cmp[cmddep].b[0]) {
1153 strncpy(cmdbuf,cmp[cmddep].b[0],CMDBL); /* 0: Command buffer */
1154 free(cmp[cmddep].b[0]);
1155 cmp[cmddep].b[0] = NULL;
1157 if (cmp[cmddep].b[1]) {
1158 strncpy(savbuf,cmp[cmddep].b[1],CMDBL); /* 1: Save buffer */
1159 free(cmp[cmddep].b[1]);
1160 cmp[cmddep].b[1] = NULL;
1162 if (cmp[cmddep].b[3]) {
1163 strncpy(atmbuf,cmp[cmddep].b[3],ATMBL); /* 3: Atomic buffer! */
1164 free(cmp[cmddep].b[3]);
1165 cmp[cmddep].b[3] = NULL;
1167 if (cmp[cmddep].b[4]) {
1168 strncpy(atxbuf,cmp[cmddep].b[4],ATMBL); /* 4: eXpansion buffer */
1169 free(cmp[cmddep].b[4]);
1170 cmp[cmddep].b[4] = NULL;
1172 if (cmp[cmddep].b[5]) {
1173 strncpy(atybuf,cmp[cmddep].b[5],ATMBL); /* 5: Atom buffer copY */
1174 free(cmp[cmddep].b[5]);
1175 cmp[cmddep].b[5] = NULL;
1177 if (cmp[cmddep].b[6]) {
1178 strncpy(filbuf,cmp[cmddep].b[6],ATMBL); /* 6: Filename buffer */
1179 free(cmp[cmddep].b[6]);
1180 cmp[cmddep].b[6] = NULL;
1183 if (cmp_b[cmddep][0]) {
1184 strncpy(cmdbuf,cmp_b[cmddep][0],CMDBL); /* 0: Command buffer */
1185 free(cmp_b[cmddep][0]);
1186 cmp_b[cmddep][0] = NULL;
1188 if (cmp_b[cmddep][1]) {
1189 strncpy(savbuf,cmp_b[cmddep][1],CMDBL); /* 1: Save buffer */
1190 free(cmp_b[cmddep][1]);
1191 cmp_b[cmddep][1] = NULL;
1193 if (cmp_b[cmddep][3]) {
1194 strncpy(atmbuf,cmp_b[cmddep][3],ATMBL); /* 3: Atomic buffer! */
1195 free(cmp_b[cmddep][3]);
1196 cmp_b[cmddep][3] = NULL;
1198 if (cmp_b[cmddep][4]) {
1199 strncpy(atxbuf,cmp_b[cmddep][4],ATMBL); /* 4: eXpansion buffer */
1200 free(cmp_b[cmddep][4]);
1201 cmp_b[cmddep][4] = NULL;
1203 if (cmp_b[cmddep][5]) {
1204 strncpy(atybuf,cmp_b[cmddep][5],ATMBL); /* 5: Atom buffer copY */
1205 free(cmp_b[cmddep][5]);
1206 cmp_b[cmddep][5] = NULL;
1208 if (cmp_b[cmddep][6]) {
1209 strncpy(filbuf,cmp_b[cmddep][6],ATMBL); /* 6: Filename buffer */
1210 free(cmp_b[cmddep][6]);
1211 cmp_b[cmddep][6] = NULL;
1213 #endif /* DCMDBUF */
1215 cmddep--; /* Rise, rise */
1216 debug(F101,"&cmpop to depth","",cmddep);
1223 stripq(s) char *s; { /* Function to strip '\' quotes */
1227 for (t = s; *t != '\0'; t++) *t = *(t+1);
1232 #endif /* COMMENT */
1234 /* Convert tabs to spaces, one for one */
1238 if (*s == HT) *s = SP;
1243 /* C M N U M -- Parse a number in the indicated radix */
1246 The radix is specified in the arg list.
1247 Parses unquoted numeric strings in the given radix.
1248 Parses backslash-quoted numbers in the radix indicated by the quote:
1249 \nnn = \dnnn = decimal, \onnn = octal, \xnn = Hexadecimal.
1250 If these fail, then if a preprocessing function is supplied, that is applied
1251 and then a second attempt is made to parse an unquoted decimal string.
1252 And if that fails, the preprocessed string is passed to an arithmetic
1253 expression evaluator.
1256 -3 if no input present when required,
1257 -2 if user typed an illegal number,
1258 -1 if reparse needed,
1259 0 otherwise, with argument n set to the number that was parsed
1261 /* This is the traditional cmnum() that gets an int */
1263 cmnum(xhlp,xdef,radix,n,f) char *xhlp, *xdef; int radix, *n; xx_strp f; {
1264 CK_OFF_T z = (CK_OFF_T)0, check;
1266 x = cmnumw(xhlp,xdef,radix,&z,f);
1270 printf("?Magnitude of result too large for integer - %s\n",ckfstoa(z));
1277 This is the new cmnum() that gets a "wide" result, whatever CK_OFF_T
1278 is defined to be, normally 32 or 64 bits, depending on the platform.
1282 cmnumw(xhlp,xdef,radix,n,f)
1283 char *xhlp, *xdef; int radix; CK_OFF_T *n; xx_strp f; {
1284 int x; char *s, *zp, *zq;
1286 char lbrace, rbrace;
1287 #endif /* COMMENT */
1289 if (!xhlp) xhlp = "";
1290 if (!xdef) xdef = "";
1293 if (cmfldflgs & 1) {
1300 #endif /* COMMENT */
1302 if (radix != 10 && radix != 8) { /* Just do bases 8 and 10 */
1303 printf("cmnum: illegal radix - %d\n",radix);
1305 } /* Easy to add others but there has never been a need for it. */
1306 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1307 debug(F101,"cmnum: cmfld","",x);
1308 if (x < 0) return(x); /* Parse a field */
1311 Edit 192 - Allow any number field to be braced. This lets us include
1312 spaces in expressions, but perhaps more important lets us have user-defined
1313 functions in numeric fields.
1315 zp = brstrip(zp); /* Strip braces */
1316 if (cmfldflgs & 1 && *zp == '(') { /* Parens too.. */
1317 x = (int) strlen(atmbuf);
1319 if (*(atmbuf+x-1) == ')') {
1320 *(atmbuf+x-1) = NUL;
1325 if (chknum(zp)) { /* Check for number */
1326 if (radix == 8) { /* If it's supposed to be octal */
1327 zp = ckradix(zp,8,10); /* convert to decimal */
1328 if (!zp) return(-2);
1329 if (!strcmp(zp,"-1")) return(-2);
1331 errno = 0; /* Got one, we're done. */
1337 debug(F101,"cmnum 1st chknum ok","",*n);
1339 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1347 debug(F101,"cmnum xxesc ok","",*n);
1348 return(*zp ? -2 : 0);
1349 } else if (f) { /* If conversion function given */
1350 zq = atxbuf; /* Try that */
1352 if ((*f)(zp,&zq,&atxn) < 0) /* Convert */
1356 debug(F110,"cmnum zp 1",zp,0);
1357 if (!*zp) zp = xdef; /* Result empty, substitute default */
1358 debug(F110,"cmnum zp 2",zp,0);
1359 if (chknum(zp)) { /* Check again for decimal number */
1360 if (radix == 8) { /* If it's supposed to be octal */
1361 zp = ckradix(zp,8,10); /* convert to decimal */
1362 if (!zp) return(-2);
1363 if (!strcmp(zp,"-1")) return(-2);
1371 debug(F101,"cmnum 2nd chknum ok","",*n);
1374 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1380 debug(F101,"cmnum xxesc 2 ok","",*n);
1381 return(*zp ? -2 : 0);
1382 } else if (f) { /* Not numeric, maybe an expression */
1386 if (radix == 8) { /* If it's supposed to be octal */
1387 zp = ckradix(zp,8,10); /* convert to decimal */
1388 if (!zp) return(-2);
1389 if (!strcmp(zp,"-1")) return(-2);
1397 debug(F101,"cmnum exp eval ok","",*n);
1401 } else { /* Not numeric */
1408 #endif /* CKCHANNELIO */
1410 /* C M O F I -- Parse the name of an output file */
1413 Depends on the external function zchko(); if zchko() not available, use
1414 cmfld() to parse output file names.
1417 -9 like -2, except message already printed,
1418 -3 if no input present when required,
1419 -2 if permission would be denied to create the file,
1420 -1 if reparse needed,
1421 0 or 1 if file can be created, with xp pointing to name.
1422 2 if given the name of an existing directory.
1425 cmofi(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1426 int x; char *s, *zq;
1429 #endif /* DOCHKVAR */
1436 if (!xhlp) xhlp = "";
1437 if (!xdef) xdef = "";
1439 if (*xhlp == NUL) xhlp = "Output file";
1442 debug(F110,"cmofi xdef",xdef,0);
1443 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1444 debug(F111,"cmofi cmfld returns",s,x);
1448 s = brstrip(s); /* Strip enclosing braces */
1449 debug(F110,"cmofi 1.5",s,0);
1456 This is really ugly. If we skip conversion the first time through,
1457 then variable names like \%a will be used as filenames (e.g. creating
1458 a file called %A in the root directory). If we DON'T skip conversion
1459 the first time through, then single backslashes used as directory
1460 separators in filenames will be misinterpreted as variable lead-ins.
1461 So we prescan to see if it has any variable references. But this
1462 module is not supposed to know anything about variables, functions,
1463 etc, so this code does not really belong here, but rather it should
1464 be at the same level as zzstring().
1467 Hmmm, this looks a lot like chkvar() except it that includes \nnn number
1468 escapes. But why? This makes commands like "mkdir c:\123" impossible.
1469 And in fact, "mkdir c:\123" creates a directory called "c:{". What's worse,
1470 rmdir(), which *does* call chkvar(), won't let us remove it. So let's at
1471 least try making cmofi() symmetrical with cmifi()...
1475 while ( (tries == 0) && (p = strchr(p,CMDQ)) ) {
1476 q = *(p+1); /* Char after backslash */
1477 if (!q) /* None, quit */
1479 if (isupper(q)) /* If letter, convert to lowercase */
1481 if (isdigit(q)) { /* If it's a digit, */
1482 tries = 1; /* assume it's a backslash code */
1486 case CMDQ: /* Double backslash */
1487 tries = 1; /* so call the conversion function */
1489 case '%': /* Variable or array reference */
1490 case '&': /* must be followed by letter */
1491 if (isalpha(*(p+2)) || (*(p+2) >= '0' && *(p+2) <= '9'))
1494 case 'm': case 'v': case '$': /* \m(), \v(), \$() */
1496 if (strchr(p+2,')'))
1499 case 'f': /* \Fname() */
1500 if (strchr(p+2,'('))
1501 if (strchr(p+2,')'))
1504 case '{': /* \{...} */
1505 if (strchr(p+2,'}'))
1508 case 'd': case 'o': /* Decimal or Octal number */
1509 if (isdigit(*(p+2)))
1512 case 'x': /* Hex number */
1513 if (isdigit(*(p+2)) ||
1514 ((*(p+2) >= 'a' && *(p+2) <= 'f') ||
1515 ((*(p+2) >= 'A' && *(p+2) <= 'F'))))
1524 if (f) { /* If a conversion function is given */
1525 char *s = p; /* See if there are any variables in */
1526 while (*s) { /* the string and if so, expand them */
1535 #endif /* COMMENT */
1541 #endif /* DOCHKVAR */
1542 if (f) { /* If a conversion function is given */
1543 zq = atxbuf; /* do the conversion. */
1545 if ((x = (*f)(s,&zq,&atxn)) < 0)
1548 if (!*s) /* Result empty, substitute default */
1551 debug(F111,"cmofi 2",s,x);
1554 dirp = tilde_expand(s); /* Expand tilde, if any, */
1555 if (*dirp != '\0') { /* right in the atom buffer. */
1556 if (setatm(dirp,1) < 0) {
1557 printf("?Name too long\n");
1562 debug(F110,"cmofi 3",s,0);
1566 printf("?Wildcards not allowed - %s\n",s);
1569 debug(F110,"cmofi 4",s,0);
1572 /* isdir() function required for this! */
1574 debug(F110,"cmofi 5: is directory",s,0);
1578 #endif /* CK_TMPDIR */
1580 if (strcmp(s,CTTNAM) && (zchko(s) < 0)) { /* OK to write to console */
1584 We don't try again because we already prescanned the string to see if
1585 if it contained anything that could be used by zzstring().
1590 #endif /* COMMENT */
1592 Note: there are certain circumstances where zchko() can give a false
1593 positive, so don't rely on it to catch every conceivable situation in
1594 which the given output file can't be created. In other words, we print
1595 a message and fail here if we KNOW the file can't be created. If we
1596 succeed but the file can't be opened, the code that tries to open the file
1597 has to print a message.
1599 debug(F110,"cmofi 6: failure",s,0);
1602 printf("?Off Limits: %s\n",s);
1605 printf("?Write permission denied - %s\n",s);
1608 #endif /* CKCHANNELIO */
1611 debug(F110,"cmofi 7: ok",s,0);
1617 /* C M I F I -- Parse the name of an existing file */
1620 This function depends on the external functions:
1621 zchki() - Check if input file exists and is readable.
1622 zxpand() - Expand a wild file specification into a list.
1623 znext() - Return next file name from list.
1624 If these functions aren't available, then use cmfld() to parse filenames.
1629 -3 if no input present when required,
1630 -2 if file does not exist or is not readable,
1631 -1 if reparse needed,
1632 0 or 1 otherwise, with:
1633 xp pointing to name,
1634 wild = 1 if name contains '*' or '?', 0 otherwise.
1637 #ifdef COMMENT /* This horrible hack has been replaced - see further down */
1639 C M I O F I -- Parse an input file OR the name of a nonexistent file.
1641 Use this when an existing file is wanted (so we get help, completion, etc),
1642 but if a file of the given name does not exist, the name of a new file is
1643 accepted. For example, with the EDIT command (edit an existing file, or
1644 create a new file). Returns -9 if file does not exist. It is up to the
1645 caller to check creatability.
1647 static int nomsg = 0;
1649 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1653 x = cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0);
1657 #endif /* COMMENT */
1660 cmifi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1661 return(cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0));
1664 cmifip() is called when we want to supply a path or path list to search
1665 in case the filename that the user gives is (a) not absolute, and (b) can't
1666 be found as given. The path string can be the name of a single directory,
1667 or a list of directories separated by the PATHSEP character, defined in
1668 ckucmd.h. Look in ckuusr.c and ckuus3.c for examples of usage.
1671 cmifip(xhlp,xdef,xp,wild,d,path,f)
1672 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; {
1673 return(cmifi2(xhlp,xdef,xp,wild,0,path,f,0));
1676 /* C M D I R -- Parse a directory name */
1679 This function depends on the external functions:
1680 isdir(s) - Check if string s is the name of a directory
1681 zchki(s) - Check if input file s exists and what type it is.
1682 If these functions aren't available, then use cmfld() to parse dir names.
1685 -9 For all sorts of reasons, after printing appropriate error message.
1687 -3 if no input present when required,
1688 -2 if out of space or other internal error,
1689 -1 if reparse needed,
1690 0 or 1, with xp pointing to name, if directory specified,
1693 cmdir(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1695 return(cmifi2(xhlp,xdef,xp,&wild,0,NULL,f,1));
1698 /* Like CMDIR but includes PATH search */
1701 cmdirp(xhlp,xdef,xp,path,f) char *xhlp, *xdef, **xp; char * path; xx_strp f; {
1703 return(cmifi2(xhlp,xdef,xp,&wild,0,path,f,1));
1707 cmifi2() is the base filename parser called by cmifi, cmifip, cmdir, etc.
1708 Use it directly when you also want to parse a directory or device
1709 name as an input file, as in the DIRECTORY command. Call with:
1710 xhlp -- help message on ?
1711 xdef -- default response
1712 xp -- pointer to result (in our space, must be copied from here)
1713 wild -- flag set upon return to indicate if filespec was wild
1714 d -- 0 to parse files, 1 to parse files or directories
1715 Add 2 to inhibit following of symlinks.
1716 path -- search path for files
1717 f -- pointer to string processing function (e.g. to evaluate variables)
1718 dirflg -- 1 to parse *only* directories, 0 otherwise
1721 cmifi2(xhlp,xdef,xp,wild,d,path,f,dirflg)
1722 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; int dirflg; {
1723 extern int recursive, diractive, cdactive, dblquo;
1724 int i, x, itsadir, xc, expanded = 0, nfiles = 0, children = -1;
1728 char *sp = NULL, *zq, *np = NULL;
1729 char *sv = NULL, *p = NULL;
1737 /* This large array is dynamic for OS-9 -- should do for others too... */
1738 extern char **mtchs;
1741 /* OK, for UNIX too */
1742 extern char **mtchs;
1745 extern char **mtchs;
1747 extern char *mtchs[];
1752 #endif /* NOPARTIAL */
1754 if (!xhlp) xhlp = "";
1755 if (!xdef) xdef = "";
1758 makestr(&tmplastfile,NULL);
1759 #endif /* NOLASTFILE */
1760 nzxopts = 0; /* zxpand() options */
1761 debug(F101,"cmifi d","",d);
1762 if (d & 2) { /* d & 2 means don't follow symlinks */
1764 nzxopts = ZX_NOLINKS;
1766 debug(F101,"cmifi nzxopts","",nzxopts);
1771 if (path) { /* Make a copy we can poke */
1773 np = (char *) malloc(x + 1);
1779 debug(F110,"cmifi2 path",path,0);
1781 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
1784 inword = 0; /* Initialize counts & pointers */
1787 *xp = ""; /* Pointer to result string */
1788 if ((x = cmflgs) != 1) { /* Already confirmed? */
1791 x = gtword(0); /* No, get a word */
1794 x = gtword(0); /* No, get a word */
1795 #endif /* BS_DIRSEP */
1796 } else { /* If so, use default, if any. */
1797 if (setatm(xdef,1) < 0) {
1798 printf("?Default name too long\n");
1804 *xp = atmbuf; /* Point to result. */
1807 xc += cc; /* Count this character. */
1808 debug(F111,"cmifi gtword",atmbuf,xc);
1809 debug(F101,"cmifi switch x","",x);
1810 switch (x) { /* x = gtword() return code */
1812 if (gtimer() > timelimit) {
1815 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
1816 doexit(GOOD_EXIT,0);
1819 /* if (!quiet) printf("?Timed out\n"); */
1826 printf("Command or field too long\n");
1828 case -2: /* Out of space. */
1829 case -1: /* Reparse needed */
1834 if (xc == 0) /* If no input... */
1835 *xp = xdef; /* substitute the default */
1837 makestr(&tmplastfile,*xp); /* Make a copy before bstripping */
1838 #endif /* #ifndef NOLASTFILE */
1839 *xp = brstrip(*xp); /* Strip braces */
1840 if (**xp == NUL) { /* 12 mar 2001 */
1844 debug(F110,"cmifi brstrip",*xp,0);
1846 if (f) { /* If a conversion function is given */
1848 char *s = *xp; /* See if there are any variables in */
1850 while (*s) { /* the string and if so, expand them */
1852 /* debug(F111,"cmifi chkvar",*xp,x); */
1854 #endif /* DOCHKVAR */
1857 if ((*f)(*xp,&zq,&atxn) < 0) {
1869 #endif /* DOCHKVAR */
1872 if (**xp == NUL) { /* 12 mar 2001 */
1878 dirp = tilde_expand(*xp); /* Expand tilde, if any, */
1879 if (*dirp != '\0') { /* in the atom buffer. */
1880 if (setatm(dirp,1) < 0) {
1881 printf("Expanded name too long\n");
1887 debug(F110,"cmifi tilde_expand",*xp,0);
1890 if (!sv) { /* Only do this once */
1891 sv = malloc((int)strlen(*xp)+1); /* Make a safe copy */
1893 printf("?cmifi: malloc error\n");
1898 debug(F110,"cmifi sv",sv,0);
1901 /* This is to get around "cd /" failing because "too many directories match" */
1903 expanded = 0; /* Didn't call zxpand */
1905 debug(F110,"cmifi isdir 1",*xp,0);
1917 debug(F110,"cmifi isdir 2",*xp,0);
1918 #endif /* datageneral */
1922 if (!strcmp(*xp,"..")) { /* For UNIXers... */
1925 } else if (!strcmp(*xp,".")) {
1931 itsadir = isdir(*xp); /* Is it a directory? */
1932 debug(F111,"cmifi itsadir",*xp,itsadir);
1934 /* If they said "blah" where "blah.dir" is a directory... */
1935 /* change it to [.blah]. */
1938 int flag = 0; char c, * p;
1940 while ((c = *p++) && !flag)
1941 if (ckstrchr(".[]:*?<>",c))
1943 debug(F111,"cmifi VMS dirname flag",*xp,flag);
1945 ckmakmsg(tmpbuf,TMPBUFSIZ,"[.",*xp,"]",NULL);
1946 itsadir = isdir(tmpbuf);
1951 debug(F111,"cmifi VMS dirname flag itsadir",*xp,itsadir);
1953 } else if (itsadir == 1 && *(xp[0]) == '.' && *(xp[1])) {
1955 if (p = malloc(cc + 4)) {
1956 ckmakmsg(p,cc+4,"[",*xp,"]",NULL);
1959 debug(F110,"cmdir .foo",*xp,0);
1962 } else if (itsadir == 2 && !diractive) {
1963 int x; /* [FOO]BAR.DIR instead of [FOO.BAR] */
1967 x = cvtdir(*xp,p,ATMBL); /* Convert to [FOO.BAR] */
1971 debug(F110,"cmdir cvtdir",*xp,0);
1978 debug(F101,"cmifi dirflg","",dirflg);
1979 if (dirflg) { /* Parsing a directory name? */
1980 /* Yes, does it contain wildcards? */
1982 (diractive && (!strcmp(*xp,".") || !strcmp(*xp,"..")))
1984 nzxopts |= ZX_DIRONLY; /* Match only directory names */
1985 if (matchdot) nzxopts |= ZX_MATCHDOT;
1986 if (recursive) nzxopts |= ZX_RECURSE;
1987 debug(F111,"cmifi nzxopts 2",*xp,nzxopts);
1988 y = nzxpand(*xp,nzxopts);
1989 debug(F111,"cmifi nzxpand 2",*xp,y);
1995 This is to allow (e.g.) "cd foo", where FOO.DIR;1 is in the
1998 debug(F111,"cmdir itsadir",*xp,itsadir);
2006 *s != '[' && s[n-1] != ']' &&
2007 *s != '<' && s[n-1] != '>' &&
2009 ckindex("[",s,0,0,1) == 0 &&
2010 ckindex("<",s,0,0,1) == 0 &&
2011 #endif /* COMMENT */
2013 char * dirbuf = NULL;
2014 dirbuf = (char *)malloc(n+4);
2017 ckmakmsg(dirbuf,n+4,"[",s,"]",NULL);
2019 ckmakmsg(dirbuf,n+4,"[.",s,"]",NULL);
2020 itsadir = isdir(dirbuf);
2021 debug(F111,"cmdir dirbuf",dirbuf,itsadir);
2025 debug(F110,"cmdir new *xp",*xp,0);
2030 /* This is to allow CDPATH to work in VMS... */
2033 char * p; int i, j, k, d;
2035 if (p = malloc(x + 8)) {
2036 ckstrncpy(p,*xp,x+8);
2037 i = ckindex(".",p,-1,1,1);
2038 d = ckindex(".dir",p,0,0,0);
2039 j = ckindex("]",p,-1,1,1);
2041 j = ckindex(">",p,-1,1,1);
2044 k = ckindex(":",p,-1,1,1);
2045 if (i < j || i < k) i = 0;
2046 if (d < j || d < k) d = 0;
2047 /* Change [FOO]BAR or [FOO]BAR.DIR */
2049 if (j > 0 && j < n) {
2051 if (d > 0) p[d-1] = NUL;
2052 ckstrncat(p,rb,x+8);
2053 debug(F110,"cmdir xxx",p,0);
2056 debug(F111,"cmdir p",p,itsadir);
2060 debug(F110,"cmdir new *xp",*xp,0);
2067 y = (!itsadir) ? 0 : 1;
2068 debug(F111,"cmifi y itsadir",*xp,y);
2070 } else { /* Parsing a filename. */
2071 debug(F110,"cmifi *xp pre-zxpand",*xp,0);
2073 nzxopts |= (d == 0) ? ZX_FILONLY : 0; /* So always expand. */
2074 if (matchdot) nzxopts |= ZX_MATCHDOT;
2075 if (recursive) nzxopts |= ZX_RECURSE;
2076 y = nzxpand(*xp,nzxopts);
2078 /* Here we're trying to fix a problem in which a directory name is accepted */
2079 /* as a filename, but this breaks too many other things. */
2082 if (itsadir & !iswild(*xp)) {
2083 debug(F100,"cmifi dir when filonly","",0);
2084 printf("?Not a regular file: \"%s\"\n",*xp);
2089 nzxopts |= ZX_FILONLY;
2090 if (matchdot) nzxopts |= ZX_MATCHDOT;
2091 if (recursive) nzxopts |= ZX_RECURSE;
2092 y = nzxpand(*xp,nzxopts);
2095 #endif /* COMMENT */
2097 debug(F111,"cmifi y nzxpand",*xp,y);
2098 debug(F111,"cmifi y atmbuf",atmbuf,itsadir);
2101 /* domydir() calls zxrewind() so we MUST call nzxpand() here */
2102 if (!expanded && diractive) {
2103 debug(F110,"cmifi diractive catch-all zxpand",*xp,0);
2104 nzxopts |= (d == 0) ? ZX_FILONLY : (dirflg ? ZX_DIRONLY : 0);
2105 if (matchdot) nzxopts |= ZX_MATCHDOT;
2106 if (recursive) nzxopts |= ZX_RECURSE;
2107 y = nzxpand(*xp,nzxopts);
2108 debug(F111,"cmifi diractive nzxpand",*xp,y);
2112 *wild = (iswild(sv) || (y > 1)) && (itsadir == 0);
2115 if (!*wild) *wild = recursive;
2116 #endif /* RECURSIVE */
2118 debug(F111,"cmifi sv wild",sv,*wild);
2119 debug(F101,"cmifi y","",y);
2120 if (dirflg && *wild && cdactive) {
2122 printf("?Wildcard matches more than one directory\n");
2130 if (itsadir && d && !dirflg) { /* It's a directory and not wild */
2131 if (sv) free(sv); /* and it's ok to parse directories */
2134 makestr(&lastfile,tmplastfile);
2135 #endif /* NOLASTFILE */
2138 if (y == 0) { /* File was not found */
2140 dosearch = (path != NULL); /* A search path was given */
2142 dosearch = hasnopath(sv); /* Filename includes no path */
2143 debug(F111,"cmifip hasnopath",sv,dosearch);
2145 if (dosearch) { /* Search the path... */
2150 if (c == PATHSEP || c == NUL) {
2157 /* By definition of CDPATH, an empty member denotes the current directory */
2159 ckstrncpy(atmbuf,".",ATMBL);
2162 ckstrncpy(atmbuf,path,ATMBL);
2164 atmbuf[ATMBL] = NUL;
2165 /* If we have a logical name, evaluate it recursively */
2166 if (*(ptr-1) == ':') { /* Logical name ends in : */
2168 while (((n = strlen(atmbuf)) > 0) &&
2169 atmbuf[n-1] == ':') {
2171 for (p = atmbuf; *p; p++)
2172 if (islower(*p)) *p = toupper(*p);
2173 debug(F111,"cmdir CDPATH LN 1",atmbuf,n);
2175 debug(F110,"cmdir CDPATH LN 2",p,0);
2178 strncpy(atmbuf,p,ATMBL);
2179 atmbuf[ATMBL] = NUL;
2184 if (*(ptr-1) != '\\' && *(ptr-1) != '/')
2185 ckstrncat(atmbuf,"\\",ATMBL);
2188 if (*(ptr-1) != '/')
2189 ckstrncat(atmbuf,"/",ATMBL);
2192 if (*(ptr-1) != ':')
2193 ckstrncat(atmbuf,":",ATMBL);
2194 #endif /* datageneral */
2198 ckstrncat(atmbuf,sv,ATMBL);
2199 debug(F110,"cmifip add path",atmbuf,0);
2200 if (c == PATHSEP) ptr++;
2209 xc = (int) strlen(atmbuf);
2221 printf("?Off Limits: %s\n",sv);
2225 printf("?No %s match - %s\n",
2226 dirflg ? "directories" : "files", sv);
2235 printf("?Off Limits: %s\n",sv);
2238 printf("?Too many %s match - %s\n",
2239 dirflg ? "directories" : "files", sv);
2243 } else if (*wild || y > 1) {
2247 makestr(&lastfile,tmplastfile);
2248 #endif /* NOLASTFILE */
2252 /* If not wild, see if it exists and is readable. */
2254 debug(F111,"cmifi sv not wild",sv,*wild);
2256 znext(*xp); /* Get first (only?) matching file */
2257 if (dirflg) /* Maybe wild and expanded */
2258 itsadir = isdir(*xp); /* so do this again. */
2259 filesize = dirflg ? itsadir : zchki(*xp); /* Check accessibility */
2262 nfiles = zxrewind(); /* Rewind so next znext() gets 1st */
2265 nzxopts |= dirflg ? ZX_DIRONLY : 0;
2266 if (matchdot) nzxopts |= ZX_MATCHDOT;
2267 if (recursive) nzxopts |= ZX_RECURSE;
2268 nfiles = nzxpand(*xp,nzxopts);
2269 #endif /* ZXREWIND */
2271 debug(F111,"cmifi nfiles",*xp,nfiles);
2272 debug(F101,"cmifi filesize","",filesize);
2273 free(sv); /* done with this */
2275 if (dirflg && !filesize) {
2276 printf("?Not a directory - %s\n",*xp);
2279 #endif /* CKCHANNELIO */
2281 } else if (filesize == (CK_OFF_T)-3) {
2284 /* Don't show filename if we're not allowed to see it */
2285 printf("?Read permission denied\n");
2287 printf("?Read permission denied - %s\n",*xp);
2292 #endif /* CKCHANNELIO */
2293 return(xcmfdb ? -6 : -9);
2294 } else if (filesize == (CK_OFF_T)-2) {
2299 makestr(&lastfile,tmplastfile);
2300 #endif /* NOLASTFILE */
2304 printf("?File not readable - %s\n",*xp);
2307 #endif /* CKCHANNELIO */
2308 return(xcmfdb ? -6 : -9);
2310 } else if (filesize < (CK_OFF_T)0) {
2312 if (!nomsg && !xcmfdb)
2313 printf("?File not found - %s\n",*xp);
2316 #endif /* CKCHANNELIO */
2317 return(xcmfdb ? -6 : -9);
2321 makestr(&lastfile,tmplastfile);
2322 #endif /* NOLASTFILE */
2327 debug(F101,"cmifi esc, xc","",xc);
2330 printf("%s ",xdef); /* If at beginning of field */
2334 inword = cmflgs = 0;
2335 addbuf(xdef); /* Supply default. */
2336 if (setatm(xdef,0) < 0) {
2337 printf("Default name too long\n");
2341 } else { /* No default */
2346 if (**xp == '{') { /* Did user type opening brace... */
2351 } else if (dblquo && **xp == '"') { /* or doublequote? */
2352 *xp = *xp + 1; /* If so ignore it and space past it */
2358 if (f) { /* If a conversion function is given */
2360 char *s = *xp; /* See if there are any variables in */
2361 while (*s) { /* the string and if so, expand it. */
2363 #endif /* DOCHKVAR */
2366 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2371 /* reduce cc by number of \\ consumed by conversion */
2372 /* function (needed for OS/2, where \ is path separator) */
2373 cc -= (strlen(*xp) - strlen(atxbuf));
2374 #endif /* DOCHKVAR */
2376 if (!atxbuf[0]) { /* Result empty, use default */
2385 #endif /* DOCHKVAR */
2390 if (dirflg && *(*xp) == '~') {
2391 debug(F111,"cmifi tilde_expand A",*xp,cc);
2392 dirp = tilde_expand(*xp); /* Expand tilde, if any... */
2393 if (!dirp) dirp = "";
2397 xc = cc; /* Length of ~thing */
2398 xx = setatm(dirp,0); /* Copy expansion to atom buffer */
2399 debug(F111,"cmifi tilde_expand B",atmbuf,cc);
2401 printf("Expanded name too long\n");
2405 debug(F111,"cmifi tilde_expand xc","",xc);
2406 for (i = 0; i < xc; i++) {
2407 cmdchardel(); /* Back up over ~thing */
2410 xc = cc; /* How many new ones we just got */
2412 printf("%s",sp); /* Print them */
2413 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2414 bp--; /* Back up over NUL */
2423 if (!strcmp(atmbuf,"..")) {
2425 ckstrncat(cmdbuf," ",CMDBL);
2431 } else if (!strcmp(atmbuf,".")) {
2436 /* This patches a glitch when user types "./foo<ESC>" */
2437 /* in which the next two chars are omitted from the */
2438 /* expansion. There should be a better fix, however, */
2439 /* since there is no problem with "../foo<ESC>". */
2441 if (*p == '.' && *(p+1) == '/')
2444 #endif /* UNIXOROSK */
2447 *sp++ = '+'; /* Data General AOS wildcard */
2449 *sp++ = '*'; /* Others */
2450 #endif /* datageneral */
2453 if (!strchr(*xp, '.')) /* abde.e -> abcde.e* */
2454 strcat(*xp, ".*"); /* abc -> abc*.* */
2456 /* Add wildcard and expand list. */
2458 /* This kills partial completion when ESC given in path segment */
2459 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2462 #endif /* COMMENT */
2463 if (matchdot) nzxopts |= ZX_MATCHDOT;
2464 if (recursive) nzxopts |= ZX_RECURSE;
2465 y = nzxpand(*xp,nzxopts);
2467 debug(F111,"cmifi nzxpand",*xp,y);
2470 znext(filbuf); /* Get first */
2472 zxrewind(); /* Must "rewind" */
2474 nzxpand(*xp,nxzopts);
2475 #endif /* ZXREWIND */
2477 ckstrncpy(filbuf,mtchs[0],CKMAXPATH);
2481 filbuf[CKMAXPATH] = NUL;
2482 *sp = '\0'; /* Remove wildcard. */
2483 debug(F111,"cmifi filbuf",filbuf,y);
2484 debug(F111,"cmifi *xp",*xp,cc);
2491 printf("?Off Limits: %s\n",atmbuf);
2494 printf("?No %s match - %s\n",
2495 dirflg ? "directories" : "files", atmbuf);
2506 printf("?Off Limits: %s\n",atmbuf);
2509 printf("?Too many %s match - %s\n",
2510 dirflg ? "directories" : "files", atmbuf);
2513 } else if (y > 1 /* Not unique */
2515 || (y == 1 && isdir(filbuf)) /* Unique directory */
2519 /* Partial filename completion */
2522 debug(F111,"cmifi partial",filbuf,cc);
2528 min = strlen(filbuf),
2530 char localfn[CKMAXPATH+1];
2533 for (j = 1; j <= y; j++) {
2535 if (dirflg && !isdir(localfn))
2538 len2 = strlen(localfn);
2540 cur < len && cur < len2 && cur <= min;
2543 /* OS/2 or Windows, case doesn't matter */
2544 if (tolower(filbuf[cur]) != tolower(localfn[cur]))
2557 for (i = cc; (c = filbuf[i]); i++) {
2558 for (j = 1; j < y; j++)
2559 if (mtchs[j][i] != c) break;
2561 else filbuf[i] = filbuf[i+1] = NUL;
2567 /* isdir() function required for this! */
2568 if (y == 1 && isdir(filbuf)) { /* Dont we already know this? */
2570 len = strlen(filbuf);
2571 if (len > 0 && len < ATMBL - 1) {
2572 if (filbuf[len-1] != dirsep) {
2573 filbuf[len] = dirsep;
2574 filbuf[len+1] = NUL;
2578 At this point, before just doing partial completion, we should look first to
2579 see if the given directory does indeed have any subdirectories (dirflg) or
2580 files (!dirflg); if it doesn't we should do full completion. Otherwise, the
2581 result looks funny to the user and "?" blows up the command for no good
2586 filbuf[len+1] = '*';
2587 filbuf[len+2] = NUL;
2588 if (dirflg) flags = ZX_DIRONLY;
2589 children = nzxpand(filbuf,flags);
2590 debug(F111,"cmifi children",filbuf,children);
2591 filbuf[len+1] = NUL;
2592 nzxpand(filbuf,flags); /* Restore previous list */
2599 /* Add doublequotes if there are spaces in the name */
2603 x = (qflag == '}'); /* (or braces) */
2607 if (filbuf[0] != '"' && filbuf[0] != '{')
2608 k = dquote(filbuf,ATMBL,x);
2611 debug(F111,"cmifi REPAINT filbuf",filbuf,k);
2612 if (k > 0) { /* Got more characters */
2613 debug(F101,"cmifi REPAINT cc","",cc);
2614 debug(F101,"cmifi REPAINT xc","",xc);
2615 debug(F110,"cmifi REPAINT bp-cc",bp-cc,0);
2616 debug(F110,"cmifi REPAINT bp-xc",bp-xc,0);
2617 sp = filbuf + cc; /* Point to new ones */
2618 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2622 for (i = 0; i < x; i++) {
2623 cmdchardel(); /* Back up over old partial spec */
2626 sp = filbuf; /* Point to new word start */
2627 debug(F110,"cmifi erase ok",sp,0);
2629 cc = k; /* How many new ones we just got */
2630 printf("%s",sp); /* Print them */
2631 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2632 bp--; /* Back up over NUL */
2633 debug(F110,"cmifi partial cmdbuf",cmdbuf,0);
2634 if (setatm(filbuf,0) < 0) {
2635 printf("?Partial name too long\n");
2639 debug(F111,"cmifi partial atmbuf",atmbuf,cc);
2642 #endif /* NOPARTIAL */
2644 } else { /* Unique, complete it. */
2647 /* isdir() function required for this! */
2649 debug(F111,"cmifi unique",filbuf,children);
2650 if (isdir(filbuf) && children > 0) {
2652 len = strlen(filbuf);
2653 if (len > 0 && len < ATMBL - 1) {
2654 if (filbuf[len-1] != dirsep) {
2655 filbuf[len] = dirsep;
2656 filbuf[len+1] = NUL;
2663 while ((*bp++ = *sp++)) ;
2665 if (setatm(filbuf,0) < 0) {
2666 printf("?Directory name too long\n");
2670 debug(F111,"cmifi directory atmbuf",atmbuf,cc);
2672 } else { /* Not a directory or dirflg */
2673 #endif /* CK_TMPDIR */
2675 #ifndef VMS /* VMS dir names are special */
2676 #ifndef datageneral /* VS dirnames must not end in ":" */
2679 len = strlen(filbuf);
2680 if (len > 0 && len < ATMBL - 1) {
2681 if (filbuf[len-1] != dirsep) {
2682 filbuf[len] = dirsep;
2683 filbuf[len+1] = NUL;
2687 #endif /* datageneral */
2689 sp = filbuf + cc; /* Point past what user typed. */
2697 if (filbuf[0] != '"' && filbuf[0] != '{')
2698 dquote(filbuf,ATMBL,x);
2700 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2704 for (i = 0; i < x; i++) {
2705 cmdchardel(); /* Back up over old partial spec */
2708 sp = filbuf; /* Point to new word start */
2709 debug(F111,"cmifi after erase sp=",sp,cc);
2711 printf("%s ",sp); /* Print the completed name. */
2715 addbuf(sp); /* Add the characters to cmdbuf. */
2716 if (setatm(filbuf,0) < 0) { /* And to atmbuf. */
2717 printf("?Completed name too long\n");
2721 inword = cmflgs = 0;
2722 *xp = brstrip(atmbuf); /* Return pointer to atmbuf. */
2723 if (dirflg && !isdir(*xp)) {
2724 printf("?Not a directory - %s\n", filbuf);
2730 makestr(&lastfile,tmplastfile);
2731 #endif /* NOLASTFILE */
2736 #endif /* CK_TMPDIR */
2741 case 3: /* Question mark - file menu wanted */
2743 printf(dirflg ? " Directory name" : " Input file specification");
2749 /* If user typed an opening quote or brace, just skip past it */
2751 if (**xp == '"' || **xp == '{') {
2757 if (f) { /* If a conversion function is given */
2759 char *s = *xp; /* See if there are any variables in */
2760 while (*s) { /* the string and if so, expand them */
2762 #endif /* DOCHKVAR */
2765 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2770 /* reduce cc by number of \\ consumed by conversion */
2771 /* function (needed for OS/2, where \ is path separator) */
2772 cc -= (strlen(*xp) - strlen(atxbuf));
2773 #endif /* DOCHKVAR */
2780 #endif /* DOCHKVAR */
2783 debug(F111,"cmifi ? *xp, cc",*xp,cc);
2784 sp = *xp + cc; /* Insert "*" at end */
2786 *sp++ = '+'; /* Insert +, the DG wild card */
2789 #endif /* datageneral */
2792 if (! strchr(*xp, '.')) /* abde.e -> abcde.e* */
2793 strcat(*xp, ".*"); /* abc -> abc*.* */
2795 debug(F110,"cmifi ? wild",*xp,0);
2797 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2799 debug(F101,"cmifi matchdot","",matchdot);
2800 if (matchdot) nzxopts |= ZX_MATCHDOT;
2801 if (recursive) nzxopts |= ZX_RECURSE;
2802 y = nzxpand(*xp,nzxopts);
2807 printf(": %s\n",atmbuf);
2808 printf("%s%s",cmprom,cmdbuf);
2815 printf("?Off Limits: %s\n",atmbuf);
2818 printf("?No %s match - %s\n",
2819 dirflg ? "directories" : "files", atmbuf);
2826 printf("?Off Limits: %s\n",atmbuf);
2829 printf("?Too many %s match - %s\n",
2830 dirflg ? "directories" : "files", atmbuf);
2834 printf(", one of the following:\n");
2835 if (filhelp((int)y,"","",1,dirflg) < 0) {
2840 printf("%s%s",cmprom,cmdbuf);
2847 x = gtword(0); /* No, get a word */
2850 x = gtword(0); /* No, get a word */
2851 #endif /* BS_DIRSEP */
2856 /* C M F L D -- Parse an arbitrary field */
2859 -3 if no input present when required,
2860 -2 if field too big for buffer,
2861 -1 if reparse needed,
2862 0 otherwise, xp pointing to string result.
2864 NOTE: Global flag keepallchars says whether this routine should break on CR
2865 or LF: needed for MINPUT targets and DECLARE initializers, where we want to
2866 keep control characters if the user specifies them (March 2003). It might
2867 have been better to change the calling sequence but that was not practical.
2870 cmfld(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
2874 inword = 0; /* Initialize counts & pointers */
2879 debug(F110,"cmfld xdef 1",xdef,0);
2881 if (!xhlp) xhlp = "";
2882 if (!xdef) xdef = "";
2883 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2886 debug(F111,"cmfld xdef 2",xdef,cmflgs);
2887 debug(F111,"cmfld atmbuf 1",atmbuf,xc);
2889 if ((x = cmflgs) != 1) { /* Already confirmed? */
2890 x = gtword(0); /* No, get a word */
2892 if (setatm(xdef,0) < 0) { /* If so, use default, if any. */
2893 printf("?Default too long\n");
2897 *xp = atmbuf; /* Point to result. */
2898 debug(F111,"cmfld atmbuf 2",atmbuf,cmflgs);
2901 xc += cc; /* Count the characters. */
2902 debug(F111,"cmfld gtword",atmbuf,xc);
2903 debug(F101,"cmfld x","",x);
2906 printf("Command or field too long\n");
2908 case -3: /* Empty. */
2909 case -2: /* Out of space. */
2910 case -1: /* Reparse needed */
2914 debug(F111,"cmfld 1",atmbuf,xc);
2915 if (xc == 0) { /* If no input, return default. */
2916 if (setatm(xdef,0) < 0) {
2917 printf("?Default too long\n");
2921 *xp = atmbuf; /* Point to what we got. */
2922 debug(F111,"cmfld 2",atmbuf,((f) ? 1 : 0));
2923 if (f) { /* If a conversion function is given */
2924 zq = atxbuf; /* employ it now. */
2926 if ((*f)(*xp,&zq,&atxn) < 0)
2928 debug(F111,"cmfld 3",atxbuf,xc);
2929 /* Replace by new value -- for MINPUT only keep all chars */
2930 if (setatm(atxbuf,keepallchars ? 3:1) < 0) { /* 16 Mar 2003 */
2931 printf("Value too long\n");
2936 debug(F111,"cmfld 4",atmbuf,xc);
2937 if (**xp == NUL) { /* If variable evaluates to null */
2938 if (setatm(xdef,0) < 0) {
2939 printf("?Default too long\n");
2942 if (**xp == NUL) x = -3; /* If still empty, return -3. */
2944 debug(F111,"cmfld returns",*xp,x);
2947 if (xc == 0 && *xdef) {
2948 printf("%s ",xdef); /* If at beginning of field, */
2952 addbuf(xdef); /* Supply default. */
2953 inword = cmflgs = 0;
2954 if (setatm(xdef,0) < 0) {
2955 printf("?Default too long\n");
2957 } else /* Return as if whole field */
2958 return(0); /* typed, followed by space. */
2963 case 3: /* Question mark */
2964 debug(F110,"cmfld QUESTIONMARK",cmdbuf,0);
2966 printf(" Please complete this field");
2969 printf("\n%s%s",cmprom,cmdbuf);
2973 debug(F111,"cmfld gtword A x",cmdbuf,x);
2975 debug(F111,"cmfld gtword B x",cmdbuf,x);
2980 /* C M T X T -- Get a text string, including confirmation */
2983 Print help message 'xhlp' if ? typed, supply default 'xdef' if null
2984 string typed. Returns:
2986 -1 if reparse needed or buffer overflows.
2989 with cmflgs set to return code, and xp pointing to result string.
2992 cmtxt(xhlp,xdef,xp,f) char *xhlp; char *xdef; char **xp; xx_strp f; {
2998 if (!xhlp) xhlp = "";
2999 if (!xdef) xdef = "";
3005 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
3008 debug(F101,"cmtxt cmflgs","",cmflgs);
3009 inword = 0; /* Start atmbuf counter off at 0 */
3011 if (cmflgs == -1) { /* If reparsing, */
3013 xc = (int)strlen(*xp); /* get back the total text length, */
3014 bp = *xp; /* and back up the pointers. */
3017 } else { /* otherwise, */
3018 /* debug(F100,"cmtxt: fresh start","",0); */
3019 *xp = ""; /* start fresh. */
3022 *atmbuf = NUL; /* And empty the atom buffer. */
3023 rtimer(); /* Reset timer */
3024 if ((x = cmflgs) != 1) {
3027 x = gtword(0); /* Get first word. */
3028 *xp = pp; /* Save pointer to it. */
3029 /* debug(F111,"cmtxt:",*xp,cc); */
3031 if (gtimer() > timelimit) {
3032 /* if (!quiet) printf("?Timed out\n"); */
3039 while (1) { /* Loop for each word in text. */
3040 xc += cc; /* Char count for all words. */
3041 /* debug(F111,"cmtxt gtword",atmbuf,xc); */
3042 /* debug(F101,"cmtxt x","",x); */
3045 if (gtimer() > timelimit) {
3047 extern int inserver;
3049 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3050 doexit(GOOD_EXIT,0);
3053 /* if (!quiet) printf("?Timed out\n"); */
3059 case -9: /* Buffer overflow */
3060 printf("Command or field too long\n");
3063 case -3: /* Quit/Timeout */
3065 case -2: /* Overflow */
3066 case -1: /* Deletion */
3069 xc++; /* Just count it */
3071 case 1: /* CR or LF */
3072 if (xc == 0) *xp = xdef;
3073 if (f) { /* If a conversion function is given */
3075 zq = atxbuf; /* Point to the expansion buffer */
3076 atxn = CMDBL; /* specify its length */
3077 /* debug(F111,"cmtxt calling (*f)",*xp,atxbuf); */
3078 if ((x = (*f)(*xp,&zq,&atxn)) < 0) return(-2);
3082 while (*sx++) cc++; /* (faster than calling strlen) */
3084 cc = (int)strlen(atxbuf);
3085 #endif /* COMMENT */
3086 /* Should be equal to (CMDBL - atxn) but isn't always. */
3088 if (cc < 1) { /* Nothing in expansion buffer? */
3089 *xp = xdef; /* Point to default string instead. */
3092 while (*sx++) cc++; /* (faster than calling strlen) */
3095 #endif /* COMMENT */
3096 } else { /* Expansion function got something */
3097 *xp = atxbuf; /* return pointer to it. */
3099 debug(F111,"cmtxt (*f)",*xp,cc);
3100 } else { /* No expansion function */
3102 /* Avoid a strlen() call */
3107 /* NO! xc is apparently not always set appropriately */
3109 #endif /* COMMENT */
3113 /* strlen() no longer needed */
3114 for (i = (int)strlen(xx) - 1; i > 0; i--)
3116 for (i = cc - 1; i > 0; i--)
3117 #endif /* COMMENT */
3118 if (xx[i] != SP) /* Trim trailing blanks */
3124 if (xc == 0) { /* Nothing typed yet */
3125 if (*xdef) { /* Have a default for this field? */
3126 printf("%s ",xdef); /* Yes, supply it */
3127 inword = cmflgs = 0;
3132 } else bleep(BP_WARN); /* No default */
3133 } else { /* Already in field */
3136 if (ckstrcmp(atmbuf,xdef,x,0)) { /* Matches default? */
3137 bleep(BP_WARN); /* No */
3138 } else if ((int)strlen(xdef) > x) { /* Yes */
3145 inword = cmflgs = 0;
3146 debug(F110,"cmtxt: addbuf",cmdbuf,0);
3152 case 3: /* Question Mark */
3154 printf(" Text string");
3157 printf("\n%s%s",cmprom,cmdbuf);
3161 printf("?Unexpected return code from gtword() - %d\n",x);
3168 /* C M K E Y -- Parse a keyword */
3172 table -- keyword table, in 'struct keytab' format;
3173 n -- number of entries in table;
3174 xhlp -- pointer to help string;
3175 xdef -- pointer to default keyword;
3176 f -- string preprocessing function (e.g. to evaluate variables)
3177 pmsg -- 0 = don't print error messages
3178 1 = print error messages
3179 2 = include CM_HLP keywords even if invisible
3181 4 = parse a switch (keyword possibly ending in : or =)
3182 8 = don't strip comments (used, e.g., for "help #")
3184 -3 -- no input supplied and no default available
3185 -2 -- input doesn't uniquely match a keyword in the table
3186 -1 -- user deleted too much, command reparse required
3187 n >= 0 -- value associated with keyword
3191 Front ends for cmkey2():
3192 cmkey() - The normal keyword parser
3193 cmkeyx() - Like cmkey() but suppresses error messages
3194 cmswi() - Switch parser
3197 cmkey(table,n,xhlp,xdef,f)
3198 /* cmkey */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3199 return(cmkey2(table,n,xhlp,xdef,"",f,1));
3202 cmkeyx(table,n,xhlp,xdef,f)
3203 /* cmkeyx */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3204 return(cmkey2(table,n,xhlp,xdef,"",f,0));
3207 cmswi(table,n,xhlp,xdef,f)
3208 /* cmswi */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3209 return(cmkey2(table,n,xhlp,xdef,"",f,4));
3213 cmkey2(table,n,xhlp,xdef,tok,f,pmsg)
3214 struct keytab table[];
3221 extern int havetoken;
3222 int i, tl, y, z = 0, zz, xc, wordlen = 0, cmswitch;
3225 if (!xhlp) xhlp = "";
3226 if (!xdef) xdef = "";
3230 printf("?Keyword table missing\n");
3233 tl = (int)strlen(tok);
3235 inword = xc = cc = 0; /* Clear character counters. */
3236 cmswitch = pmsg & 4; /* Flag for parsing a switch */
3238 debug(F101,"cmkey: pmsg","",pmsg);
3239 debug(F101,"cmkey: cmflgs","",cmflgs);
3240 debug(F101,"cmkey: cmswitch","",cmswitch);
3241 /* debug(F101,"cmkey: cmdbuf","",cmdbuf);*/
3245 if ((zz = cmflgs) == 1) { /* Command already entered? */
3246 if (setatm(xdef,0) < 0) { /* Yes, copy default into atom buf */
3247 printf("?Default too long\n");
3250 rtimer(); /* Reset timer */
3251 } else { /* Otherwise get a command word */
3252 rtimer(); /* Reset timer */
3253 if (pmsg & 8) /* 8 is for parsing HELP tokens */
3256 zz = gtword((pmsg == 4) ? 1 : 0);
3259 debug(F101,"cmkey table length","",n);
3260 debug(F101,"cmkey cmflgs","",cmflgs);
3261 debug(F101,"cmkey cc","",cc);
3265 debug(F111,"cmkey gtword xc",atmbuf,xc);
3266 debug(F101,"cmkey gtword zz","",zz);
3269 case -10: /* Timeout */
3270 if (gtimer() < timelimit) {
3271 if (pmsg & 8) /* 8 is for parsing HELP tokens */
3274 zz = gtword((pmsg == 4) ? 1 : 0);
3278 extern int inserver;
3280 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3281 doexit(GOOD_EXIT,0);
3289 printf("Command or field too long\n");
3291 case -3: /* Null Command/Quit/Timeout */
3292 case -2: /* Buffer overflow */
3293 case -1: /* Or user did some deleting. */
3294 return(cmflgs = zz);
3298 case 0: /* User terminated word with space */
3299 case 4: /* or switch ending in : or = */
3300 wordlen = cc; /* Length if no conversion */
3301 if (cc == 0) { /* Supply default if we got nothing */
3302 if ((wordlen = setatm(xdef,(zz == 4) ? 2 : 0)) < 0) {
3303 printf("?Default too long\n");
3307 if (zz == 1 && cc == 0) /* Required field missing */
3310 if (f) { /* If a conversion function is given */
3312 zq = atxbuf; /* apply it */
3315 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3316 debug(F110,"cmkey atxbuf after *f",atxbuf,0);
3317 if (!*p2) /* Supply default if we got nothing */
3319 ckstrncpy(ppvnambuf,atmbuf,PPVLEN);
3320 if ((wordlen = setatm(p2,(zz == 4) ? 2 : 0)) < 0) {
3321 printf("Evaluated keyword too long\n");
3326 This bit lets us save more than one "word".
3327 For example, "define \%x echo one two three", "\%x".
3328 It works too, but it breaks labels, and therefore
3329 WHILE and FOR loops, etc.
3331 if (p2[wordlen] >= SP) {
3333 while (*p2 == SP) p2++;
3341 #ifdef COMMENT /* ^^^ */
3342 if (cmswitch && *atmbuf != '/') {
3345 printf("?Not a switch - %s\n",atmbuf);
3350 #endif /* COMMENT */
3353 for (i = 0; i < wordlen; i++) {
3354 if (atmbuf[i] == ':' || atmbuf[i] == '=') {
3355 brkchar = atmbuf[i];
3363 /* This was an effective optimization but it breaks sometimes on labels. */
3364 if (tl && !isalpha(atmbuf[0])) { /* Precheck for token */
3365 for (i = 0; i < tl; i++) { /* Save function call to ckstrchr */
3366 if (tok[i] == atmbuf[0]) {
3367 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3368 ungword(); /* Put back the following word */
3369 return(-5); /* Special return code for token */
3373 #endif /* TOKPRECHECK */
3375 y = lookup(table,atmbuf,n,&z); /* Look up word in the table */
3376 debug(F111,"cmkey lookup",atmbuf,y);
3377 debug(F101,"cmkey zz","",zz);
3378 debug(F101,"cmkey cmflgs","",cmflgs);
3379 debug(F101,"cmkey crflag","",crflag);
3381 case -3: /* Nothing to look up */
3383 case -2: /* Ambiguous */
3387 printf("?Ambiguous - %s\n",atmbuf);
3391 case -1: /* Not found at all */
3394 for (i = 0; i < tl; i++) /* Check for token */
3395 if (tok[i] == *atmbuf) { /* Got one */
3396 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3397 ungword(); /* Put back the following word */
3398 return(-5); /* Special return code for token */
3401 #endif /* TOKPRECHECK */
3403 if (tl == 0) { /* No tokens were included */
3405 /* In OS/2 and Windows, allow for a disk letter like DOS */
3406 if (isalpha(*atmbuf) && *(atmbuf+1) == ':')
3409 if ((pmsg & 1) && !quiet) {
3411 printf("?No keywords match - %s\n",atmbuf); /* cmkey */
3413 return(cmflgs = -9);
3415 if (cmflgs == 1 || cmswitch) /* cmkey2 or cmswi */
3416 return(cmflgs = -6);
3418 return(cmflgs = -2);
3419 /* The -6 code is to let caller try another table */
3424 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3425 #endif /* CK_RECALL */
3428 cmkwflgs = table[z].flgs;
3433 case 2: /* User terminated word with ESC */
3434 debug(F101,"cmkey Esc cc","",cc);
3436 if (*xdef != NUL) { /* Nothing in atmbuf */
3437 printf("%s ",xdef); /* Supply default if any */
3442 if (setatm(xdef,0) < 0) {
3443 printf("?Default too long\n");
3446 inword = cmflgs = 0;
3447 debug(F111,"cmkey: default",atmbuf,cc);
3449 debug(F101,"cmkey Esc pmsg","",0);
3452 Chained FDBs... The idea is that this function might not have a default,
3453 but the next one might. But if it doesn't, there is no way to come back to
3454 this one. To be revisited later...
3456 if (xcmfdb) /* Chained fdb -- try next one */
3458 #endif /* COMMENT */
3459 if (pmsg & (1|4)) { /* So for now just beep */
3465 if (f) { /* If a conversion function is given */
3467 zq = atxbuf; /* apply it */
3470 if ((*f)(atmbuf,&zq,&atxn) < 0)
3474 if (setatm(pp,0) < 0) {
3475 printf("Evaluated keyword too long\n");
3479 y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
3480 debug(F111,"cmkey lookup y",atmbuf,y);
3481 debug(F111,"cmkey lookup z",atmbuf,z);
3482 if (y == -2 && z >= 0 && z < n) { /* Ambiguous */
3484 int j, k, len = 9999; /* Do partial completion */
3485 /* Skip past any abbreviations in the table */
3486 for ( ; z < n; z++) {
3487 if ((table[z].flgs & CM_ABR) == 0)
3489 if (!(table[z].flgs & CM_HLP) || (pmsg & 2))
3492 debug(F111,"cmkey partial z",atmbuf,z);
3493 debug(F111,"cmkey partial n",atmbuf,n);
3494 for (j = z+1; j < n; j++) {
3495 debug(F111,"cmkey partial j",table[j].kwd,j);
3496 if (ckstrcmp(atmbuf,table[j].kwd,cc,0))
3498 if (table[j].flgs & CM_ABR)
3500 if ((table[j].flgs & CM_HLP) && !(pmsg & 2))
3502 k = ckstrpre(table[z].kwd,table[j].kwd);
3503 debug(F111,"cmkey partial k",table[z].kwd,k);
3505 len = k; /* Length of longest common prefix */
3507 debug(F111,"cmkey partial len",table[z].kwd,len);
3508 if (len != 9999 && len > cc) {
3509 ckstrncat(atmbuf,table[z].kwd+cc,ATMBL);
3511 printf("%s",atmbuf+cc);
3512 ckstrncat(cmdbuf,atmbuf+cc,CMDBL);
3516 #endif /* NOPARTIAL */
3519 } else if (y == -3) {
3522 } else if (y == -1) { /* Not found */
3523 if ((pmsg & 1) && !quiet) {
3525 printf("?No keywords match - \"%s\"\n",atmbuf);
3531 If we found it, but it's a help-only keyword and the "help" bit is not
3532 set in pmsg, then not found.
3534 debug(F101,"cmkey flgs","",table[z].flgs);
3535 if (test(table[z].flgs,CM_HLP) && ((pmsg & 2) == 0)) {
3536 if ((pmsg & 1) && !quiet) {
3538 printf("?No keywords match - %s\n",atmbuf);
3544 See if the keyword just found has the CM_ABR bit set in its flgs field, and
3545 if so, search forwards in the table for a keyword that has the same kwval
3546 but does not have CM_ABR (or CM_INV?) set, and then expand using the full
3547 keyword. WARNING: This assumes that (a) keywords are in alphabetical order,
3548 and (b) the CM_ABR bit is set only if the the abbreviated keyword is a true
3549 abbreviation (left substring) of the full keyword.
3551 if (test(table[z].flgs,CM_ABR)) {
3553 for (zz = z+1; zz < n; zz++)
3554 if ((table[zz].kwval == table[z].kwval) &&
3555 (!test(table[zz].flgs,CM_ABR)) &&
3556 (!test(table[zz].flgs,CM_INV))) {
3561 xp = table[z].kwd + cc;
3562 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3575 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3576 #endif /* CK_RECALL */
3577 cmkwflgs = table[z].flgs;
3582 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3583 bp--; /* Replace trailing space with : */
3595 debug(F110,"cmkey: addbuf",cmdbuf,0);
3598 case 3: /* User typed "?" */
3599 if (f) { /* If a conversion function is given */
3601 zq = atxbuf; /* do the conversion now. */
3604 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3605 if (setatm(pp,0) < 0) {
3606 printf("?Evaluated keyword too long\n");
3610 y = lookup(table,atmbuf,n,&z); /* Look up what we have so far. */
3613 Strictly speaking if the main keyword table search fails,
3614 then we should look in the token table if one is given.
3615 But in practice, tokens are also included in the main
3619 if ((pmsg & 1) && !quiet) {
3621 printf(" No keywords match\n");
3627 /* This is to allow ?-help to work immediately after a token */
3628 /* without having to type an intermediate space */
3630 for (i = 0; i < tl; i++) /* Check for token */
3631 if (tok[i] == *atmbuf) { /* Got one */
3632 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3633 ungword(); /* Put back the following word */
3634 cmflgs = 3; /* Force help next time around */
3635 return(-5); /* Special return code for token */
3638 #endif /* COMMENT */
3641 printf(" One of the following:\n");
3643 printf(" %s, one of the following:\n",xhlp);
3646 x = pmsg & (2|4); /* See kwdhelp() comments */
3647 if (atmbuf[0]) /* If not at beginning of field */
3648 x |= 1; /* also show invisibles */
3649 kwdhelp(table,n,atmbuf,"","",1,x);
3654 if (tl > 0 && topcmd != XXHLP) /* This is bad... */
3655 printf("or a macro name (\"do ?\" for a list) ");
3658 if (*atmbuf == NUL && !havetoken) {
3660 printf("or the token %c\n",*tok);
3662 printf("or one of the tokens: %s\n",ckspread(tok));
3664 printf("%s%s", cmprom, cmdbuf);
3669 printf("\n%d - Unexpected return code from gtword\n",zz);
3670 return(cmflgs = -2);
3672 zz = (pmsg & 8) ? gtword(4) : gtword((pmsg == 4) ? 1 : 0);
3673 debug(F111,"cmkey gtword zz",atmbuf,zz);
3678 chktok(tlist) char *tlist; {
3681 while (*p != NUL && *p != *atmbuf) p++;
3682 return((*p) ? (int) *p : 0);
3685 /* Routines for parsing and converting dates and times */
3687 #define isdatesep(c) (ckstrchr(" -/._",c))
3689 #define CMDATEBUF 1024
3690 char cmdatebuf[CMDATEBUF+4] = { NUL, NUL };
3691 static char * cmdatebp = cmdatebuf;
3692 char * cmdatemsg = NULL;
3694 static struct keytab timeunits[] = {
3695 { "days", TU_DAYS, 0 },
3696 { "months", TU_MONTHS, 0 },
3697 { "weeks", TU_WEEKS, 0 },
3698 { "wks", TU_WEEKS, 0 },
3699 { "years", TU_YEARS, 0 },
3700 { "yrs", TU_YEARS, 0 }
3702 static int nunits = (sizeof(timeunits) / sizeof(struct keytab));
3709 static struct keytab symdaytab[] = {
3710 { "now", SYM_NOW, 0 },
3711 { "today", SYM_TODA, 0 },
3712 { "tomorrow", SYM_TOMO, 0 },
3713 { "yesterday", SYM_YEST, 0 }
3715 static int nsymdays = (sizeof(symdaytab) / sizeof(struct keytab));
3717 static struct keytab daysofweek[] = {
3720 { "Saturday", 6, 0 },
3722 { "Thursday", 4, 0 },
3723 { "Tuesday", 2, 0 },
3724 { "Wednesday", 3, 0 }
3727 static struct keytab usatz[] = { /* RFC 822 timezones */
3728 { "cdt", 5, 0 }, /* Values are GMT offsets */
3740 static int nusatz = (sizeof(usatz) / sizeof(struct keytab));
3743 /* C M C V T D A T E -- Converts free-form date to standard form. */
3747 s = pointer to free-format date, time, or date and time.
3748 t = 0: return time only if time was given in s.
3749 t = 1: always return time (00:00:00 if no time given in s).
3750 t = 2: allow time to be > 24:00:00.
3753 Pointer to "yyyymmdd hh:mm:ss" (local date-time) on success.
3757 Before final release the following long lines should be wrapped.
3758 Until then we leave them long since wrapping them wrecks EMACS's
3762 /* asctime pattern */
3763 static char * atp1 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9]";
3765 /* asctime pattern with timezone */
3766 static char * atp2 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [A-Z][A-Z][A-Z] [0-9][0-9][0-9][0-9]";
3768 #define DATEBUFLEN 127
3771 #define isleap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
3772 static int mdays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
3780 #define DELTABUF 256
3781 static char deltabuf[DELTABUF];
3782 static char * deltabp = deltabuf;
3785 cmdelta(yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss)
3786 int yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss;
3788 int zyy, zmo, zdd, zhh, zmm, zss;
3789 long t1, t2, t3, t4;
3790 long d1 = 0, d2, d3;
3791 char datebuf[DATEBUFLEN+1];
3795 debug(F101,"cmdelta yy","",yy);
3796 debug(F101,"cmdelta mo","",mo);
3797 debug(F101,"cmdelta dd","",dd);
3798 debug(F101,"cmdelta hh","",hh);
3799 debug(F101,"cmdelta mm","",mm);
3800 debug(F101,"cmdelta ss","",ss);
3801 debug(F101,"cmdelta sign","",sign);
3802 debug(F101,"cmdelta dyy","",dyy);
3803 debug(F101,"cmdelta dmo","",dmo);
3804 debug(F101,"cmdelta ddd","",ddd);
3805 debug(F101,"cmdelta dhh","",dhh);
3806 debug(F101,"cmdelta dmm","",dmm);
3807 debug(F101,"cmdelta dss","",dss);
3811 if (yy < 0 || yy > 9999) {
3812 makestr(&cmdatemsg,"Base year out of range");
3813 debug(F111,"cmdelta",cmdatemsg,-1);
3816 if (mo < 1 || mo > 12) {
3817 makestr(&cmdatemsg,"Base month out of range");
3818 debug(F111,"cmdelta",cmdatemsg,-1);
3821 if (dd < 1 || dd > mdays[mo]) {
3822 makestr(&cmdatemsg,"Base day out of range");
3823 debug(F111,"cmdelta",cmdatemsg,-1);
3826 if (hh < 0 || hh > 23) {
3827 makestr(&cmdatemsg,"Base hour out of range");
3828 debug(F111,"cmdelta",cmdatemsg,-1);
3831 if (mm < 0 || mm > 59) {
3832 makestr(&cmdatemsg,"Base minute out of range");
3833 debug(F111,"cmdelta",cmdatemsg,-1);
3836 if (ss < 0 || ss > 60) {
3837 makestr(&cmdatemsg,"Base second out of range");
3838 debug(F111,"cmdelta",cmdatemsg,-1);
3841 sign = (sign < 0) ? -1 : 1;
3849 } else if (sign < 0) {
3858 mo = 12 - (dmo - mo);
3864 if (yy > 9999 || yy < 0) {
3865 makestr(&cmdatemsg,"Result year out of range");
3866 debug(F111,"cmdelta",cmdatemsg,-1);
3870 sprintf(datebuf,"%04d%02d%02d %02d:%02d:%02d",yy,mo,dd,hh,mm,ss);
3872 debug(F111,"cmdelta mjd",datebuf,d1);
3874 t1 = hh * 3600 + mm * 60 + ss; /* Base time to secs since midnight */
3875 t2 = dhh * 3600 + dmm * 60 + dss; /* Delta time, ditto */
3876 t3 = t1 + (sign * t2); /* Get sum (or difference) */
3878 d2 = (sign * ddd); /* Delta days */
3881 t4 = t3 % 86400L; /* Fractional part of day */
3882 if (t4 < 0) { /* If negative */
3883 d2--; /* one less delta day */
3884 t4 += 86400L; /* get positive seconds */
3886 hh = (int) (t4 / 3600L);
3887 mm = (int) (t4 % 3600L) / 60;
3888 ss = (int) (t4 % 3600L) % 60;
3890 sprintf(datebuf,"%s %02d:%02d:%02d", mjd2date(d1+d2),hh,mm,ss);
3894 len = strlen(datebuf);
3895 k = deltabp - (char *)deltabuf; /* Space used */
3896 n = DELTABUF - k - 1; /* Space left */
3897 if (n < len) { /* Not enough? */
3898 deltabp = deltabuf; /* Wrap around */
3901 ckstrncpy(deltabp,datebuf,n);
3909 /* Convert Delta Time to Seconds */
3912 delta2sec(s,result) char * s; long * result; {
3913 long ddays = 0L, zz;
3914 int dsign = 1, dhours = 0, dmins = 0, dsecs = 0, units;
3915 int state = NEED_DAYS;
3916 char *p, *p2, *p3, c = 0;
3922 if ((int)strlen(s) > 63)
3924 ckstrncpy(buf,s,64);
3927 if (*p != '+' && *p != '-')
3932 while (*p == SP) /* Skip intervening spaces */
3935 while (state) { /* FSA to parse delta time */
3936 if (state < 0 || !isdigit(*p))
3938 p2 = p; /* Get next numeric field */
3939 while (isdigit(*p2))
3941 c = *p2; /* And break character */
3942 *p2 = NUL; /* Terminate the number */
3943 switch (state) { /* Interpret according to state */
3944 case NEED_DAYS: /* Initial */
3945 if ((c == '-') || /* VMS format */
3946 ((c == 'd' || c == 'D')
3947 && !isalpha(*(p2+1)))) { /* Days */
3951 else /* if anything is left */
3952 state = NEED_HRS; /* now we want hours. */
3953 } else if (c == ':') { /* delimiter is colon */
3954 dhours = atoi(p); /* so it's hours */
3955 state = NEED_MINS; /* now we want minutes */
3956 } else if (!c) { /* end of string */
3957 dhours = atoi(p); /* it's still hours */
3958 state = 0; /* and we're done */
3959 } else if (isalpha(c) || c == SP) {
3960 if (c == SP) { /* It's a keyword? */
3961 p2++; /* Skip spaces */
3964 } else { /* or replace first letter */
3967 p3 = p2; /* p2 points to beginning of keyword */
3968 while (isalpha(*p3)) /* Find end of keyword */
3970 c = *p3; /* NUL it out so we can look it up */
3971 if (*p3) /* p3 points to keyword terminator */
3973 if ((units = lookup(timeunits,p2,nunits,NULL)) < 0)
3975 *p2 = NUL; /* Re-terminate the number */
3977 while (*p3 == SP) /* Point at field after units */
3992 } else { /* Anything else */
3993 state = -1; /* is an error */
3996 case NEED_HRS: /* Looking for hours */
4007 case NEED_MINS: /* Looking for minutes */
4018 case NEED_SECS: /* Looking for seconds */
4029 case NEED_FRAC: /* Fraction of second */
4030 if (!c && rdigits(p)) {
4039 if (c) /* next field if any */
4045 /* if days > 24854 and sizeof(long) == 32 we overflow */
4047 zz = ddays * 86400L;
4048 if (zz < 0L) /* This catches it */
4050 zz += dhours * 3600L + dmins * 60L + dsecs;
4058 cmcvtdate(s,t) char * s; int t; {
4059 int x, i, j, k, hh, mm, ss, ff, pmflag = 0, nodate = 0, len, dow;
4060 int units, isgmt = 0, gmtsign = 0, d = 0, state = 0, nday;
4061 int kn = 0, ft[8], isletter = 0, f2len = 0;
4063 int zhh = 0; /* Timezone adjustments */
4067 int dsign = 1; /* Delta-time adjustments */
4076 char * fld[8], * p = "", * p2, * p3; /* Assorted buffers and pointers */
4078 char * year = NULL, * month = NULL, * day = NULL;
4079 char * hour = "00", * min = "00", * sec = "00";
4082 char xbuf[DATEBUFLEN+1];
4083 char ybuf[DATEBUFLEN+1];
4084 char zbuf[DATEBUFLEN+1];
4085 char yyyymmdd[YYYYMMDD];
4090 char timbuf[16], *tb, cc;
4091 char * dp = NULL; /* Result pointer */
4096 while (*s == SP) s++; /* Gobble any leading blanks */
4097 if (isalpha(*s)) /* Remember if 1st char is a letter */
4101 debug(F110,"cmcvtdate",s,len);
4102 if (len == 0) { /* No arg - return current date-time */
4106 if (len > DATEBUFLEN) { /* Check length of arg */
4107 makestr(&cmdatemsg,"Date-time string too long");
4108 debug(F111,"cmcvtdate",cmdatemsg,-1);
4111 hh = 0; /* Init time to 00:00:00.0 */
4118 if (*p) { /* Init time to current time */
4119 x = ckstrncpy(dbuf,p,26);
4121 hh = atoi(&dbuf[11]);
4122 mm = atoi(&dbuf[14]);
4123 ss = atoi(&dbuf[17]);
4126 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD); /* Init date to current date */
4127 ckstrncpy(yearbuf,yyyymmdd,5);
4128 ckstrncpy(monbuf,&yyyymmdd[4],3);
4129 ckstrncpy(daybuf,&yyyymmdd[6],3);
4133 nday = atoi(daybuf);
4134 ckstrncpy(xbuf,s,DATEBUFLEN); /* Make a local copy we can poke */
4135 s = xbuf; /* Point to it */
4141 /* Special preset formats... */
4143 if (len >= 14) { /* FTP MDTM all-numeric date */
4145 c = s[14]; /* e.g. 19980615100045.014 */
4150 ckstrncpy(yyyymmdd,s,8+1);
4156 x = 0; /* Becomes > 0 for asctime format */
4157 if (isalpha(s[0])) {
4158 if (len == 24) { /* Asctime format? */
4159 /* Sat Jul 14 15:57:32 2001 */
4160 x = ckmatch(atp1,s,0,0);
4161 debug(F111,"cmcvtdate asctime",s,x);
4162 } else if (len == 28) { /* Or Asctime plus timezone? */
4163 /* Sat Jul 14 15:15:39 EDT 2001 */
4164 x = ckmatch(atp2,s,0,0);
4165 debug(F111,"cmcvtdate asctime+timezone",s,x);
4168 if (x > 0) { /* Asctime format */
4170 strncpy(yearbuf,s + len - 4,4);
4172 for (i = 0; i < 3; i++)
4175 if ((xx = lookup(cmonths,tmpbuf,12,NULL)) < 0) {
4176 makestr(&cmdatemsg,"Invalid month");
4177 debug(F111,"cmcvtdate",cmdatemsg,-1);
4180 debug(F101,"cmcvtdate asctime month","",xx);
4181 monbuf[0] = (xx / 10) + '0';
4182 monbuf[1] = (xx % 10) + '0';
4184 daybuf[0] = (s[8] == ' ' ? '0' : s[8]);
4188 for (i = 11; i < 19; i++)
4191 ckmakmsg(zbuf,18,yearbuf,monbuf,daybuf,xbuf);
4192 debug(F110,"cmcvtdate asctime ok",zbuf,0);
4198 n = ckmakmsg(ybuf,DATEBUFLEN-4,zbuf," ",NULL,NULL);
4203 ckstrncpy(xbuf,ybuf,DATEBUFLEN);
4209 /* Check for day of week */
4212 while (*p == SP) p++;
4218 if (*p2 == ',' || *p2 == SP || !*p2) {
4219 cc = *p2; /* Save break char */
4220 *p2 = NUL; /* NUL it out */
4221 p3 = p2; /* Remember this spot */
4222 if ((dow = lookup(daysofweek,p,7,NULL)) > -1) {
4223 debug(F111,"cmcvtdate dow",p,dow);
4225 if (cc == ',' || cc == SP) { /* Point to next field */
4227 while (*s == SP) s++;
4230 debug(F111,"cmcvtdate dow new p",p,dow);
4232 } else if (isalpha(*p) && cc == ',') {
4233 makestr(&cmdatemsg,"Unrecognized day of week");
4234 debug(F111,"cmcvtdate",cmdatemsg,-1);
4244 len = strlen(s); /* Update length */
4245 debug(F111,"cmcvtdate s",s,len);
4247 debug(F111,"cmcvtdate dow",s,dow);
4248 if (dow > -1) { /* Have a day-of-week number */
4250 zz = mjd(zzndate()); /* Get today's MJD */
4251 debug(F111,"cmcvtdate zz","",zz);
4252 j = (((int)(zz % 7L)) + 3) % 7; /* Today's day-of-week number */
4253 debug(F111,"cmcvtdate j","",j);
4254 hh = 0; /* Init time to midnight */
4258 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD);
4261 n = dow - j; /* Days from now */
4264 if (n < 0) n += 7; /* Add to MJD */
4266 ckstrncpy(yyyymmdd,mjd2date(zz),YYYYMMDD); /* New date */
4269 debug(F111,"cmcvtdate A",yyyymmdd,len);
4270 if (len == 0) { /* No more fields after this */
4271 ckmakmsg(zbuf,18,yyyymmdd," 00:00:00",NULL,NULL);
4276 if (rdigits(p) && len < 8) /* Next field is time? */
4277 goto dotime; /* If so go straight to time section */
4281 else if (isdigit(*(p+1)) && (*(p+2) == ':'))
4285 debug(F111,"cmcvtdate B s",s,dow);
4286 debug(F111,"cmcvtdate B p",p,dow);
4288 if (*s == '+' || *s == '-') { /* Delta time only - skip ahead. */
4294 What is the purpose of this? It breaks parsing of email dates like
4295 "Wed, 13 Feb 2002 17:43:02 -0800 (PST)". Removing this code fixes the
4296 problem and Kermit still passes the 'dates' script.
4297 - fdc, Sat Nov 26 10:52:45 2005.
4300 /* Day of week given followed by something that is not a time */
4301 /* or a delta so it can't be valid */
4302 makestr(&cmdatemsg,"Invalid tokens after day of week");
4303 debug(F111,"cmcvtdate fail",cmdatemsg,-1);
4306 #endif /* COMMENT */
4308 /* Handle "today", "yesterday", "tomorrow", and +/- n units */
4310 if (ckstrchr("TtYyNn",s[0])) {
4311 int i, k, n, minus = 0;
4315 debug(F111,"cmcvtdate mjd",s,jd);
4317 /* Symbolic date: TODAY, TOMORROW, etc...? */
4319 s2 = s; /* Find end of keyword */
4321 while (isalpha(*s2)) { /* and get its length */
4325 c = *s2; /* Zap but save delimiter */
4327 k = lookup(symdaytab,s,nsymdays,NULL); /* Look up keyword */
4328 *s2 = c; /* Replace delimiter */
4329 if (k < 0) /* Keyword not found */
4332 while (*s3 == SP) /* Skip whitespace */
4334 if (*s3 == '_' || *s3 == ':')
4337 switch (k) { /* Have keyword */
4338 case SYM_NOW: /* NOW */
4339 ckstrncpy(ybuf,ckdate(),DATEBUFLEN);
4340 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4342 if (*s3) { /* No overwriting current time. */
4343 ckstrncat(ybuf," ",DATEBUFLEN);
4344 ckstrncat(ybuf,s3,DATEBUFLEN);
4347 default: /* Yesterday, Today, and Tomorrow */
4348 if (k == SYM_TOMO) { /* TOMORROW */
4349 strncpy(ybuf,mjd2date(jd+1),8);
4350 } else if (k == SYM_YEST) { /* YESTERDAY */
4351 strncpy(ybuf,mjd2date(jd-1),8);
4352 } else { /* TODAY */
4353 strncpy(ybuf,ckdate(),8);
4355 strncpy(ybuf+8," 00:00:00",DATEBUFLEN-8); /* Default time is 0 */
4356 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4358 if (*s3) { /* If something follows keyword... */
4359 if (isdigit(*s3)) { /* Time - overwrite default time */
4360 strncpy(ybuf+8,s+i,DATEBUFLEN-8);
4361 } else { /* Something else, keep default time */
4362 ckstrncat(ybuf," ",DATEBUFLEN); /* and append */
4363 ckstrncat(ybuf,s3,DATEBUFLEN); /* whatever we have */
4367 s = ybuf; /* Point to rewritten date-time */
4368 len = strlen(s); /* Update length */
4369 isletter = 0; /* Cancel this */
4372 /* Regular free-format non-symbolic date */
4376 debug(F111,"cmcvtdate NORMAL",s,len);
4377 debug(F111,"cmcvtdate dow",s,dow);
4378 if (yyyymmdd[0] && !year) {
4379 ckstrncpy(yearbuf,yyyymmdd,5);
4380 ckstrncpy(monbuf,&yyyymmdd[4],3);
4381 ckstrncpy(daybuf,&yyyymmdd[6],3);
4385 nday = atoi(daybuf);
4387 if (isdigit(s[0])) { /* Time without date? */
4390 debug(F111,"cmcvtdate NORMAL X1",s,len);
4392 } else if (len > 1 && isdigit(s[1]) && s[2] == ':') {
4393 debug(F111,"cmcvtdate NORMAL X2",s,len);
4395 } else if (rdigits(s) && len < 8) {
4396 debug(F111,"cmcvtdate NORMAL X3",s,len);
4400 if (len >= 8 && isdigit(*s)) { /* Check first for yyyymmdd* */
4401 debug(F111,"cmcvtdate NORMAL A",s,len);
4403 s[8] = NUL; /* Isolate first 8 characters */
4405 /* Have valid time separator? */
4406 p2 = cc ? ckstrchr(" Tt_-:",cc) : NULL;
4408 ckstrncpy(yyyymmdd,s,YYYYMMDD); /* Valid separator */
4410 s += 8; /* or time not given */
4411 if (cc) s++; /* Keep date */
4412 p = s; /* and go handle time */
4416 makestr(&cmdatemsg,"Numeric date too long");
4418 makestr(&cmdatemsg,"Invalid date-time separator");
4419 debug(F111,"cmcvtdate",cmdatemsg,-1);
4423 s[8] = cc; /* Put this back! */
4425 debug(F111,"cmcvtdate NORMAL non-yyyymmdd",s,len);
4427 /* Free-format date -- figure it out */
4430 if (*s && !isdigit(*s)) {
4431 makestr(&cmdatemsg,"Unrecognized word in date");
4432 debug(F111,"cmcvtdate",cmdatemsg,-1);
4435 #endif /* COMMENT */
4436 for (i = 0; i < 8; i++) /* Field types */
4438 fld[i = 0] = (p = s); /* First field */
4439 while (*p) { /* Get next two fields */
4440 if (isdatesep(*p)) { /* Have a date separator */
4443 } else if (i == 1 && *p != datesep) {
4444 makestr(&cmdatemsg,"Inconsistent date separators");
4445 debug(F111,"cmcvtdate",cmdatemsg,-1);
4448 *p++ = NUL; /* Replace by NUL */
4449 if (*p) { /* Now we're at the next field */
4450 while (*p == SP) p++; /* Skip leading spaces */
4451 if (!*p) break; /* Make sure we still have something */
4452 if (i == 2) /* Last one? */
4454 fld[++i] = p; /* No, record pointer to this one */
4458 } else if ((*p == 'T' || *p == 't') && isdigit(*(p+1))) { /* Time */
4461 } else if (*p == ':') {
4462 if (i == 0 && p == s) {
4465 } else if (i != 0) { /* After a date */
4466 if (i == 2) { /* OK as date-time separator (VMS) */
4471 makestr(&cmdatemsg,"Too few fields in date");
4473 makestr(&cmdatemsg,"Misplaced time separator");
4474 debug(F111,"cmcvtdate",cmdatemsg,-1);
4477 nodate = 1; /* Or without a date */
4482 if (p > s && i == 0) /* Make sure we have a date */
4483 nodate = 1; /* No date. */
4485 if (nodate && dow > -1) { /* Have implied date from DOW? */
4486 goto dotime; /* Use, use that, go do time. */
4488 } else if (nodate) { /* No date and no implied date */
4489 char *tmp = NULL; /* Substitute today's date */
4494 makestr(&cmdatemsg,"Problem supplying current date");
4495 debug(F111,"cmcvtdate",cmdatemsg,-1);
4498 ckstrncpy(dbuf,tmp,26); /* Reformat */
4499 if (dbuf[8] == SP) dbuf[8] = '0';
4500 fld[0] = dbuf+8; /* dd */
4502 fld[1] = dbuf+4; /* mmm */
4504 fld[2] = dbuf+20; /* yyyy */
4506 hh = atoi(&dbuf[11]);
4507 mm = atoi(&dbuf[14]);
4508 ss = atoi(&dbuf[17]);
4509 p = s; /* Back up source pointer to reparse */
4511 makestr(&cmdatemsg,"Too few fields in date");
4512 debug(F111,"cmcvtdate",cmdatemsg,-1);
4515 /* Have three date fields - see what they are */
4517 for (k = 0, j = 0; j < 3; j++) { /* Get number of non-numeric fields */
4518 ft[j] = rdigits(fld[j]);
4519 debug(F111,"cmcvtdate fld",fld[j],j);
4523 kn = k; /* How many numeric fields */
4524 month = NULL; /* Strike out default values */
4528 if (k == 2 && ft[2] > 0) { /* Jul 20, 2001 */
4530 xx = strlen(fld[1]);
4532 if (xx > 0) if (p3[xx-1] == ',') {
4537 } else p3[xx-1] = ',';
4540 if (k > 1) { /* We can have only one non-numeric */
4542 makestr(&cmdatemsg,"Unrecognized word in date");
4543 else if (!ft[2] && isdigit(*(fld[2])))
4544 makestr(&cmdatemsg,"Invalid date-time separator");
4546 makestr(&cmdatemsg,"Too many non-numeric fields in date");
4547 debug(F111,"cmcvtdate",cmdatemsg,-1);
4552 } else if (!ft[1]) {
4554 } else if (!ft[2]) {
4555 makestr(&cmdatemsg,"Non-digit in third date field");
4556 debug(F111,"cmcvtdate",cmdatemsg,-1);
4562 if ((x = lookup(cmonths,fld[k],12,NULL)) < 0) {
4563 makestr(&cmdatemsg,"Unknown month");
4564 debug(F111,"cmcvtdate",cmdatemsg,-1);
4567 sprintf(tmpbuf,"%02d",x);
4570 f2len = strlen(fld[2]); /* Length of 3rd field */
4572 if (k == 0) { /* monthname dd, yyyy */
4575 } else if (((int)strlen(fld[0]) == 4)) { /* yyyy-xx-dd */
4579 month = fld[1]; /* yyyy-mm-dd */
4580 } else if (f2len == 4) { /* xx-xx-yyyy */
4582 if (month) { /* dd-name-yyyy */
4584 } else { /* xx-xx-yyyy */
4588 if (((f0 > 12) && (f1 <= 12)) || (f1 <= 12 && f0 == f1)) {
4589 day = fld[0]; /* mm-dd-yyyy */
4591 } else if ((f0 <= 12) && (f1 > 12)) {
4592 if (!rdigits(fld[1])) {
4593 makestr(&cmdatemsg,"Day not numeric");
4594 debug(F111,"cmcvtdate",cmdatemsg,-1);
4597 day = fld[1]; /* dd-mm-yyyy */
4602 makestr(&cmdatemsg,"Day or month out of range");
4604 makestr(&cmdatemsg,"Day and month are ambiguous");
4605 debug(F111,"cmcvtdate",cmdatemsg,-1);
4609 } else if ((f2len < 4) && /* dd mmm yy (RFC822) */
4610 !rdigits(fld[1]) && /* middle field is monthname */
4615 makestr(&cmdatemsg,"Too few digits in year");
4616 debug(F111,"cmcvtdate",cmdatemsg,-1);
4619 tmpyear = atoi(fld[2]);
4620 if (tmpyear < 50) /* RFC 2822 windowing */
4622 else /* This includes 3-digit years. */
4624 year = ckitoa(tmpyear);
4626 } else if ((f2len < 4) && (k < 0) && ((int)strlen(fld[0]) < 4)) {
4627 makestr(&cmdatemsg,"Ambiguous numeric date");
4628 debug(F111,"cmcvtdate",cmdatemsg,-1);
4630 } else if ((f2len > 4) && ft[2]) {
4631 makestr(&cmdatemsg,"Too many digits in year");
4632 debug(F111,"cmcvtdate",cmdatemsg,-1);
4635 makestr(&cmdatemsg,"Unexpected date format");
4636 debug(F111,"cmcvtdate",cmdatemsg,-1);
4640 sprintf(tmpbuf,"%02d",x); /* 2-digit numeric month */
4646 state = 4 = fractions of seconds
4650 if (isletter && (s == p)) {
4651 makestr(&cmdatemsg,"Unknown date-time word");
4652 debug(F111,"cmcvtdate",cmdatemsg,-1);
4655 if (!year && yyyymmdd[0]) {
4656 debug(F110,"cmcvtdate dotime yyyymmdd",yyyymmdd,0);
4657 for (i = 0; i < 4; i++)
4658 yearbuf[i] = yyyymmdd[i];
4660 monbuf[0] = yyyymmdd[4];
4661 monbuf[1] = yyyymmdd[5];
4663 daybuf[0] = yyyymmdd[6];
4664 daybuf[1] = yyyymmdd[7];
4667 nday = atoi(daybuf);
4672 makestr(&cmdatemsg,"Internal error - date not defaulted");
4673 debug(F111,"cmcvtdate",cmdatemsg,-1);
4676 /* Get here with day, month, and year set */
4677 debug(F110,"cmcvtdate dotime day",day,0);
4678 debug(F110,"cmcvtdate dotime month",month,0);
4679 debug(F110,"cmcvtdate dotime year",year,0);
4680 debug(F110,"cmcvtdate dotime s",s,0);
4681 debug(F110,"cmcvtdate dotime p",p,0);
4683 if (x > 12 || x < 1) {
4684 makestr(&cmdatemsg,"Month out of range");
4685 debug(F111,"cmcvtdate",cmdatemsg,-1);
4690 if (x == 2) if (isleap(atoi(year))) i++;
4691 if (nday > i || nday < 1) {
4692 makestr(&cmdatemsg,"Day out of range");
4693 debug(F111,"cmcvtdate",cmdatemsg,-1);
4696 if (!*p && t == 0) {
4697 sprintf(zbuf,"%04d%02d%02d",atoi(year),atoi(month),nday);
4701 if (*p == '+' || *p == '-') { /* GMT offset without a time */
4702 hh = 0; /* so default time to 00:00:00 */
4705 goto cmtimezone; /* and go do timezone */
4707 if (*p && !isdigit(*p) && *p != ':') {
4708 makestr(&cmdatemsg,"Invalid time");
4709 debug(F111,"cmcvtdate",cmdatemsg,-1);
4712 sprintf(yyyymmdd,"%s%s%02d",year,month,nday); /* for tz calculations... */
4714 state = 1; /* Initialize time-parsing FSA */
4716 mm = 0; /* minutes */
4717 ss = 0; /* seconds */
4718 ff = -1; /* fraction */
4719 d = 0; /* Digit counter */
4720 p2 = p; /* Preliminary digit count... */
4721 while (isdigit(*p2)) {
4726 makestr(&cmdatemsg,"Too many time digits");
4727 debug(F111,"cmcvtdate",cmdatemsg,-1);
4730 d = (d & 1 && *p2 != ':') ? 1 : 0; /* Odd implies leading '0' */
4732 while (*p) { /* Get the time, if any */
4733 if (isdigit(*p)) { /* digit */
4740 hh = hh * 10 + (*p - '0');
4742 case 2: /* Minutes */
4743 mm = mm * 10 + (*p - '0');
4745 case 3: /* Seconds */
4746 ss = ss * 10 + (*p - '0');
4748 case 4: /* Fraction of second */
4750 ff = (*p > '4') ? 1 : 0;
4753 } else if (*p == ':') { /* Colon */
4757 makestr(&cmdatemsg,"Too many time fields");
4758 debug(F111,"cmcvtdate",cmdatemsg,-1);
4761 } else if (*p == '.') {
4766 makestr(&cmdatemsg,"Improper fraction");
4767 debug(F111,"cmcvtdate",cmdatemsg,-1);
4770 } else if (*p == SP) { /* Space */
4771 while (*p && (*p == SP)) /* position to first nonspace */
4774 } else if (isalpha(*p)) { /* AM/PM/Z or timezone */
4776 } else if (*p == '+' || *p == '-') { /* GMT offset */
4779 makestr(&cmdatemsg,"Invalid time characters");
4780 debug(F111,"cmcvtdate",cmdatemsg,-1);
4785 if (!*p) /* If nothing left */
4786 goto xcmdate; /* go finish up */
4788 /* At this point we have HH, MM, SS, and FF */
4789 /* Now handle the rest: AM, PM, and/or timezone info */
4791 if (!ckstrcmp(p,"am",2,0)) { /* AM/PM... */
4794 } else if (!ckstrcmp(p,"a.m.",4,0)) {
4797 } else if (!ckstrcmp(p,"pm",2,0)) {
4800 } else if (!ckstrcmp(p,"p.m.",4,0)) {
4804 if (pmflag && hh < 12) /* If PM was given */
4805 hh += 12; /* add 12 to the hour */
4807 /* Now handle timezone */
4810 debug(F110,"cmcvtdate timezone",p,0);
4812 zhh = 0; /* GMT offset HH */
4813 zmm = 0; /* GMT offset MM */
4814 gmtsign = 0; /* Sign of GMT offset */
4815 isgmt = 0; /* 1 if time is GMT */
4817 while (*p && *p == SP) /* Gobble spaces */
4819 if (!*p) /* If nothing left */
4820 goto xcmdate; /* we're done */
4822 if (isalpha(*p)) { /* Something left */
4823 int zone = 0; /* Alphabetic must be timezone */
4824 p2 = p; /* Isolate timezone */
4831 p = p2; /* Have timezone, look it up */
4832 zone = lookup(usatz,p,nusatz,NULL);
4833 debug(F111,"cmcvtdate timezone alpha",p,zone);
4835 if (zone < 0) { /* Not found */
4836 makestr(&cmdatemsg,"Unknown timezone");
4837 debug(F111,"cmcvtdate",cmdatemsg,-1);
4840 isgmt++; /* All dates are GMT from here down */
4841 if (zone != 0) { /* But not this one so make it GMT */
4842 hh += zone; /* RFC 822 timezone: EST etc */
4843 debug(F101,"cmcvtdate hh + zone","",hh);
4844 if (hh > 23) { /* Offset crosses date boundary */
4847 jd = mjd(yyyymmdd); /* Get MJD */
4848 jd += hh / 24; /* Add new day(s) */
4849 hh = hh % 24; /* and convert back to yyyymmdd */
4850 ckstrncpy(yyyymmdd,mjd2date(jd),YYYYMMDD);
4851 debug(F111,"cmcvtdate zone-adjusted date",yyyymmdd,hh);
4852 for (i = 0; i < 4; i++)
4853 yearbuf[i] = yyyymmdd[i];
4855 monbuf[0] = yyyymmdd[4];
4856 monbuf[1] = yyyymmdd[5];
4858 daybuf[0] = yyyymmdd[6];
4859 daybuf[1] = yyyymmdd[7];
4862 nday = atoi(daybuf);
4867 p = p3; /* Put back whatever we poked above */
4870 } else if (*p == '+' || *p == '-') { /* GMT/UTC offset */
4872 debug(F110,"cmcvtdate timezone GMT offset",p,0);
4873 gmtsign = (*p == '+') ? -1 : 1;
4876 while (*p == SP) p++;
4879 while (isdigit(*p)) { /* Count digits */
4883 if (d != 4) { /* Strict RFC [2]822 */
4884 isgmt = 0; /* If not exactly 4 digits */
4885 p = p3; /* it's not a GMT offset. */
4886 goto delta; /* So treat it as a delta time. */
4888 d = (d & 1 && *p != ':') ? 1 : 0; /* Odd implies leading '0' */
4890 debug(F111,"cmcvtdate GMT offset sign",p,gmtsign);
4891 debug(F101,"cmcvtdate GMT offset d","",d);
4894 if (isdigit(*p)) { /* digit */
4901 zhh = zhh * 10 + (*p - '0');
4904 zmm = zmm * 10 + (*p - '0');
4906 default: /* Ignore seconds or fractions */
4909 } else if (*p == ':') { /* Colon */
4912 } else if (*p == SP || *p == '(') {
4915 p = p3; /* Maybe it's not a GMT offset. */
4916 goto delta; /* So treat it as a delta time. */
4921 debug(F110,"cmcvtdate source string after timezone",p,0);
4923 if (*p) { /* Anything left? */
4925 while (*p2 == SP) /* Skip past spaces */
4927 if (*p2 == '(') { /* RFC-822 comment? */
4928 int pc = 1; /* paren counter */
4936 } else if (*p2 == ')') {
4941 while (*p2 == SP) /* Skip past spaces */
4943 if (!*p2) /* Anything left? */
4944 *p = NUL; /* No, erase comment */
4946 if (!*p2) /* Anything left? */
4947 goto xcmdate; /* No, done. */
4951 debug(F110,"cmcvtdate delta yyyymmdd",yyyymmdd,0);
4952 debug(F110,"cmcvtdate delta year",year,0);
4953 debug(F110,"cmcvtdate delta p",p,0);
4955 if (*p == '+' || *p == '-') { /* Delta time */
4956 int state = NEED_DAYS; /* Start off looking for days */
4958 dsign = 1; /* Get sign */
4961 while (*p == SP) /* Skip intervening spaces */
4963 while (state) { /* FSA to parse delta time */
4964 if (state < 0 || !isdigit(*p)) {
4965 makestr(&cmdatemsg,"Invalid delta time");
4966 debug(F111,"cmcvtdate",cmdatemsg,-1);
4969 p2 = p; /* Get next numeric field */
4970 while (isdigit(*p2))
4972 c = *p2; /* And break character */
4973 *p2 = NUL; /* Terminate the number */
4975 switch (state) { /* Interpret according to state */
4976 case NEED_DAYS: /* Initial */
4977 if ((c == '-') || /* VMS format */
4978 ((c == 'd' || c == 'D')
4979 && !isalpha(*(p2+1)))) { /* Days */
4983 else /* if anything is left */
4984 state = NEED_HRS; /* now we want hours. */
4985 } else if ((c == 'W' || c == 'w') && !isalpha(*(p2+1))) {
4986 ddays = atoi(p) * 7; /* weeks... */
4991 } else if ((c == 'M' || c == 'm') && !isalpha(*(p2+1))) {
4992 dmonths = atoi(p); /* months... */
4997 } else if ((c == 'Y' || c == 'y') && !isalpha(*(p2+1))) {
4998 dyears = atoi(p); /* years... */
5003 } else if (c == ':') { /* delimiter is colon */
5004 dhours = atoi(p); /* so it's hours */
5005 state = NEED_MINS; /* now we want minutes */
5006 } else if (!c) { /* end of string */
5007 dhours = atoi(p); /* it's still hours */
5008 state = 0; /* and we're done */
5009 } else if (isalpha(c) || c == SP) {
5010 if (c == SP) { /* It's a keyword? */
5011 p2++; /* Skip spaces */
5014 } else { /* or replace first letter */
5017 p3 = p2; /* p2 points to beginning of keyword */
5018 while (isalpha(*p3)) /* Find end of keyword */
5020 c = *p3; /* NUL it out so we can look it up */
5021 if (*p3) /* p3 points to keyword terminator */
5023 units = lookup(timeunits,p2,nunits,NULL);
5025 makestr(&cmdatemsg,"Invalid units in delta time");
5026 debug(F111,"cmcvtdate",cmdatemsg,-1);
5029 *p2 = NUL; /* Re-terminate the number */
5031 while (*p3 == SP) /* Point at field after units */
5039 ddays = atoi(p) * 7;
5054 } else { /* Anything else */
5055 state = -1; /* is an error */
5058 case NEED_HRS: /* Looking for hours */
5059 debug(F000,"cmcvtdate NEED_HRS",p,c);
5070 case NEED_MINS: /* Looking for minutes */
5081 case NEED_SECS: /* Looking for seconds */
5092 case NEED_FRAC: /* Fraction of second */
5093 if (!c && rdigits(p)) {
5102 if (c) /* next field if any */
5108 makestr(&cmdatemsg,"Extraneous material at end");
5109 debug(F111,"cmcvtdate",cmdatemsg,-1);
5116 if ((t != 2 && hh > 24) || hh < 0) { /* Hour range check */
5117 makestr(&cmdatemsg,"Invalid hours");
5118 debug(F111,"cmcvtdate",cmdatemsg,-1);
5121 if (mm > 59) { /* Minute range check */
5122 makestr(&cmdatemsg,"Invalid minutes");
5123 debug(F111,"cmcvtdate",cmdatemsg,-1);
5126 if (ff > 0) { /* Fraction of second? */
5130 } else if (mm < 59) {
5134 } else if (hh < 24) {
5140 /* Must add a day -- leave ff at 1... */
5141 /* (DO SOMETHING ABOUT THIS LATER) */
5143 if (ss > 60) { /* Seconds range check */
5144 makestr(&cmdatemsg,"Invalid seconds"); /* 60 is ok because of */
5145 debug(F111,"cmcvtdate",cmdatemsg,-1); /* Leap Second. */
5148 if ((mm < 0 || ss < 0) ||
5149 (t != 2 && (ss > 0 || mm > 0) && hh > 23)) {
5150 makestr(&cmdatemsg,"Invalid minutes or seconds");
5151 debug(F111,"cmcvtdate",cmdatemsg,-1);
5154 debug(F110,"cmcvtdate year",year,0);
5155 debug(F110,"cmcvtdate month",month,0);
5156 debug(F101,"cmcvtdate nday","",nday);
5157 debug(F101,"cmcvtdate hh","",hh);
5158 debug(F101,"cmcvtdate mm","",mm);
5159 debug(F101,"cmcvtdate ss","",ss);
5160 debug(F101,"cmcvtdate gmtsign","",gmtsign);
5161 debug(F101,"cmcvtdate zhh","",zhh);
5162 debug(F101,"cmcvtdate zmm","",zmm);
5163 debug(F101,"cmcvtdate isgmt","",isgmt);
5166 /* Handle timezone -- first convert to GMT */
5168 zdd = 0; /* Days changed */
5169 if (isgmt && (zmm || zhh)) { /* If GMT offset given */
5170 long sec1, sec2, zz;
5171 sec1 = ss + 60 * mm + 3600 * hh;
5172 sec2 = gmtsign * (60 * zmm + 3600 * zhh);
5176 zdd = 0L - (sec1 / 86400L);
5177 sec1 = sec1 % 86400L;
5178 } else if (sec1 > 86400L) {
5179 zdd = sec1 / 86400L;
5180 sec1 = sec1 % 86400L;
5186 debug(F101,"cmcvtdate NEW hh","",hh);
5187 debug(F101,"cmcvtdate NEW mm","",mm);
5188 debug(F101,"cmcvtdate NEW dd","",zdd);
5190 /* At this point hh:mm:ss is in GMT and zdd is the calendar adjustment */
5193 #endif /* ZLOCALTIME */
5195 if (yyyymmdd[0] && !year) {
5196 ckstrncpy(yearbuf,yyyymmdd,5);
5197 ckstrncpy(monbuf,&yyyymmdd[4],3);
5198 ckstrncpy(daybuf,&yyyymmdd[6],3);
5202 nday = atoi(daybuf);
5204 sprintf(zbuf,"%04d%02d%02d %02d:%02d:%02d", /* SAFE */
5205 atoi(year),atoi(month),nday,hh,mm,ss
5210 /* Now convert from GMT to local time */
5212 if (isgmt) { /* If GMT convert to local time */
5213 debug(F110,"cmcvtdate GMT 1",dp,0);
5214 if (zdd) { /* Apply any calendar adjustment */
5217 sprintf(zbuf,"%s %02d:%02d:%02d",mjd2date(zz),hh,mm,ss);
5219 debug(F110,"cmcvtdate GMT 2",dp,0);
5220 if ((p = zlocaltime(dp))) {
5221 debug(F110,"cmcvtdate asctime zlocaltime",p,0);
5222 if (p) ckstrncpy(zbuf,p,18);
5224 debug(F110,"cmcvtdate GMT 3",dp,0);
5225 for (i = 0; i < 4; i++)
5235 nday = atoi(daybuf);
5242 #endif /* ZLOCALTIME */
5246 debug(F101,"cmcvtdate hour","",hh);
5247 debug(F101,"cmcvtdate minute","",mm);
5248 debug(F101,"cmcvtdate second","",ss);
5252 makestr(&cmdatemsg,NULL);
5256 debug(F110,"cmcvtdate base ",dp,0);
5257 debug(F101,"cmcvtdate delta sign","",dsign);
5258 debug(F101,"cmcvtdate delta yrs ","",dyears);
5259 debug(F101,"cmcvtdate delta mos ","",dmonths);
5260 debug(F101,"cmcvtdate delta days","",ddays);
5261 debug(F101,"cmcvtdate delta hrs ","",dhours);
5262 debug(F101,"cmcvtdate delta mins","",dmins);
5263 debug(F101,"cmcvtdate delta secs","",dsecs);
5266 if (!(dp = cmdelta(atoi(year),
5269 dsign, dyears, dmonths, ddays, dhours, dmins, dsecs))) {
5270 debug(F111,"cmcvtdate",cmdatemsg,-1);
5275 xcvtdate: /* Exit point for success */
5279 debug(F110,"cmcvtdate xcvtdate dp",dp,0);
5280 if (!dp) dp = ""; /* Shouldn't happen */
5281 if (!*dp) return(NULL); /* ... */
5283 debug(F111,"cmcvtdate result",dp,len);
5284 k = cmdatebp - (char *)cmdatebuf; /* Space used */
5285 n = CMDATEBUF - k - 1; /* Space left */
5286 if (n < len) { /* Not enough? */
5287 cmdatebp = cmdatebuf; /* Wrap around */
5290 ckstrncpy(cmdatebp,dp,n);
5292 cmdatebp += len + 1;
5298 cmvdate(d) char * d; { /* Verify date-time */
5301 if ((int)strlen(d) != 17) return(0);
5302 for (i = 0; i < 8; i++) { if (!isdigit(d[i])) return(0); }
5303 if (!isdigit(d[9]) || !isdigit(d[10]) ||
5304 !isdigit(d[12]) || !isdigit(d[13]) ||
5305 !isdigit(d[15]) || !isdigit(d[16]))
5307 if (!ckstrchr(" Tt_-:",d[8])) return(0);
5308 if (d[11] != ':' && d[14] != ':') return(0);
5312 /* c m d i f f d a t e -- Get difference between two date-times */
5315 cmdiffdate(d1,d2) char * d1, * d2; {
5316 char d1buf[9], d2buf[9];
5317 char x1buf[18], x2buf[18];
5320 int hh1 = 0, mm1 = 0, ss1 = 0;
5321 int hh2 = 0, mm2 = 0, ss2 = 0;
5324 long jd1, jd2, jd, f1, f2, fx;
5325 static char result[24], *rp;
5327 debug(F110,"cmdiffdate d1 A",d1,0);
5328 debug(F110,"cmdiffdate d2 A",d2,0);
5330 if (!(p = cmcvtdate(d1,1))) /* Convert dates to standard format */
5332 ckstrncpy(x1buf,p,18);
5335 if (!(p = cmcvtdate(d2,1)))
5337 ckstrncpy(x2buf,p,18);
5340 debug(F110,"cmdiffdate d1 B",d1,0);
5341 debug(F110,"cmdiffdate d2 B",d2,0);
5342 if (!cmvdate(d1) || !cmvdate(d2))
5345 hh1 = atoi(&d1[9]); /* Get hours, minutes, and seconds */
5346 mm1 = atoi(&d1[12]); /* for first date */
5347 ss1 = atoi(&d1[15]);
5348 ckstrncpy(d1buf,d1,9);
5350 hh2 = atoi(&d2[9]); /* ditto for second date */
5351 mm2 = atoi(&d2[12]);
5352 ss2 = atoi(&d2[15]);
5353 ckstrncpy(d2buf,d2,9);
5355 jd1 = mjd(d1buf); /* Get the two Julian dates */
5357 f1 = ss1 + 60 * mm1 + 3600 * hh1; /* Convert first time to seconds */
5359 f2 = ss2 + 60 * mm2 + 3600 * hh2; /* Ditto for second time */
5360 debug(F101,"cmdiffdate jd1","",jd1);
5361 debug(F101,"cmdiffdate f1","",f1);
5362 debug(F101,"cmdiffdate jd2","",jd2);
5363 debug(F101,"cmdiffdate f2","",f2);
5365 if (jd2 > jd1 || (jd1 == jd2 && f2 > f1)) {
5367 if (f1 > f2) {jd2--; f2 += 86400L;}
5372 if (f2 > f1) {jd1--; f1 += 86400L;}
5376 debug(F111,"cmdiffdate sign jd",sign<0?"-":"+",jd);
5377 debug(F101,"cmdiffdate fx","",fx);
5379 hh = (int) (fx / 3600L); /* Convert seconds to hh:mm:ss */
5381 mm = (int) (fx % 3600L) / 60L;
5382 ss = (int) (fx % 3600L) % 60L;
5384 rp = result; /* Format the result */
5385 *rp++ = (sign < 0) ? '-' : '+';
5386 if (jd != 0 && hh+mm+ss == 0) {
5387 sprintf(rp,"%ldd",jd);
5388 } else if (jd == 0) {
5390 sprintf(rp,"%d:%02d",hh,mm);
5392 sprintf(rp,"%d:%02d:%02d",hh,mm,ss);
5395 sprintf(rp,"%ldd%d:%02d",jd,hh,mm);
5397 sprintf(rp,"%ldd%d:%02d:%02d",jd,hh,mm,ss);
5399 debug(F110,"cmdiffdate result",result,0);
5400 return((char *)result);
5404 /* s h u f f l e d a t e -- Rearrange date string */
5408 A date string in standard format: yyyymmdd hh:mm:ss (time optional).
5410 1: Reformat date to yyyy-mmm-dd (mmm = English month abbreviation).
5411 2: Reformat date to dd-mmm-yyyy (mmm = English month abbreviation).
5412 3: Reformat as numeric yyyymmddhhmmss.
5413 4: Reformat in asctime() format Sat Nov 26 11:10:34 2005
5415 Pointer to result if args valid, otherwise original arg pointer.
5418 shuffledate(p,opt) char * p; int opt; {
5419 extern char * wkdays[];
5422 static char obuf[48];
5427 if (!*p) p = ckdate();
5428 if (opt < 1 || opt > 4)
5431 if (len < 8 || len > 31) return(p);
5432 if (opt == 4) { /* Asctime format (26 Nov 2005) */
5435 ckstrncpy(ibuf,p,31);
5437 while (k >= 0 && ibuf[k] == CR || ibuf[k] == LF)
5439 while (k >= 0 && ibuf[k] == SP || ibuf[k] == HT)
5441 if (k < 9) ckstrncpy(&ibuf[8]," 00:00:00",9);
5443 z = mjd(p); /* Convert to modified Julian date */
5447 k = 6 - ((int)z + 3) % 7;
5449 k = ((int)z + 3) % 7; /* Day of week */
5452 obuf[0] = s[0]; /* Day of week */
5455 obuf[3] = SP; /* Space */
5458 mm = atoi(&ibuf[4]); /* Month */
5459 s = moname[mm-1]; /* Name of month */
5462 obuf[4] = s[0]; /* Month */
5465 obuf[7] = SP; /* Space */
5466 if (p[6] == '0') /* Date of month */
5471 ckstrncpy(&obuf[10],&p[8],10); /* Time */
5472 obuf[19] = SP; /* Space */
5473 obuf[20] = p[0]; /* Year */
5478 return((char *)obuf);
5481 ckstrncpy(obuf,p,48);
5482 /* yyyymmdd hh:mm:ss */
5483 /* 01234567890123456 */
5484 /* yyyymmddhhmmss */
5487 obuf[10] = obuf[12];
5488 obuf[11] = obuf[13];
5489 obuf[12] = obuf[15];
5490 obuf[13] = obuf[16];
5492 return((char *)obuf);
5494 ckstrncpy(ibuf,p,32);
5495 c = ibuf[4]; /* Warning: not Y10K compliant */
5500 if (yy < 1 || yy > 9999)
5505 if (!rdigits(&ibuf[4]))
5507 mm = atoi(&ibuf[4]);
5508 if (mm < 1 || mm > 12)
5513 if (!rdigits(&ibuf[6]))
5515 dd = atoi(&ibuf[6]);
5517 if (dd < 1 || mm > 31)
5519 /* IGNORE WARNINGS ABOUT moname[] REFS OUT OF RANGE - it's prechecked. */
5522 sprintf(obuf,"%04d-%s-%02d%s",yy,moname[mm-1],dd,&ibuf[8]);
5525 sprintf(obuf,"%02d-%s-%04d%s",dd,moname[mm-1],yy,&ibuf[8]);
5527 return((char *)obuf);
5531 /* C K C V T D A T E -- Like cmcvtdate(), but returns string. */
5532 /* For use by date-related functions */
5533 /* See calling conventions for cmcvtdate() above. */
5536 ckcvtdate(p,t) char * p; int t; {
5538 if (!(s = cmcvtdate(p,t)))
5539 return("<BAD_DATE_OR_TIME>"); /* \fblah() error message */
5545 /* C M D A T E -- Parse a date and/or time */
5548 Accepts date in various formats. If the date is recognized,
5549 this routine returns 0 or greater with the result string pointer
5550 pointing to a buffer containing the date as "yyyymmdd hh:mm:ss".
5553 cmdate(xhlp,xdef,xp,quiet,f) char *xhlp, *xdef, **xp; int quiet; xx_strp f; {
5555 char *o, *s, *zq, *dp;
5558 if (!xhlp) xhlp = "";
5559 if (!xdef) xdef = "";
5560 if (!*xhlp) xhlp = "Date and/or time";
5563 rc = cmfld(xhlp,xdef,&s,(xx_strp)0);
5564 debug(F101,"cmdate cmfld rc","",rc);
5567 debug(F110,"cmdate 1",s,0);
5568 o = s; /* Remember what they typed. */
5570 debug(F110,"cmdate 2",s,0);
5573 if (f) { /* If a conversion function is given */
5575 zq = atxbuf; /* do the conversion. */
5578 if ((x = (*f)(s,&zq,&atxn)) < 0) return(-2);
5581 if (setatm(pp,0) < 0) {
5582 if (!quiet) printf("?Evaluated date too long\n");
5587 dp = cmcvtdate(s,1);
5589 if (!quiet) printf("?%s\n",cmdatemsg);
5596 #ifdef CK_RECALL /* Command-recall functions */
5598 /* C M R I N I -- Initialize or change size of command recall buffer */
5603 if (recall && in_recall) { /* Free old storage, if any */
5604 for (i = 0; i < cm_recall; i++) {
5613 cm_recall = n; /* Set new size */
5614 rlast = current = -1; /* Initialize pointers */
5616 recall = (char **)malloc((cm_recall + 1) * sizeof(char *));
5619 for (i = 0; i < cm_recall; i++) {
5622 in_recall = 1; /* Recall buffers init'd */
5627 /* C M A D D N E X T -- Force addition of next command */
5631 if (on_recall && in_recall) { /* Even if it doesn't come */
5632 force_add = 1; /* from the keyboard */
5638 /* C M G E T C M D -- Find most recent matching command */
5641 cmgetcmd(s) char * s; {
5643 for (i = current; i >= 0; i--) { /* Search backward thru history list */
5644 if (!recall[i]) continue; /* This one's null, skip it */
5645 if (ckmatch(s,recall[i],0,1)) /* Match? */
5646 return(recall[i]); /* Yes, return pointer */
5648 return(NULL); /* No match, return NULL pointer */
5650 #endif /* CK_RECALL */
5652 /* A D D C M D -- Add a command to the recall buffer */
5655 addcmd(s) char * s; {
5656 int len = 0, nq = 0;
5659 extern int learning;
5660 #endif /* CKLEARN */
5662 if (xcmdsrc) /* Only for interactive commands */
5665 if (!newcmd) /* The command has been here already */
5666 return; /* so ignore it. */
5667 newcmd = 0; /* It's new but do this only once. */
5673 if (len < 1) /* Don't save empty commands */
5677 while (*p) { if (*p++ == '?') nq++; } /* Count question marks */
5680 if (learning) /* If a learned script is active */
5681 learncmd(s); /* record this command. */
5682 #endif /* CKLEARN */
5684 debug(F010,"CMD(P)",s,0); /* Maybe record it in the debug log */
5687 if (ckxlogging) { /* Maybe record it in syslog */
5688 if (ckxsyslog >= SYSLG_CX || ckxsyslog >= SYSLG_CM)
5689 cksyslog(SYSLG_CX, 1, "command", s, NULL);
5691 #endif /* CKSYSLOG */
5696 if (on_recall && /* Command recall is on? */
5697 cm_recall > 0 && /* Recall buffer size is > 0? */
5698 !no_recall) { /* Not not saving this command? */
5700 if (!force_add && rlast > -1) /* If previous command was identical */
5701 if (!strcmp(s,recall[rlast])) /* don't add another copy */
5704 force_add = 0; /* Reset now in case it was set */
5706 if (rlast >= cm_recall - 1) { /* Recall buffer full? */
5708 if (recall[0]) { /* Discard oldest command */
5712 for (i = 0; i < rlast; i++) { /* The rest */
5713 recall[i] = recall[i+1]; /* move back */
5715 rlast--; /* Now we have one less */
5717 rlast++; /* Index of last command in buffer */
5718 current = rlast; /* Also now the current command */
5719 if (current >= cm_recall) { /* Shouldn't happen */
5720 printf("?Command history error\n"); /* but if it does */
5721 on_recall = 0; /* turn off command saving */
5723 } else if (nq > 0) { /* Have at least one question mark */
5724 recall[current] = malloc(len+nq+1);
5725 if (recall[current]) {
5726 p = recall[current];
5734 #endif /* COMMENT */
5735 } else { /* Normal case, just copy */
5736 recall[current] = malloc(len+1);
5737 if (recall[current])
5738 ckstrncpy(recall[current],s,len+1);
5741 #endif /* CK_RECALL */
5747 /* C M H I S T O R Y */
5752 for (i = 0; i <= current; i++) {
5753 printf(" %s\n", recall[i]);
5754 if (++lc > (cmd_rows - 2)) { /* Screen full? */
5755 if (!askmore()) /* Do more-prompting... */
5764 savhistory(s,disp) char *s; int disp; {
5768 fp = fopen(s, disp ? "a" : "w");
5773 for (i = 0; i <= current; i++)
5774 fprintf(fp,"%s\n", recall[i]);
5778 #endif /* CK_RECALL */
5781 /* apparently not used */
5783 cmgetlc(s) char * s; { /* Get leading char */
5785 while ((c = *s++) <= SP) {
5791 #endif /* COMMENT */
5794 /* C M C F M -- Parse command confirmation (end of line) */
5798 -2: User typed anything but whitespace or newline
5800 0: Confirmation was received
5805 debug(F101,"cmcfm: cmflgs","",cmflgs);
5806 debug(F110,"cmcfm: atmbuf",atmbuf,0);
5807 inword = xc = cc = 0;
5809 setatm("",0); /* (Probably unnecessary) */
5811 while (cmflgs != 1) {
5817 printf("Command or field too long\n");
5822 case 1: /* End of line */
5827 printf("?Not confirmed - %s\n",atmbuf);
5831 break; /* Finish up below */
5835 continue; /* or fall thru. */
5838 if (xc == 0) /* If no chars typed, continue, */
5839 continue; /* else fall thru. */
5840 /* else fall thru... */
5842 case 3: /* Question mark */
5847 printf("?Not confirmed - %s\n",atmbuf);
5852 "\n Press the Return or Enter key to confirm the command\n");
5853 printf("%s%s",cmprom,cmdbuf);
5863 /* The following material supports chained parsing functions. */
5864 /* See ckucmd.h for FDB and OFDB definitions. */
5866 struct OFDB cmresult = { /* Universal cmfdb result holder */
5867 NULL, /* Address of succeeding FDB struct */
5868 0, /* Function code */
5869 NULL, /* String result */
5870 0, /* Integer result */
5871 (CK_OFF_T)0 /* Wide result */
5875 cmfdbi(p,fc,s1,s2,s3,n1,n2,f,k,nxt) /* Initialize an FDB */
5878 char * s1, * s2, * s3;
5895 /* C M F D B -- Parse a field with several possible functions */
5898 cmfdb(fdbin) struct FDB * fdbin; {
5900 extern int x_ifnum; /* IF NUMERIC - disables warnings */
5902 struct FDB * in = fdbin;
5903 struct OFDB * out = &cmresult;
5905 CK_OFF_T w = (CK_OFF_T)0;
5906 char *s, *xp, *m = NULL;
5911 out->fcode = -1; /* Initialize output struct */
5912 out->fdbaddr = NULL;
5913 out->sresult = NULL;
5916 Currently we make one trip through the FDBs. So if the user types Esc or
5917 Tab at the beginning of a field, only the first FDB is examined for a
5918 default. If the user types ?, help is given only for one FDB. We should
5919 search through the FDBs for all matching possibilities -- and in particular
5920 display the pertinent context-sensitive help for each function, rather than
5921 the only the first one that works, and then rewind the FDB pointer so we
5922 are not locked out of the earlier ones.
5925 while (1) { /* Loop through the chain of FDBs */
5930 debug(F101,"cmfdb in->fcode","",in->fcode);
5931 switch (in->fcode) { /* Current parsing function code */
5934 if (r != 10 && r != 8) r = 10;
5936 x_ifnum = 1; /* Disables warning messages */
5938 x = cmnum(in->hlpmsg,in->dflt,r,&n,in->spf);
5942 debug(F101,"cmfdb cmnum","",x);
5943 if (x < 0) errbits |= 1;
5945 case _CMNUW: /* Wide cmnum - 24 Dec 2005 */
5947 if (r != 10 && r != 8) r = 10;
5949 x_ifnum = 1; /* Disables warning messages */
5951 x = cmnumw(in->hlpmsg,in->dflt,r,&w,in->spf);
5955 debug(F101,"cmfdb cmnumw","",w);
5956 if (x < 0) errbits |= 1;
5959 x = cmofi(in->hlpmsg,in->dflt,&s,in->spf);
5960 debug(F101,"cmfdb cmofi","",x);
5961 if (x < 0) errbits |= 2;
5964 x = cmifi2(in->hlpmsg,
5973 debug(F101,"cmfdb cmifi2 x","",x);
5974 debug(F101,"cmfdb cmifi2 n","",n);
5975 if (x < 0) errbits |= 4;
5978 cmfldflgs = in->ndata1;
5979 x = cmfld(in->hlpmsg,in->dflt,&s,in->spf);
5980 debug(F101,"cmfdb cmfld","",x);
5981 if (x < 0) errbits |= 8;
5984 x = cmtxt(in->hlpmsg,in->dflt,&s,in->spf);
5985 debug(F101,"cmfdb cmtxt","",x);
5986 if (x < 0) errbits |= 16;
5989 x = cmkey2(in->kwdtbl,
5991 in->hlpmsg,in->dflt,in->sdata,in->spf,in->ndata2);
5992 debug(F101,"cmfdb cmkey","",x);
5993 if (x < 0) errbits |= ((in->ndata2 & 4) ? 32 : 64);
5997 debug(F101,"cmfdb cmcfm","",x);
5998 if (x < 0) errbits |= 128;
6001 debug(F101,"cmfdb - unexpected function code","",in->fcode);
6002 printf("?cmfdb - unexpected function code: %d\n",in->fcode);
6004 debug(F101,"cmfdb x","",x);
6005 debug(F101,"cmfdb cmflgs","",cmflgs);
6006 debug(F101,"cmfdb crflag","",crflag);
6007 debug(F101,"cmfdb qmflag","",qmflag);
6008 debug(F101,"cmfdb esflag","",esflag);
6010 if (x > -1) { /* Success */
6011 out->fcode = in->fcode; /* Fill in output struct */
6014 out->nresult = (in->fcode == _CMKEY) ? x : n;
6016 out->kflags = (in->fcode == _CMKEY) ? cmkwflgs : 0;
6017 debug(F111,"cmfdb out->nresult",out->sresult,out->nresult);
6018 debug(F111,"cmfdb out->wresult",out->sresult,out->wresult);
6021 /* debug(F111,"cmfdb cmdbuf & crflag",cmdbuf,crflag); */
6025 return(x); /* and return */
6027 in = in->nxtfdb; /* Failed, get next parsing function */
6030 if (!in) { /* No more */
6031 debug(F101,"cmfdb failure x","",x);
6032 debug(F101,"cmfdb failure errbits","",errbits);
6041 /* Make informative messages for a few common cases */
6043 case 4+32: m = "Does not match filename or switch"; break;
6044 case 4+64: m = "Does not match filename or keyword"; break;
6045 case 1+32: m = "Not a number or valid keyword"; break;
6046 case 1+64: m = "Not a number or valid switch"; break;
6047 default: m = "Not valid in this position";
6049 printf("?%s: \"%s\"\n",m, atmbuf);
6053 if (x != -2 && x != -6 && x != -9 && x != -3) /* Editing or somesuch */
6054 return(x); /* Go back and reparse */
6055 pp = np = bp = xp; /* Back up pointers */
6056 cmflgs = -1; /* Force a reparse */
6059 if (!askflag) { /* If not executing ASK-class cmd... */
6061 if (crflag) { /* If CR was typed, put it back */
6062 pushc = LF; /* But as a linefeed */
6063 } else if (qmflag) { /* Ditto for Question mark */
6065 } else if (esflag) { /* and Escape or Tab */
6075 C M I O F I -- Parse an input file OR the name of a nonexistent file.
6077 Replaces the commented-out version above. This one actually works and
6078 has the expected straightforward interface.
6081 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
6084 cmfdbi(&f1,_CMIFI,xhlp,xdef,"",0,0,f,NULL,&f2);
6085 cmfdbi(&f2,_CMOFI,"","","",0,0,f,NULL,NULL);
6090 printf("?Filename required\n");
6093 *wild = cmresult.nresult;
6094 *xp = cmresult.sresult;
6098 /* G T W O R D -- Gets a "word" from the command input stream */
6101 Usage: retcode = gtword(brk);
6102 brk = 0 for normal word breaks (space, CR, Esc, ?)
6103 brk = 1 to add ':' and '=' (for parsing switches). These characters
6104 act as break characters only if the first character of the field
6105 is slash ('/'), i.e. switch introducer.
6106 brk = 4 to not strip comments (used only for "help #" and "help ;").
6109 -10 Timelimit set and timed out
6110 -9 if input was too long
6111 -4 if end of file (e.g. pipe broken)
6113 -2 if command buffer overflows
6114 -1 if user did some deleting
6115 0 if word terminates with SP or tab
6118 3 if ... ? (question mark)
6119 4 if ... : or = and called with brk != 0
6122 pp pointing to beginning of word in buffer
6123 bp pointing to after current position
6124 atmbuf containing a copy of the word
6125 cc containing the number of characters in the word copied to atmbuf
6129 ungword() { /* Unget a word */
6130 debug(F101,"ungword cmflgs","",cmflgs);
6131 if (ungw) return(0);
6138 /* Un-un-get word. Undo ungword() if it has been done. */
6142 debug(F010,"unungw atmbuf",atmbuf,0);
6151 gtword(brk) int brk; {
6152 int c; /* Current char */
6153 int quote = 0; /* Flag for quote character */
6154 int echof = 0; /* Flag for whether to echo */
6155 int comment = 0; /* Flag for in comment */
6156 char *cp = NULL; /* Comment pointer */
6157 int eintr = 0; /* Flag for syscall interrupted */
6158 int bracelvl = 0; /* nested brace counter [jrs] */
6159 int iscontd = 0; /* Flag for continuation */
6160 int realtty = 0; /* Stdin is really a tty */
6162 char lastchar = NUL;
6163 char prevchar = NUL;
6164 char lbrace, rbrace;
6165 int dq = 0; /* Doublequote flag */
6166 int dqn = 0; /* and count */
6174 extern int inserver;
6176 extern int kstartactive;
6179 extern int termtype; /* DG terminal type flag */
6180 extern int con_reads_mt; /* Console read asynch is active */
6181 if (con_reads_mt) connoi_mt(); /* Task would interfere w/cons read */
6182 #endif /* datageneral */
6187 debug(F101,"gtword brk","",brk);
6188 debug(F101,"gtword cmfldflgs","",cmfldflgs);
6189 debug(F101,"gtword swarg","",swarg);
6190 debug(F101,"gtword dpx","",dpx);
6191 debug(F101,"gtword echof","",echof);
6193 debug(F101,"gtword askflag","",askflag);
6194 debug(F101,"gtword timelimit","",timelimit);
6198 debug(F101,"gtword cmdadl","",cmdadl);
6199 #endif /* CK_AUTODL */
6201 #endif /* NOLOCAL */
6205 #endif /* COMMENT */
6207 realtty = is_a_tty(0); /* Stdin is really a tty? */
6209 if (cmfldflgs & 1) {
6220 if (swarg) { /* No leading space for switch args */
6224 if (ungw) { /* Have a word saved? */
6226 /* Experimental code to allow ungetting multiple words. */
6227 /* See comments in ckmkey2() above. */
6229 if (np > pp) pp = np;
6230 while (*pp == SP) pp++;
6235 if ((x = setatm(pp,2)) < 0) {
6236 printf("?Saved word too long\n");
6243 while (*p2 == SP) p2++;
6251 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6257 You would think the following should be:
6258 while (*pp == SP) pp++;
6259 but you would be wrong -- making this change breaks GOTO.
6261 while (*pp++ == SP) ;
6262 if (setatm(pp,2) < 0) {
6263 printf("?Saved word too long\n");
6268 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6272 pp = np; /* Start of current field */
6277 debug(F110,"gtword cmdbuf",cmdbuf,0);
6278 debug(F110,"gtword bp",bp,0);
6279 debug(F110,"gtword pp",pp,0);
6282 #endif /* COMMENT */
6284 /* If we are reparsing we have to recount any braces or doublequotes */
6292 else if (c == rbrace)
6294 else if (dq && c == '"')
6297 while (bp < cmdbuf+CMDBL) { /* Big get-a-character loop */
6298 echof = 0; /* Assume we don't echo because */
6299 chsrc = 0; /* character came from reparse buf. */
6302 #endif /* BS_DIRSEP */
6305 if (!c) { /* If no char waiting in reparse buf */
6314 )) /* Get from tty, set echo flag */
6316 c = cmdgetc(timelimit); /* Read a command character. */
6318 debug(F101,"gtword c","",c);
6321 if (timelimit && c < -1) { /* Timed out */
6327 The following allows packet recognition in the command parser.
6328 Presently it works only for Kermit packets, and if our current protocol
6329 happens to be anything besides Kermit, we simply force it to Kermit.
6330 We don't use the APC mechanism here for mechanical reasons, and also
6331 because this way, it works even with minimally configured interactive
6332 versions. Add Zmodem later...
6335 if ((!local && cmdadl) /* Autodownload enabled? */
6337 || TELOPT_SB(TELOPT_KERMIT).kermit.me_start
6338 #endif /* IKS_OPTION */
6341 k = kstart((CHAR)c); /* Kermit S or I packet? */
6344 if (k < 0) { /* Minus-Protocol? */
6346 goto noserver; /* Need server mode for this */
6348 ksign = 1; /* Remember */
6349 k = 0 - k; /* Convert to actual protocol */
6350 justone = 1; /* Flag for protocol module */
6351 #endif /* NOSERVER */
6354 k--; /* Adjust kstart's return value */
6356 extern int protocol, g_proto;
6359 protocol = PROTO_K; /* Crude... */
6360 sstate = ksign ? 'x' : 'v';
6368 #endif /* NOSERVER */
6369 #endif /* CK_AUTODL */
6372 chsrc = 1; /* Remember character source is tty. */
6376 if (inserver && c < 0) { /* End of session? */
6377 debug(F111,"gtword c < 0","exiting",c);
6378 return(-4); /* Cleanup and terminate */
6383 if (c < 0) { /* Error */
6384 if (c == -3) { /* Empty word? */
6385 if (blocklvl > 0) /* In a block */
6386 continue; /* so keep looking for block end */
6388 return(-3); /* Otherwise say we got nothing */
6389 } else { /* Not empty word */
6390 return(-4); /* So some kind of i/o error */
6395 if (c == -3) /* Empty word... */
6402 if (c == EOF) { /* This can happen if stdin not tty. */
6405 Some operating and/or C runtime systems return EINTR for no good reason,
6406 when the end of the standard input "file" is encountered. In cases like
6407 this, we get into an infinite loop; hence the eintr counter, which is reset
6408 to 0 upon each call to this routine.
6410 debug(F101,"gtword EOF","",errno);
6411 if (errno == EINTR && ++eintr < 4) /* When bg'd process is */
6412 continue; /* fg'd again. */
6416 c &= cmdmsk; /* Strip any parity bit */
6419 /* Now we have the next character */
6421 isesc = (c == ESC); /* A real ESC? */
6423 if (!firstnb && c > SP) { /* First nonblank */
6425 if (c == '"') /* Starts with doublequote */
6428 if (c == '"') /* Count doublequotes */
6431 if (quote && (c == CR || c == LF)) { /* Enter key following quote */
6432 *bp++ = CMDQ; /* Double it */
6436 if (quote == 0) { /* If this is not a quoted character */
6438 case CMDQ: /* Got the quote character itself */
6439 if (!comment && quoting)
6440 quote = 1; /* Flag it if not in a comment */
6442 case FF: /* Formfeed. */
6443 c = NL; /* Replace with newline */
6444 cmdclrscn(); /* Clear the screen */
6446 case HT: /* Horizontal Tab */
6447 if (comment) /* If in comment, */
6448 c = SP; /* substitute space */
6449 else /* otherwise */
6450 c = ESC; /* substitute ESC (for completion) */
6452 case ';': /* Trailing comment */
6454 if (! (brk & 4) ) { /* If not keeping comments */
6455 if (inword == 0 && quoting) { /* If not in a word */
6456 comment = 1; /* start a comment. */
6457 cp = bp; /* remember where it starts. */
6462 if (!kstartactive && /* Not in possible Kermit packet */
6463 !comment && c == SP) { /* Space not in comment */
6464 *bp++ = (char) c; /* deposit in buffer if not already */
6465 /* debug(F101,"gtword echof 2","",echof); */
6468 cmdecho((char) c, 0); /* Echo what was typed. */
6474 cmdecho((char) c, 0); /* Echo what was typed. */
6479 if (inword == 0) { /* If leading, gobble it. */
6482 } else { /* If terminating, return. */
6483 if ((!dq && ((*pp != lbrace) || (bracelvl == 0))) ||
6484 (dq && dqn > 1 && *(bp-2) == '"')) {
6487 if (setatm(pp,0) < 0) {
6488 printf("?Field too long error 1\n");
6489 debug(F111,"gtword too long #1",pp,strlen(pp));
6493 inword = cmflgs = 0;
6501 /* debug(F101,"gtword bracelvl++","",bracelvl); */
6503 if (c == rbrace && bracelvl > 0) {
6505 /* debug(F101,"gtword bracelvl--","",bracelvl); */
6509 if ((c == '=' || c == ':') &&
6511 !kstartactive && !comment && brk /* && (firstnb == '/') */
6513 *bp++ = (char) c; /* Switch argument separator */
6514 /* debug(F111,"gtword switch argsep",cmdbuf,brk); */
6517 cmdecho((char) c, 0); /* Echo what was typed. */
6523 cmdecho((char) c, 0); /* Echo what was typed. */
6528 if ((*pp != lbrace) || (bracelvl == 0)) {
6531 if (setatm(pp,2) < 0) { /* ^^^ */
6532 printf("?Field too long error 1\n");
6533 debug(F111,"gtword too long #1",pp,strlen(pp));
6536 inword = cmflgs = 0;
6541 if (c == LF || c == CR) { /* CR or LF. */
6543 cmdnewl((char)c); /* echo it. */
6550 /* Trim trailing comment and whitespace */
6552 if (comment) { /* Erase comment */
6553 while (bp >= cp) /* Back to comment pointer */
6556 pp = bp; /* Adjust other pointers */
6557 inword = 0; /* and flags */
6561 qq = inword ? pp : (char *)cmdbuf;
6562 /* Erase trailing whitespace */
6563 while (bp > qq && (*(bp-1) == SP || *(bp-1) == HT)) {
6565 /* debug(F000,"erasing","",*bp); */
6568 lastchar = (bp > qq) ? *(bp-1) : NUL;
6569 prevchar = (bp > qq+1) ? *(bp-2) : NUL;
6571 if (linebegin && blocklvl > 0) /* Blank line in {...} block */
6574 linebegin = 1; /* At beginning of next line */
6575 iscontd = prevchar != CMDQ &&
6576 (lastchar == '-' || lastchar == lbrace);
6577 debug(F101,"gtword iscontd","",iscontd);
6579 if (iscontd) { /* If line is continued... */
6580 if (chsrc) { /* If reading from tty, */
6581 if (*(bp-1) == lbrace) { /* Check for "begin block" */
6582 *bp++ = SP; /* Insert a space for neatness */
6583 blocklvl++; /* Count block nesting level */
6584 } else { /* Or hyphen */
6585 bp--; /* Overwrite the hyphen */
6587 *bp = NUL; /* erase the dash, */
6588 continue; /* and go back for next char now. */
6590 } else if (blocklvl > 0) { /* No continuation character */
6591 if (chsrc) { /* But we're in a "block" */
6592 *bp++ = ','; /* Add comma */
6596 } else { /* No continuation, end of command. */
6597 *bp = NUL; /* Terminate the command string. */
6598 if (comment) { /* If we're in a comment, */
6599 comment = 0; /* Say we're not any more, */
6600 *cp = NUL; /* cut it off. */
6602 np = bp; /* Where to start next field. */
6604 if (setatm(pp,0) < 0) { /* Copy field to atom buffer */
6605 debug(F111,"gtword too long #2",pp,strlen(pp));
6606 printf("?Field too long error 2\n");
6609 inword = 0; /* Not in a word any more. */
6611 /* debug(F110,"gtword","crflag is set",0); */
6614 #endif /* CK_RECALL */
6619 #endif /* CK_RECALL */
6626 This section handles interactive help, completion, editing, and history.
6627 Rearranged as a switch statement executed only if we're at top level since
6628 there is no need for any of this within command files and macros: Aug 2000.
6629 Jun 2001: Even if at top level, skip this if the character was fetched from
6630 the reparse or recall buffer, or if stdin is redirected.
6632 if ((xcmdsrc == 0 /* Only at top level */
6634 || askflag /* or user is typing ASK response */
6636 ) && chsrc != 0 && realtty) { /* from the real keyboard */
6638 /* Use ANSI / VT100 up and down arrow keys for command recall. */
6646 #ifdef USE_ARROWKEYS
6648 #endif /* USE_ARROWKEYS */
6650 ) { /* A real ESC was typed */
6652 msleep(200); /* Wait 1/5 sec */
6653 x = cmdconchk(); /* Was it followed by anything? */
6654 debug(F101,"Arrowkey ESC cmdconchk","",x);
6656 if (x > 1) { /* If followed by at least 2 chars */
6658 c2 = cmdgetc(0); /* Get the first one */
6659 debug(F101,"Arrowkey ESC c2","",c2);
6661 if (c2 != '[' && c2 != 'O') { /* If not [ or O */
6662 pushc = c2; /* Push it and take the ESC solo */
6664 c2 = cmdgetc(0); /* Get the second one */
6665 debug(F101,"Arrowkey ESC c3","",c2);
6672 case 'B': /* Down */
6676 case 'C': /* Right */
6677 case 'D': /* Left */
6680 #endif /* NORECALL */
6681 c = BEL; /* We don't use these yet */
6689 case '?': /* ?-Help */
6691 if (askflag) /* No help in ASK response */
6698 cmdecho((char) c, 0);
6700 if (setatm(pp,0) < 0) {
6701 debug(F111,"gtword too long ?",pp,strlen(pp));
6702 printf("?Too long\n");
6709 case ESC: /* Esc or Tab completion */
6712 if (setatm(pp,0) < 0) {
6713 debug(F111,"gtword too long Esc",pp,strlen(pp));
6714 printf("?Too long\n");
6724 case BS: /* Character deletion */
6726 if (bp > cmdbuf) { /* If still in buffer... */
6727 cmdchardel(); /* erase it. */
6728 bp--; /* point behind it, */
6729 if (*bp == lbrace) bracelvl--; /* Adjust brace count */
6730 if (*bp == rbrace) bracelvl++;
6731 if ((*bp == SP) && /* Flag if current field gone */
6732 (*pp != lbrace || bracelvl == 0))
6734 *bp = NUL; /* Erase character from buffer. */
6735 } else { /* Otherwise, */
6737 cmres(); /* and start parsing a new command. */
6738 *bp = *atmbuf = NUL;
6743 return(cmflgs = -1);
6745 case LDEL: /* ^U, line deletion */
6746 while ((bp--) > cmdbuf) {
6750 cmres(); /* Restart the command. */
6751 *bp = *atmbuf = NUL;
6753 return(cmflgs = -1);
6755 case WDEL: /* ^W, word deletion */
6756 if (bp <= cmdbuf) { /* Beep if nothing to delete */
6759 *bp = *atmbuf = NUL;
6760 return(cmflgs = -1);
6763 /* Back up over any trailing nonalphanums */
6764 /* This is dependent on ASCII collating sequence */
6765 /* but isalphanum() is not available everywhere. */
6769 ((*bp > '9') && (*bp < '@')) ||
6770 ((*bp > 'Z') && (*bp < 'a')) ||
6777 /* Now delete back to rightmost remaining nonalphanum */
6778 for ( ; (bp >= cmdbuf) && (*bp) ; bp--) {
6780 (*bp > '9' && *bp < '@') ||
6781 (*bp > 'Z' && *bp < 'a') ||
6789 return(cmflgs = -1);
6791 case RDIS: { /* ^R, redisplay */
6794 printf("\n%s",cmprom);
6796 while ((cx = *cpx++)) {
6805 printf("%s ",lastfile);
6809 inword = cmflgs = 0;
6810 addbuf(lastfile); /* Supply default. */
6811 if (setatm(lastfile,0) < 0) {
6812 printf("Last name too long\n");
6816 } else { /* No default */
6820 #endif /* NOLASTFILE */
6824 if (on_recall && /* Reading commands from keyboard? */
6825 (cm_recall > 0) && /* Saving commands? */
6826 (c == C_UP || c == C_UP2)) { /* Go up one */
6827 if (last_recall == 2 && current > 0)
6829 if (current < 0) { /* Nowhere to go, */
6833 if (recall[current]) { /* We have a previous command */
6834 while ((bp--) > cmdbuf) { /* Erase current line */
6838 ckstrncpy(cmdbuf,recall[current],CMDBL);
6841 write(fileno(stdout), "\r", 1);
6842 printf("%s%s",cmprom,cmdbuf);
6844 printf("\r%s%s",cmprom,cmdbuf);
6849 return(cmflgs = -1); /* Force a reparse */
6851 if (on_recall && /* Reading commands from keyboard? */
6852 (cm_recall > 0) && /* Saving commands? */
6853 (c == C_DN)) { /* Down one */
6855 if (last_recall == 1)
6857 if (current + x > rlast) { /* Already at bottom, beep */
6861 current += x; /* OK to go down */
6862 if (recall[current]) {
6863 while ((bp--) > cmdbuf) { /* Erase current line */
6867 ckstrncpy(cmdbuf,recall[current],CMDBL);
6870 write(fileno(stdout), "\r", 1);
6871 printf("%s%s",cmprom,cmdbuf);
6873 printf("\r%s%s",cmprom,cmdbuf);
6876 return(cmflgs = -1); /* Force reparse */
6879 #endif /* CK_RECALL */
6882 if (c < SP && quote == 0) { /* Any other unquoted control char */
6883 if (!chsrc) { /* If cmd file, point past it */
6888 continue; /* continue, don't put in buffer */
6890 linebegin = 0; /* Not at beginning of line */
6893 cmdecho((char) c, 0); /* Echo what was typed. */
6901 if (echof || (echostars && chsrc))
6903 cmdecho((char) c, 0); /* Echo what was typed. */
6905 } else { /* This character was quoted. */
6907 quote = 0; /* Unset the quote flag. */
6908 /* debug(F000,"gtword quote 0","",c); */
6909 /* Quote character at this level is only for SP, ?, and controls */
6910 /* If anything else was quoted, leave quote in, and let */
6911 /* the command-specific parsing routines handle it, e.g. \007 */
6912 if (c > 32 && c != '?' && c != RUB && chsrc != 0) {
6913 /* debug(F000,"gtword quote 1","",c); */
6914 *bp++ = CMDQ; /* Deposit \ if it came from tty */
6915 qf = 0; /* and don't erase it from screen */
6916 linebegin = 0; /* Not at beginning of line */
6919 This is a hack to handle "cd \" or "cd foo\" on OS/2 and similar systems.
6920 If we were called from cmdir() and the previous character was the quote
6921 character, i.e. backslash, and this character is the command terminator,
6922 then we stuff an extra backslash into the buffer without echoing, then
6923 we stuff the carriage return back in again, and go back and process it,
6924 this time with the quote flag off.
6926 } else if (dirnamflg && (c == CR || c == LF || c == SP)) {
6927 /* debug(F000,"gtword quote 2","",c); */
6929 linebegin = 0; /* Not at beginning of line */
6930 *bp = (c == SP ? SP : CR);
6932 #endif /* BS_DIRSEP */
6936 cmdecho((char) c, qf); /* Echo what was typed. */
6941 if (echof) cmdecho((char) c, qf); /* Now echo quoted character */
6943 /* debug(F111,"gtword quote",cmdbuf,c); */
6946 if (echof) cmdecho((char) c,quote); /* Echo what was typed. */
6947 #endif /* COMMENT */
6948 if (!comment) inword = 1; /* Flag we're in a word. */
6949 if (quote) continue; /* Don't deposit quote character. */
6950 if (c != NL) { /* Deposit command character. */
6951 *bp++ = (char) c; /* and make sure there is a NUL */
6953 *bp = NUL; /* after it */
6954 #endif /* COMMENT */
6956 } /* End of big while */
6958 printf("?Command too long, maximum length: %d.\n",CMDBL);
6963 /* Utility functions */
6965 /* A D D B U F -- Add the string pointed to by cp to the command buffer */
6968 addbuf(cp) char *cp; {
6970 while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
6971 *bp++ = *cp++; /* Copy and */
6972 len++; /* count the characters. */
6974 *bp++ = SP; /* Put a space at the end */
6975 *bp = NUL; /* Terminate with a null */
6976 np = bp; /* Update the next-field pointer */
6978 return(len); /* Return the length */
6981 /* S E T A T M -- Deposit a token in the atom buffer. */
6983 Break on space, newline, carriage return, or NUL.
6985 cp = Pointer to string to copy to atom buffer.
6986 fcode = 0 means break on whitespace or EOL.
6987 fcode = 1 means don't break on space.
6988 fcode = 2 means break on space, ':', or '='.
6989 fcode = 3 means copy the whole string.
6990 Null-terminate the result.
6991 Return length of token, and also set global "cc" to this length.
6992 Return -1 if token was too long.
6995 setatm(cp,fcode) char *cp; int fcode; {
6996 char *ap, *xp, *dqp = NULL, lbrace, rbrace;
6997 int bracelvl = 0, dq = 0;
7002 if (cmfldflgs & 1) { /* Handle grouping */
7009 cc = 0; /* Character counter */
7010 ap = atmbuf; /* Address of atom buffer */
7014 while (*s++) n++; /* Save a call to strlen */
7017 printf("?Command buffer overflow\n");
7020 /* debug(F111,"setatm",cp,n); */
7021 if (cp == ap) { /* In case source is atom buffer */
7022 xp = atybuf; /* make a copy */
7024 strncpy(xp,ap,ATMBL); /* so we can copy it back, edited. */
7028 while ((*xp++ = *s++)) ; /* We already know it's big enough */
7030 #endif /* COMMENT */
7032 *ap = NUL; /* Zero the atom buffer */
7033 if (fcode == 1) { /* Trim trailing blanks */
7034 while (--n >= 0 && cp[n] == SP)
7038 while (*cp == SP) { /* Trim leading spaces */
7042 if (*cp == '"') { /* Starts with doublequote? */
7049 else if (*cp == rbrace)
7053 if (bracelvl == 0) {
7055 if (*cp == SP || *cp == HT) {
7057 if (*(cp-1) == '"' && *(cp-2) != CMDQ) {
7062 } else if ((*cp == SP || *cp == HT) && fcode != 1 && fcode != 3)
7064 if ((fcode == 2) && (*cp == '=' || *cp == ':')) break;
7065 if ((fcode != 3) && (*cp == LF || *cp == CR)) break;
7070 *ap = NUL; /* Terminate the string. */
7071 /* debug(F111,"setatm result",atmbuf,cc); */
7072 return(cc); /* Return length. */
7076 These functions attempt to hide system dependencies from the mainline
7077 code in gtword(). Dummy arg for cmdgetc() needed for compatibility with
7078 coninc(), ttinc(), etc, since a pointer to this routine can be passed in
7079 place of those to tn_doop().
7081 No longer static. Used by askmore(). Fri Aug 20 15:03:34 1999.
7083 #define CMD_CONINC /* How we get keyboard chars */
7086 cmdgetc(timelimit) int timelimit; { /* Get a character from the tty. */
7089 extern int inserver;
7092 extern int x_logged;
7093 #endif /* CK_LOGIN */
7095 static int got_cr = 0;
7097 int tx = 0, is_tn = 0;
7105 debug(F111,"cmdgetc()","pushc",pushc);
7108 if (xcmfdb && c == '?') /* Don't echo ? twice if chaining. */
7115 c = dgncinb(0,&ch,1); /* -1 is EOF, -2 TO,
7116 * -c is AOS/VS error */
7117 if (c == -2) { /* timeout was enabled? */
7118 resto(channel(0)); /* reset timeouts */
7119 c = dgncinb(0,&ch,1); /* retry this now! */
7121 if (c < 0) return(-4); /* EOF or some error */
7122 else c = (int) ch & 0177; /* Get char without parity */
7125 #else /* Not datageneral */
7129 (!local && inserver) ||
7134 is_tn = !pushc && !local && sstelnet;
7137 c = coninc(timelimit > 0 ? 1 : 0);
7139 /* This is likely to break the asktimeout... */
7140 c = coninc(timelimit);
7141 #endif /* COMMENT */
7142 /* debug(F101,"cmdgetc coninc","",c); */
7144 if (c >= 0 && is_tn) { /* Server-side Telnet */
7147 /* debug(F111,"gtword IAC","c",c); */
7149 if ((tx = tn_doop((CHAR)(c & 0xff),ckxech,coninc)) == 0) {
7151 } else if (tx <= -1) { /* I/O error */
7152 /* If there was a fatal I/O error then ttclos() */
7153 /* has been called and the next GETNEXTCH attempt */
7154 /* will be !is_tn since ttclos() sets sstelnet = 0 */
7155 doexit(BAD_EXIT,-1); /* (or return(-4)? */
7156 } else if (tx == 1) { /* ECHO change */
7157 ckxech = dpx = 1; /* Get next char */
7159 } else if (tx == 2) { /* ECHO change */
7160 ckxech = dpx = 0; /* Get next char */
7162 } else if (tx == 3) { /* Quoted IAC */
7163 c = 255; /* proceeed with it. */
7166 else if (tx == 4) { /* IKS State Change */
7169 #endif /* IKS_OPTION */
7170 else if (tx == 6) { /* Remote Logout */
7171 doexit(GOOD_EXIT,0);
7173 goto GETNEXTCH; /* Unknown, get next char */
7178 if (!TELOPT_U(TELOPT_BINARY)) {
7180 /* This means the sender is violating Telnet */
7181 /* protocol because we received two CRs in a */
7182 /* row without getting either LF or NUL. */
7183 /* This will not solve the problem but it */
7184 /* will at least allow two CRs to do something */
7185 /* whereas before the user would have to guess */
7186 /* to send LF or NUL after the CR. */
7187 debug(F100,"gtword CR telnet error","",0);
7190 debug(F100,"gtword skipping CR","",0);
7191 got_cr = 1; /* Remember a CR was received */
7195 debug(F100,"gtword CR to LF","",0);
7200 if (!TELOPT_U(TELOPT_BINARY)) {
7202 debug(F100,"gtword LF","",0);
7206 debug(F100,"gtword skipping LF","",0);
7212 if (!TELOPT_U(TELOPT_BINARY) && got_cr) {
7214 debug(F100,"gtword NUL to LF","",0);
7216 debug(F100,"gtword NUL","",0);
7222 if ( !TELOPT_U(TELOPT_BINARY) && got_cr ) {
7223 /* This means the sender is violating Telnet */
7224 /* protocol because we received two CRs in a */
7225 /* row without getting either LF or NUL. */
7226 /* This will not solve the problem but it */
7227 /* will at least allow two CRs to do something */
7228 /* whereas before the user would have to guess */
7229 /* to send LF or NUL after the CR. */
7230 debug(F100,"gtword CR telnet error","",0);
7232 got_cr = 1; /* Remember a CR was received */
7234 /* debug(F100,"gtword CR to LF","",0); */
7240 /* debug(F100,"gtword skipping LF","",0); */
7247 /* debug(F100,"gtword skipping NUL","",0); */
7251 debug(F100,"gtword NUL","",0);
7252 #endif /* COMMENT */
7255 #endif /* COMMENT */
7257 case ETX: /* Ctrl-C... */
7258 case EOT: /* EOT = EOF */
7262 #endif /* CK_LOGIN */
7278 #endif /* CMD_CONINC */
7286 #endif /* CMD_CONINC */
7288 /* debug(F101,"cmdgetc getc","",c); */
7294 #endif /* CMD_CONINC */
7295 c = getchar(); /* RTU doesn't discard the ^Z */
7299 #endif /* datageneral */
7300 return(c); /* Return what we got */
7303 /* #ifdef USE_ARROWKEYS */
7305 /* Mechanism to use for peeking into stdin buffer */
7307 #ifndef USE_FILE_CNT /* stdin->__cnt */
7308 #ifndef USE_FILE__CNT /* Note: two underscores */
7309 #ifdef HPUX /* HPUX 7-11 */
7312 #define USE_FILE__CNT
7316 #ifdef ANYSCO /* SCO UNIX, OSR5, Unixware, etc */
7317 #ifndef OLD_UNIXWARE /* But not Unixware 1.x or 2.0 */
7318 #ifndef UNIXWARE2 /* or 2.1.0 */
7319 #define USE_FILE__CNT
7320 #endif /* UNIXWARE2 */
7321 #endif /* OLD_UNIXWARE */
7324 #endif /* USE_FILE__CNT */
7325 #endif /* USE_FILE_CNT */
7327 #ifndef USE_FILE_R /* stdin->_r */
7328 #ifndef USE_FILE_CNT
7329 #ifndef USE_FILE__CNT
7330 #ifdef BSD44 /* {Free,Open,Net}BSD, BSDI */
7333 #endif /* USE_FILE__CNT */
7334 #endif /* USE_FILE_CNT */
7335 #endif /* USE_FILE_R */
7337 #ifndef USE_FILE_R /* stdin->_cnt */
7338 #ifndef USE_FILE_CNT
7339 #ifndef USE_FILE__CNT
7340 #define USE_FILE_CNT /* Everybody else (but Linux) */
7341 #endif /* USE_FILE__CNT */
7342 #endif /* USE_FILE_CNT */
7343 #endif /* USE_FILE_R */
7349 How many characters are waiting to be read at the console? Normally
7350 conchk() would tell us, but in Unix and VMS cmdgetc() uses stdio getchar(),
7351 thus bypassing coninc()/conchk(), so we have to peek into the stdin buffer,
7352 which is totally nonportable. Which is why this routine is, at least for
7353 now, used only for checking for arrow-key sequences from the keyboard after
7354 an ESC was read. Wouldn't it be nice if the stdio package had a function
7355 that returned the number of bytes waiting to be read from its buffer?
7356 Returns 0 or greater always.
7361 y = pushc ? 1 : 0; /* Have command character pushed? */
7363 x = conchk(); /* Check device-driver buffer */
7366 #ifdef CMD_CONINC /* See cmdgetc() */
7367 x = conchk(); /* Check device-driver buffer */
7369 #else /* CMD_CONINC */
7371 /* Here we must look inside the stdin buffer - highly platform dependent */
7373 #ifdef _IO_file_flags /* Linux */
7374 x = (int) ((stdin->_IO_read_end) - (stdin->_IO_read_ptr));
7375 debug(F101,"cmdconchk _IO_file_flags","",x);
7376 #else /* _IO_file_flags */
7377 #ifdef USE_FILE_CNT /* Traditional */
7379 debug(F101,"cmdconchk (*stdin)->_cnt","",(*stdin)->_cnt);
7383 debug(F101,"cmdconchk NOARROWKEYS x","",0);
7385 debug(F101,"cmdconchk stdin->_cnt","",stdin->_cnt);
7387 #endif /* NOARROWKEYS */
7389 if (x == 0) x = conchk();
7391 #else /* USE_FILE_CNT */
7392 #ifdef USE_FILE__CNT /* HP-UX */
7393 debug(F101,"cmdconchk stdin->__cnt","",stdin->__cnt);
7395 if (x == 0) x = conchk();
7397 #else /* USE_FILE_CNT */
7398 #ifdef USE_FILE_R /* FreeBSD, OpenBSD, etc */
7399 debug(F101,"cmdconchk stdin->_r","",stdin->_r);
7401 if (x == 0) x = conchk();
7404 /* Fill in any others here... */
7406 #endif /* USE_FILE_R */
7407 #endif /* USE_FILE__CNT */
7408 #endif /* USE_FILE_CNT */
7409 #endif /* _IO_file_flags */
7410 #endif /* CMD_CONINC */
7414 /* #endif */ /* USE_ARROWKEYS */
7418 cmdclrscn() { /* Clear the screen */
7422 static VOID /* What to echo at end of command */
7427 #endif /* CK_ANSIC */
7431 extern int inserver;
7432 if (inserver && c == LF)
7437 putchar(c); /* c is the terminating character */
7439 #ifdef WINTCP /* what is this doing here? */
7440 if (c == CR) putchar(NL);
7444 A.A. Chernov, who sent in changes for FreeBSD, said we also needed this
7445 for SVORPOSIX because "setup terminal by termios and curses does
7446 not convert \r to \n, so additional \n needed in newline function." But
7447 it is also very likely to result in unwanted blank lines.
7450 if (c == CR) putchar(NL);
7454 /* OS2 no longer needs this as all CR are converted to NL in coninc() */
7455 /* This eliminates the ugly extra blank lines discussed above. */
7457 if (c == CR) putchar(NL);
7459 #endif /* COMMENT */
7461 if (c == CR) putchar(NL);
7464 if (c == CR) putchar(NL);
7467 if (c == CR) putchar(NL);
7468 #endif /* datageneral */
7470 if (c == CR) putchar(NL);
7473 if (c == CR) putchar(NL);
7474 #endif /* STRATUS */
7478 cmdchardel() { /* Erase a character from the screen */
7484 /* DG '\b' is EM (^y or \031) */
7486 /* Erase a character from non-DG screen, */
7487 dgncoub(1,"\010 \010",3);
7489 #endif /* datageneral */
7502 cmdecho(char c, int quote)
7504 cmdecho(c,quote) char c; int quote;
7505 #endif /* CK_ANSIC */
7513 c = (char)echostars;
7516 /* Echo tty input character c */
7522 putchar((CHAR) (isprint(c) ? c : '^' ));
7524 putchar((CHAR) ((c >= SP && c < DEL) ? c : '^'));
7525 #endif /* isprint */
7530 if (quote==1 && c==CR) putchar((CHAR) NL);
7536 /* Return pointer to current position in command buffer. */
7551 /* X X E S C -- Interprets backslash codes */
7552 /* Returns the int value of the backslash code if it is > -1 and < 256 */
7553 /* and updates the string pointer to first character after backslash code. */
7554 /* If the argument is invalid, leaves pointer unchanged and returns -1. */
7557 xxesc(s) char **s; { /* Expand backslash escapes */
7558 int x, y, brace, radix; /* Returns the int value */
7559 char hd = '9'; /* Highest digit in radix */
7562 p = *s; /* pointer to beginning */
7563 if (!p) return(-1); /* watch out for null pointer */
7564 x = *p++; /* character at beginning */
7565 if (x != CMDQ) return(-1); /* make sure it's a backslash code */
7567 x = *p; /* it is, get the next character */
7568 if (x == '{') { /* bracketed quantity? */
7569 p++; /* begin past bracket */
7573 switch (x) { /* Start interpreting */
7574 case 'd': /* Decimal radix indicator */
7576 p++; /* Just point past it and fall thru */
7577 case '0': /* Starts with digit */
7579 case '2': case '3': case '4': case '5':
7580 case '6': case '7': case '8': case '9':
7581 radix = 10; /* Decimal */
7582 hd = '9'; /* highest valid digit */
7584 case 'o': /* Starts with o or O */
7586 radix = 8; /* Octal */
7587 hd = '7'; /* highest valid digit */
7588 p++; /* point past radix indicator */
7590 case 'x': /* Starts with x or X */
7592 radix = 16; /* Hexadecimal */
7593 p++; /* point past radix indicator */
7595 default: /* All others */
7597 *s = p+1; /* Treat as quote of next char */
7601 #endif /* COMMENT */
7603 /* For OS/2, there are "wide" characters required for the keyboard
7604 * binding, i.e \644 and similar codes larger than 255 (byte).
7605 * For this purpose, give up checking for < 256. If someone means
7606 * \266 should result in \26 followed by a "6" character, he should
7607 * always write \{26}6 anyway. Now, return only the lower byte of
7608 * the result, i.e. 10, but eat up the whole \266 sequence and
7609 * put the wide result 266 into a global variable. Yes, that's not
7610 * the most beautiful programming style but requires the least
7611 * amount of changes to other routines.
7613 if (*p == '{') { /* Sun May 11 20:00:40 2003 */
7614 brace = 1; /* Allow {} after radix indicator */
7617 if (radix <= 10) { /* Number in radix 8 or 10 */
7619 (*p) && (*p >= '0') && (*p <= hd)
7621 && (y < 5) && (x*radix < KMSIZE);
7622 /* the maximum needed value \8196 is 4 digits long */
7623 /* while as octal it requires \1377, i.e. 5 digits */
7625 && (y < 3) && (x*radix < 256);
7628 x = x * radix + (int) *p - 48;
7631 wideresult = x; /* Remember wide result */
7634 if (y == 0 || x > 255) { /* No valid digits? */
7635 *s = p; /* point after it */
7636 return(-1); /* return failure. */
7638 } else if (radix == 16) { /* Special case for hex */
7639 if ((x = unhex(*p++)) < 0) { *s = p - 1; return(-1); }
7640 if ((y = unhex(*p++)) < 0) { *s = p - 2; return(-1); }
7641 x = ((x << 4) & 0xF0) | (y & 0x0F);
7644 if ((y = unhex(*p)) >= 0) {
7646 wideresult = ((x << 4) & 0xFF0) | (y & 0x0F);
7647 x = wideresult & 255;
7651 if (brace && *p == '}' && x > -1) /* Point past closing brace, if any */
7653 *s = p; /* Point to next char after sequence */
7654 return(x); /* Return value of sequence */
7657 int /* Convert hex string to int */
7662 #endif /* CK_ANSIC */
7665 if (x >= '0' && x <= '9') /* 0-9 is offset by hex 30 */
7667 else if (x >= 'A' && x <= 'F') /* A-F offset by hex 37 */
7669 else if (x >= 'a' && x <= 'f') /* a-f offset by hex 57 */
7670 return(x - 0x57); /* (obviously ASCII dependent) */
7674 /* L O O K U P -- Lookup the string in the given array of strings */
7677 Call this way: v = lookup(table,word,n,&x);
7679 table - a 'struct keytab' table.
7680 word - the target string to look up in the table.
7681 n - the number of elements in the table.
7682 x - address of an integer for returning the table array index,
7683 or NULL if you don't need a table index.
7685 The keyword table must be arranged in ascending alphabetical order;
7686 alphabetic case doesn't matter but letters are treated as lowercase
7687 for purposes of ordering; thus "^" and "_" come *before* the letters,
7690 Returns the keyword's associated value (zero or greater) if found,
7691 with the variable x set to the keyword-table index. If is lookup()
7692 is not successful, it returns:
7694 -3 if nothing to look up (target was null),
7698 A match is successful if the target matches a keyword exactly, or if
7699 the target is a prefix of exactly one keyword. It is ambiguous if the
7700 target matches two or more keywords from the table.
7702 Lookup() is the critical routine in scripts and so is optimized with a
7703 simple static cache plus some other tricks. Maybe it could be improved
7704 further with binary search or hash techniques but I doubt it since most
7705 keyword tables are fairly short.
7708 #ifdef USE_LUCACHE /* Lookup cache */
7709 extern int lusize; /* (initialized in ckuus5.c) */
7710 extern char * lucmd[];
7713 extern struct keytab * lutab[];
7718 #endif /* USE_LUCACHE */
7721 lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {
7724 int v, len, cmdlen = 0;
7725 char c = NUL, c1, *s;
7727 /* Get 1st char of search object, if it's null return -3. */
7729 if (!cmd || n < 1) /* Defense de nullarg */
7731 c1 = *cmd; /* First character */
7732 if (!c1) /* Make sure there is one */
7734 if (isupper(c1)) /* If letter make it lowercase */
7737 #ifdef USE_LUCACHE /* lookup() cache */
7739 lucalls++; /* Count this lookup() call */
7740 for (i = 0; i < m; i++) { /* Loop thru cache */
7741 if (*(lucmd[i]) == c1) { /* Same as 1st char of search item? */
7742 if (lutab[i] == table) { /* Yes - same table too? */
7743 if (!strcmp(cmd,lucmd[i])) { /* Yes - compare */
7744 if (x) *x = luidx[i]; /* Match - return index */
7745 luhits++; /* Count cache hit */
7746 return(luval[i]); /* Return associated value */
7751 #endif /* USE_LUCACHE */
7753 /* Not null, not in cache, look it up */
7756 while (*s++) cmdlen++; /* Length of target */
7758 Quick binary search to find last table entry whose first character is
7759 lexically less than the first character of the search object. This is
7760 the starting point of the next loop, which must go in sequence since it
7761 compares adjacent table entries.
7763 if (n < 5) { /* Not worth it for small tables */
7769 while (lo+2 < hi && ++count < 12) {
7770 i = lo + ((hi - lo) / 2);
7771 c = *(table[i].kwd);
7772 if (isupper(c)) c = tolower(c);
7779 i = (c < c1) ? lo+1 : lo;
7781 if (i > 0) xxhits++;
7782 #endif /* USE_LUCACHE */
7784 for ( ; i < n-1; i++) {
7787 #endif /* USE_LUCACHE */
7789 c = *(table[i].kwd);
7791 if (isupper(c)) c = tolower(c);
7793 /* The following is a big performance booster but makes it */
7794 /* absolutely essential that all lookup() tables are in order. */
7796 if (c > c1) /* Leave early if past our mark */
7800 /* Use LOG DEBUG to check */
7803 if (ckstrcmp(table[i].kwd,table[i+1].kwd,0,0) > 0) {
7804 printf("TABLE OUT OF ORDER [%s] [%s]\n",
7805 table[i].kwd,table[i+1].kwd);
7815 if ((len == cmdlen && !ckstrcmp(table[i].kwd,cmd,len,0)) ||
7816 ((v = !ckstrcmp(table[i].kwd,cmd,cmdlen,0)) &&
7817 ckstrcmp(table[i+1].kwd,cmd,cmdlen,0))) {
7819 return(table[i].kwval);
7823 if (v) { /* Ambiguous */
7824 if (x) *x = i; /* Set index of first match */
7829 /* Last (or only) element */
7831 if (!ckstrcmp(table[n-1].kwd,cmd,cmdlen,0)) {
7833 /* debug(F111,"lookup",table[i].kwd,table); */
7834 return(table[n-1].kwval);
7841 Like lookup, but requires a full (but case-independent) match
7842 and does NOT require the table to be in order.
7845 xlookup(table,cmd,n,x) struct keytab table[]; char *cmd; int n, *x; {
7847 int len, cmdlen, one = 0;
7848 register char c, c2, * s, * s2;
7850 if (!cmd) cmd = ""; /* Check args */
7851 if (!*cmd || n < 1) return(-3);
7853 c = *cmd; /* First char of string to look up */
7854 if (!*(cmd+1)) { /* Special handling for 1-char names */
7862 while (*s++) cmdlen++;
7870 for (i = 0; i < n; i++) {
7871 s = table[i].kwd; /* This entry */
7873 if (!*s) continue; /* Empty table entry */
7875 if (isupper(c2)) c2 = tolower(c2);
7876 if (c != c2) continue; /* First char doesn't match */
7877 if (one) { /* Name is one char long */
7881 return(table[i].kwval); /* So is table entry */
7883 } else { /* Otherwise do string comparison */
7886 while (*s2++) len++;
7887 if (len == cmdlen && !ckstrcmp(s,cmd,-1,0)) {
7889 return(table[i].kwval);
7896 /* Reverse lookup */
7899 rlookup(table,n,x) struct keytab table[]; int n, x; {
7901 for (i = 0; i < n; i++) {
7902 if (table[i].kwval == x)
7903 return(table[i].kwd);