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 */
193 #include <errno.h> /* Error number symbols */
198 #define INCL_VIO /* Needed for ckocon.h */
210 #define cc ccount /* OS-9/68K compiler bug */
213 #ifdef GEMDOS /* Atari ST */
217 #define putchar(x) conoc(x)
221 extern int cmdadl, justone;
222 #endif /* CK_AUTODL */
224 extern int timelimit, nzxopts, nopush, nolocal, xcmdsrc, keepallchars;
228 #ifdef CKXPRINTF /* Our printf macro conflicts with */
229 #undef printf /* use of "printf" in syslog.h */
230 #endif /* CKXPRINTF */
232 #include <sys/syslog.h>
237 #define printf ckxprintf
238 #endif /* CKXPRINTF */
240 #endif /* CKSYSLOG */
242 /* Local variables */
245 int psetf = 0, /* Flag that prompt has been set */
246 cc = 0, /* Character count */
247 dpx = 0, /* Duplex (0 = full) */
248 inword = 0; /* In the middle of getting a word */
250 char *dfprom = "Command? "; /* Default prompt */
252 char *lastfile = NULL; /* Last filespec */
253 static char *tmplastfile = NULL; /* Last filespec candidate */
254 #endif /* NOLASTFILE */
256 int cmflgs; /* Command flags */
257 int cmfsav; /* A saved version of them */
259 static char pushc = NUL;
260 static char brkchar = NUL;
262 #define CMDEFAULT 1023
263 static char cmdefault[CMDEFAULT+1];
266 char *cmdbuf = NULL; /* Command buffer */
267 char *savbuf = NULL; /* Buffer to save copy of command */
268 char *atmbuf = NULL; /* Atom buffer - for current field */
269 char *atxbuf = NULL; /* For expanding the atom buffer */
270 char *prevcmd = NULL;
271 static char *atybuf = NULL; /* For copying atom buffer */
272 static char *filbuf = NULL; /* File name buffer */
273 static char *cmprom = NULL; /* Program's prompt */
274 static char *cmprxx = NULL; /* Program's prompt, unevaluated */
278 Command recall is available only if we can make profligate use of malloc().
280 #define R_MAX 10 /* How many commands to save */
281 int cm_recall = R_MAX; /* Size of command recall buffer */
282 int on_recall = 1; /* Recall feature is ON */
283 static int no_recall = 0; /* Recall OFF for this cmd only */
284 static int force_add = 0; /* Force cmd into recall buffer */
285 static int last_recall = -1; /* Last recall-related action */
288 0 = CR (a command was entered)
292 int in_recall = 0; /* Recall buffers are init'd */
294 current = -1, /* Pointer to current command */
295 rlast = -1; /* Index of last command in buffer */
296 static char **recall = NULL; /* Array of recall buffer pointers */
297 #endif /* CK_RECALL */
299 char cmdbuf[CMDBL+4]; /* Command buffer */
300 char savbuf[CMDBL+4]; /* Buffer to save copy of command */
301 char atmbuf[ATMBL+4]; /* Atom buffer */
302 char atxbuf[CMDBL+4]; /* For expanding the atom buffer */
303 char prevcmd[CMDBL+4]; /* For displaying the last command */
304 static char atybuf[ATMBL+4]; /* For copying atom buffer */
305 static char filbuf[ATMBL+4]; /* File name buffer */
306 static char cmprom[PROMPTL+1]; /* Program's prompt */
307 static char cmprxx[PROMPTL+1]; /* Program's prompt, unevaluated */
310 /* Command buffer pointers */
312 #define PPVLEN VNAML /* 20080305 Wolfram Sang (was 24) */
313 char ppvnambuf[PPVLEN+1] = { NUL, NUL };
315 char * cmbptr = NULL; /* Current position (for export) */
317 static char *bp, /* Current command buffer position */
318 *pp, /* Start of current field */
319 *np; /* Start of next field */
321 static int ungw, /* For ungetting words */
322 atxn; /* Expansion buffer (atxbuf) length */
325 extern int wideresult;
328 extern int cmd_cols, cmd_rows, local, quiet;
337 _PROTOTYP( static int gtword, (int) );
338 _PROTOTYP( static int addbuf, (char *) );
339 _PROTOTYP( static int setatm, (char *, int) );
340 _PROTOTYP( static VOID cmdnewl, (char) );
341 _PROTOTYP( static VOID cmdchardel, (void) );
342 _PROTOTYP( static VOID cmdecho, (char, int) );
343 _PROTOTYP( static int test, (int, int) );
345 _PROTOTYP( extern char *strchr, (char *, int) );
350 /* The following are for use with chained FDB's */
352 static int crflag = 0; /* Carriage return was typed */
353 static int qmflag = 0; /* Question mark was typed */
354 static int esflag = 0; /* Escape was typed */
356 /* Directory separator */
359 static char dirsep = '\\';
362 static char dirsep = ':';
365 static char dirsep = ':';
368 static char dirsep = '.';
371 static char dirsep = '>';
373 static char dirsep = '/'; /* UNIX, OS/2, OS-9, Amiga, etc. */
377 #endif /* datageneral */
380 /* H A S N O P A T H */
382 /* Returns 0 if filespec s includes any path segments; 1 if it doesn't. */
385 hasnopath(s) char * s; {
390 return(ckstrcmp(s,p,CKMAXPATH,filecase) == 0 ? 1 : 0);
393 /* C K S P R E A D -- Print string double-spaced */
395 static char * sprptr = NULL;
398 ckspread(s) char * s; {
404 sprptr = malloc(n + n + 3);
413 return(sprptr ? sprptr : "");
416 /* T E S T -- Bit test */
419 test(x,m) int x, m; { /* Returns 1 if any bits from m are on in x, else 0 */
420 return((x & m) ? 1 : 0);
423 /* K W D H E L P -- Given a keyword table, print keywords in columns. */
427 n - number of entries
428 pat - pattern (left substring) that must match for each keyword
429 pre - prefix to add to each keyword
430 post - suffix to add to each keyword
431 off - offset on first screenful, allowing room for introductory text
432 xhlp - 1 to print any CM_INV keywords that are not also abbreviations.
433 2 to print CM_INV keywords if CM_HLP also set
434 4 if it's a switch table (to show ':' if CM_ARG)
436 Arranges keywords in columns with width based on longest keyword.
437 Does "more?" prompting at end of screen.
438 Uses global cmd_rows and cmd_cols for screen size.
441 kwdhelp(s,n,pat,pre,post,off,xhlp)
442 struct keytab s[]; int n, off, xhlp; char *pat, *pre, *post;
447 int cols, height, i, j, k, lc, n2 = 0;
448 char *b = NULL, *p, *q;
455 if (!s) return; /* Nothing to do */
456 if (n < 1) return; /* Ditto */
457 if (off < 0) off = 0; /* Offset for first page */
458 if (!pre) pre = ""; /* Handle null string pointers */
459 if (!post) post = "";
460 lc = off; /* Screen-line counter */
462 if (xhlp & 4) /* For switches */
463 tmpbuf = (char *)malloc(TMPBUFSIZ+1);
465 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
466 for (i = 0; i < n; i++) { /* Find longest keyword */
468 if (ckstrcmp(s[i].kwd,pat,cc,0))
471 if (s[i].flgs & CM_PSH /* NOPUSH or nopush screening */
477 if (s[i].flgs & CM_LOC /* NOLOCAL or nolocal screening */
484 if (s[i].flgs & CM_INV) {
486 /* This code does not show invisible keywords at all except for "help ?" */
487 /* and then only help topics (CM_HLP) in the top-level keyword list. */
491 else if ((s[i].flgs & CM_HLP) == 0)
494 /* This code shows invisible keywords that are not also abbreviations when */
495 /* ? was typed AFTER the beginning of the field so the user can find out */
496 /* what they are and (for example) why completion doesn't work at this point */
498 if (s[i].flgs & CM_ABR)
500 else if ((xhlp & 3) == 0)
502 else if ((xhlp & 2) && ((s[i].flgs & CM_HLP) == 0))
506 j = strlen(s[i].kwd);
507 if (!(xhlp & 4) || !tmpbuf) { /* Regular keyword table */
508 s2[n2++] = s[i].kwd; /* Copy pointers to visible ones */
509 } else { /* Switches */
510 ckmakmsg(tmpbuf, /* Make a copy that shows ":" if */
511 TMPBUFSIZ, /* the switch takes an argument. */
513 (s[i].flgs & CM_ARG) ? ":" : "",
517 makestr(&(s2[n2]),tmpbuf);
518 if (s[i].flgs & CM_ARG) j++;
527 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
530 width += (int)strlen(pre) + (int)strlen(post) + 2;
531 cols = cmd_cols / width; /* How many columns? */
532 if (cols < 1) cols = 1;
533 height = n / cols; /* How long is each column? */
534 if (n % cols) height++; /* Add one for remainder, if any */
536 for (i = 0; i < height; i++) { /* Loop for each row */
537 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
539 for (j = 0; j < cols; j++) { /* Loop for each column in row */
540 k = i + (j * height); /* Index of next keyword */
541 if (k < n) { /* In range? */
544 p = s2[k]; /* Point to verb name */
545 q = b + (j * width) + 1; /* Where to copy it to */
546 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
547 q--; /* Back up over NUL */
548 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
549 q--; /* Back up over NUL */
550 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
553 *q = SP; /* Replace the space */
557 p = b + cmd_cols - 1; /* Last char in line */
558 while (*p-- == SP) ; /* Trim */
560 printf("%s\n",b); /* Print the line */
561 if (++lc > (cmd_rows - 2)) { /* Screen full? */
562 if (!askmore()) /* Do more-prompting... */
568 /* printf("\n"); */ /* Blank line at end of report */
569 } else { /* Malloc failure, no columns */
570 for (i = 0; i < n; i++) {
571 if (s[i].flgs & CM_INV) /* Use original keyword table */
572 continue; /* skipping invisible entries */
573 printf("%s%s%s\n",pre,s[i].kwd,post);
574 if (++lc > (cmd_rows - 2)) { /* Screen full? */
575 if (!askmore()) /* Do more-prompting... */
584 if (tmpbuf) free((char *)tmpbuf);
585 for (i = 0; i < n; i++)
586 if (s2[i]) free(s2[i]);
588 if (s2) free(s2); /* Free array copy */
589 if (b) free(b); /* Free line buffer */
593 /* X F I L H E L P -- Given a file list, print names in columns. */
596 n - number of entries
597 pre - prefix to add to each filename
598 post - suffix to add to each filename
599 off - offset on first screenful, allowing room for introductory text
600 cmdirflg - 1 if only directory names should be listed, 0 to list all files
601 fs - call fileselect() to decide whether to include each file.
602 The rest of the args are the same as for fileselect().
604 Arranges filenames in columns with width based on longest filename.
605 Does "more?" prompting at end of screen.
606 Uses global cmd_rows and cmd_cols for screen size.
612 int n, char *pre, char *post, int off, int cmdirflag,
613 int fs, char *sa, char *sb, char *sna, char *snb,
614 CK_OFF_T minsiz, CK_OFF_T maxsiz,
619 xfilhelp(n,pre,post,off,cmdirflg,
620 fs,sa,sb,sna,snb,minsiz,maxsiz,nbu,nxlist,xlist)
621 int n, off; char *pre, *post; int cmdirflg;
622 int fs; char *sa,*sb,*sna,*snb; CK_OFF_T minsiz,maxsiz;
623 int nbu,nxlist; char ** xlist;
624 #endif /* CK_ANSIC */
626 char filbuf[CKMAXPATH + 1]; /* Temp buffer for one filename */
628 int cols, height, i, j, k, lc, n2 = 0, rc = 0, itsadir = 0;
629 char *b = NULL, *p, *q;
633 char * cdp = zgtdir();
636 if (n < 1) return(0);
637 if (off < 0) off = 0; /* Offset for first page */
638 if (!pre) pre = ""; /* Handle null string pointers */
639 if (!post) post = "";
641 lc = off; /* Screen-line counter */
643 if ((s2 = (char **) malloc(n * sizeof(char *)))) {
644 for (i = 0; i < n; i++) { /* Loop through filenames */
646 s2[i] = NULL; /* Initialize each pointer to NULL */
647 znext(filbuf); /* Get next filename */
648 if (!filbuf[0]) /* Shouldn't happen */
651 itsadir = isdir(filbuf); /* Is it a directory? */
652 if (cmdirflg && !itsadir) /* No, listing directories only? */
653 continue; /* So skip this one. */
655 if (fs) if (fileselect(filbuf,
657 minsiz,maxsiz,nbu,nxlist,xlist) < 1) {
661 ckstrncpy(filbuf,zrelname(filbuf,cdp),CKMAXPATH);
665 if (itsadir && j < CKMAXPATH - 1 && j > 0) {
666 if (filbuf[j-1] != dirsep) {
667 filbuf[j++] = dirsep;
672 if (!(s2[n2] = malloc(j+1))) {
673 printf("?Memory allocation failure\n");
677 if (j <= CKMAXPATH) {
678 strcpy(s2[n2],filbuf);
681 printf("?Name too long - %s\n", filbuf);
685 if (j > width) /* Get width of widest one */
688 n = n2; /* How many we actually got */
690 sh_sort(s2,NULL,n,0,0,filecase); /* Alphabetize the list */
693 if (s2 && (b = (char *) malloc(cmd_cols + 1))) { /* Make a line buffer */
696 width += (int)strlen(pre) + (int)strlen(post) + 2;
697 cols = cmd_cols / width; /* How many columns? */
698 if (cols < 1) cols = 1;
699 height = n / cols; /* How long is each column? */
700 if (n % cols) height++; /* Add one for remainder, if any */
702 for (i = 0; i < height; i++) { /* Loop for each row */
703 for (j = 0; j < cmd_cols; j++) /* First fill row with blanks */
705 for (j = 0; j < cols; j++) { /* Loop for each column in row */
706 k = i + (j * height); /* Index of next filename */
707 if (k < n) { /* In range? */
710 p = s2[k]; /* Point to filename */
711 q = b + (j * width) + 1; /* and destination */
712 while ((q < bx) && (*q++ = *pa++)) ; /* Copy prefix */
713 q--; /* Back up over NUL */
714 while ((q < bx) && (*q++ = *p++)) ; /* Copy filename */
715 q--; /* Back up over NUL */
716 while ((q < bx) && (*q++ = *px++)) ; /* Copy suffix */
719 *q = SP; /* Replace the space */
723 p = b + cmd_cols - 1; /* Last char in line */
724 while (*p-- == SP) ; /* Trim */
726 printf("%s\n",b); /* Print the line */
727 if (++lc > (cmd_rows - 2)) { /* Screen full? */
728 if (!askmore()) { /* Do more-prompting... */
735 printf("\n"); /* Blank line at end of report */
737 } else { /* Malloc failure, no columns */
738 for (i = 0; i < n; i++) {
740 if (!filbuf[0]) break;
741 printf("%s%s%s\n",pre,filbuf,post);
742 if (++lc > (cmd_rows - 2)) { /* Screen full? */
743 if (!askmore()) { /* Do more-prompting... */
751 for (i = 0; i < n2; i++)
752 if (s2[i]) free(s2[i]);
753 if (s2) free((char *)s2);
759 Simpler front end for xfilhelp() with shorter arg list when no
760 file selection is needed.
763 filhelp(n,pre,post,off,cmdirflg) int n, off; char *pre, *post; int cmdirflg; {
764 return(xfilhelp(n,pre,post,off,cmdirflg,
765 0,NULL,NULL,NULL,NULL,
766 (CK_OFF_T)0,(CK_OFF_T)0,0,0,(char **)NULL));
769 /* C M S E T U P -- Set up command buffers */
774 if (!(cmdbuf = malloc(CMDBL + 4))) return(-1);
775 if (!(savbuf = malloc(CMDBL + 4))) return(-1);
777 if (!(prevcmd = malloc(CMDBL + 4))) return(-1);
779 if (!(atmbuf = malloc(ATMBL + 4))) return(-1);
780 if (!(atxbuf = malloc(CMDBL + 4))) return(-1);
781 if (!(atybuf = malloc(ATMBL + 4))) return(-1);
782 if (!(filbuf = malloc(ATMBL + 4))) return(-1);
783 if (!(cmprom = malloc(PROMPTL + 4))) return(-1);
784 if (!(cmprxx = malloc(PROMPTL + 4))) return(-1);
787 #endif /* CK_RECALL */
792 /* C M S E T P -- Set the program prompt. */
797 ckstrncpy(cmprxx,s,PROMPTL);
798 psetf = 1; /* Flag that prompt has been set. */
801 /* C M S A V P -- Save a copy of the current prompt. */
805 cmsavp(char s[], int n)
807 cmsavp(s,n) char s[]; int n;
808 #endif /* CK_ANSIC */
810 if (psetf) /* But not if no prompt is set. */
811 ckstrncpy(s,cmprxx,n);
829 /* P R O M P T -- Issue the program prompt. */
832 prompt(f) xx_strp f; {
833 char *sx, *sy; int n;
835 extern int ssl_active_flag, tls_active_flag;
838 extern int display_demo;
840 /* If there is a demo screen to be displayed, display it */
841 if (display_demo && xcmdsrc == 0) {
847 if (psetf == 0) /* If no prompt set, set default. */
850 sx = cmprxx; /* Unevaluated copy */
851 if (f) { /* If conversion function given */
852 sy = cmprom; /* Evaluate it */
854 debug(F101,"prompt sx","",sx);
855 debug(F101,"prompt sy","",sy);
858 if ((*f)(sx,&sy,&n) < 0) /* If evaluation failed */
859 sx = cmprxx; /* revert to unevaluated copy */
860 else if (!*cmprom) /* ditto if it came up empty */
865 ckstrncpy(cmprom,sx,PROMPTL);
866 cmprom[PROMPTL-1] = NUL;
867 if (!*sx) /* Don't print if empty */
877 if (inserver) { /* Print the prompt. */
878 ttoc(CR); /* If TELNET Server */
879 ttoc(NUL); /* must folloW CR by NUL */
885 if (!(ssl_active_flag || tls_active_flag))
887 fflush(stdout); /* Now! */
894 pushcmd(s) char * s; { /* For use with IF command. */
896 ckstrncpy(savbuf,s,CMDBL); /* Save the dependent clause, */
897 cmres(); /* and clear the command buffer. */
898 debug(F110, "pushcmd savbuf", savbuf, 0);
902 pushqcmd(s) char * s; { /* For use with ELSE command. */
903 char c, * p = savbuf; /* Dest */
904 if (!s) s = np; /* Source */
905 while (*s) { /* Get first nonwhitespace char */
911 if (*s != '{') { /* If it's not "{" */
912 pushcmd(s); /* do regular pushcmd */
915 while ((c = *s++)) { /* Otherwise insert quotes */
920 cmres(); /* and clear the command buffer. */
921 debug(F110, "pushqcmd savbuf", savbuf, 0);
926 /* no longer used... */
929 ckstrncpy(cmdbuf,savbuf,CMDBL); /* Put back the saved material */
930 *savbuf = '\0'; /* and clear the save buffer */
935 /* C M R E S -- Reset pointers to beginning of command buffer. */
939 inword = 0; /* We're not in a word */
940 cc = 0; /* Character count is zero */
942 /* Initialize pointers */
944 pp = cmdbuf; /* Beginning of current field */
945 bp = cmdbuf; /* Current position within buffer */
946 np = cmdbuf; /* Where to start next field */
949 cmflgs = -5; /* Parse not yet started. */
950 ungw = 0; /* Don't need to unget a word. */
953 /* C M I N I -- Clear the command and atom buffers, reset pointers. */
956 The argument specifies who is to echo the user's typein --
957 1 means the cmd package echoes
958 0 somebody else (system, front end, terminal) echoes
965 fatal("fatal error: unable to allocate command buffers");
968 memset(cmdbuf,0,CMDBL);
969 memset(atmbuf,0,ATMBL);
971 for (bp = cmdbuf; bp < cmdbuf+CMDBL; bp++) *bp = NUL;
972 for (bp = atmbuf; bp < atmbuf+ATMBL; bp++) *bp = NUL;
973 #endif /* USE_MEMCPY */
975 *atmbuf = *savbuf = *atxbuf = *atybuf = *filbuf = NUL;
976 blocklvl = 0; /* Block level is 0 */
977 linebegin = 1; /* At the beginning of a line */
978 dpx = d; /* Global copy of the echo flag */
979 debug(F101,"cmini dpx","",dpx);
980 crflag = 0; /* Reset flags */
984 no_recall = 0; /* Start out with recall enabled */
985 #endif /* CK_RECALL */
986 cmres(); /* Sets bp etc */
987 newcmd = 1; /* See addcmd() */
992 The following bits are to allow the command package to call itself
993 in the middle of a parse. To do this, begin by calling cmpush, and
994 end by calling cmpop. As you can see, this is rather expensive.
998 int i[5]; /* stack for integers */
999 char *c[3]; /* stack for pointers */
1000 char *b[8]; /* stack for buffer contents */
1002 struct cmp *cmp = 0;
1004 int cmp_i[CMDDEP+1][5]; /* Stack for integers */
1005 char *cmp_c[CMDDEP+1][5]; /* for misc pointers */
1006 char *cmp_b[CMDDEP+1][7]; /* for buffer contents pointers */
1007 #endif /* DCMDBUF */
1009 int cmddep = -1; /* Current stack depth */
1012 cmpush() { /* Save the command environment */
1013 char *cp; /* Character pointer */
1015 if (cmddep >= CMDDEP) /* Enter a new command depth */
1018 debug(F101,"&cmpush to depth","",cmddep);
1021 /* allocate memory for cmp if not already done */
1022 if (!cmp && !(cmp = (struct cmp *) malloc(sizeof(struct cmp)*(CMDDEP+1))))
1023 fatal("cmpush: no memory for cmp");
1024 cmp[cmddep].i[0] = cmflgs; /* First do the global ints */
1025 cmp[cmddep].i[1] = cmfsav;
1026 cmp[cmddep].i[2] = atxn;
1027 cmp[cmddep].i[3] = ungw;
1029 cmp[cmddep].c[0] = bp; /* Then the global pointers */
1030 cmp[cmddep].c[1] = pp;
1031 cmp[cmddep].c[2] = np;
1033 cmp_i[cmddep][0] = cmflgs; /* First do the global ints */
1034 cmp_i[cmddep][1] = cmfsav;
1035 cmp_i[cmddep][2] = atxn;
1036 cmp_i[cmddep][3] = ungw;
1038 cmp_c[cmddep][0] = bp; /* Then the global pointers */
1039 cmp_c[cmddep][1] = pp;
1040 cmp_c[cmddep][2] = np;
1041 #endif /* DCMDBUF */
1043 /* Now the buffers themselves. A lot of repititious code... */
1046 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1047 if (cp) strcpy(cp,cmdbuf);
1048 cmp[cmddep].b[0] = cp;
1049 if (cp == NULL) return(-1);
1051 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1052 if (cp) strcpy(cp,savbuf);
1053 cmp[cmddep].b[1] = cp;
1054 if (cp == NULL) return(-1);
1056 cmp[cmddep].b[2] = NULL;
1058 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1059 if (cp) strcpy(cp,atmbuf);
1060 cmp[cmddep].b[3] = cp;
1061 if (cp == NULL) return(-1);
1063 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1064 if (cp) strcpy(cp,atxbuf);
1065 cmp[cmddep].b[4] = cp;
1066 if (cp == NULL) return(-1);
1068 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1069 if (cp) strcpy(cp,atybuf);
1070 cmp[cmddep].b[5] = cp;
1071 if (cp == NULL) return(-1);
1073 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1074 if (cp) strcpy(cp,filbuf);
1075 cmp[cmddep].b[6] = cp;
1076 if (cp == NULL) return(-1);
1078 cp = malloc((int)strlen(cmdbuf)+1); /* 0: Command buffer */
1079 if (cp) strcpy(cp,cmdbuf);
1080 cmp_b[cmddep][0] = cp;
1081 if (cp == NULL) return(-1);
1083 cp = malloc((int)strlen(savbuf)+1); /* 1: Save buffer */
1084 if (cp) strcpy(cp,savbuf);
1085 cmp_b[cmddep][1] = cp;
1086 if (cp == NULL) return(-1);
1088 cmp_b[cmddep][2] = NULL;
1090 cp = malloc((int)strlen(atmbuf)+1); /* 3: Atom buffer */
1091 if (cp) strcpy(cp,atmbuf);
1092 cmp_b[cmddep][3] = cp;
1093 if (cp == NULL) return(-1);
1095 cp = malloc((int)strlen(atxbuf)+1); /* 4: Expansion buffer */
1096 if (cp) strcpy(cp,atxbuf);
1097 cmp_b[cmddep][4] = cp;
1098 if (cp == NULL) return(-1);
1100 cp = malloc((int)strlen(atybuf)+1); /* 5: Atom buffer copy */
1101 if (cp) strcpy(cp,atybuf);
1102 cmp_b[cmddep][5] = cp;
1103 if (cp == NULL) return(-1);
1105 cp = malloc((int)strlen(filbuf)+1); /* 6: File name buffer */
1106 if (cp) strcpy(cp,filbuf);
1107 cmp_b[cmddep][6] = cp;
1108 if (cp == NULL) return(-1);
1109 #endif /* DCMDBUF */
1111 cmini(dpx); /* Initize the command parser */
1116 cmpop() { /* Restore the command environment */
1118 debug(F100,"&cmpop called from top level","",0);
1119 return(-1); /* Don't pop too much! */
1122 cmflgs = cmp[cmddep].i[0]; /* First do the global ints */
1123 cmfsav = cmp[cmddep].i[1];
1124 atxn = cmp[cmddep].i[2];
1125 ungw = cmp[cmddep].i[3];
1127 bp = cmp[cmddep].c[0]; /* Then the global pointers */
1128 pp = cmp[cmddep].c[1];
1129 np = cmp[cmddep].c[2];
1131 cmflgs = cmp_i[cmddep][0]; /* First do the global ints */
1132 cmfsav = cmp_i[cmddep][1];
1133 atxn = cmp_i[cmddep][2];
1134 ungw = cmp_i[cmddep][3];
1136 bp = cmp_c[cmddep][0]; /* Then the global pointers */
1137 pp = cmp_c[cmddep][1];
1138 np = cmp_c[cmddep][2];
1139 #endif /* DCMDBUF */
1141 /* Now the buffers themselves. */
1142 /* Note: strncpy(), not ckstrncpy() -- Here we WANT the NUL padding... */
1145 if (cmp[cmddep].b[0]) {
1147 strncpy(cmdbuf,cmp[cmddep].b[0],CMDBL); /* 0: Command buffer */
1148 free(cmp[cmddep].b[0]);
1149 cmp[cmddep].b[0] = NULL;
1151 if (cmp[cmddep].b[1]) {
1152 strncpy(savbuf,cmp[cmddep].b[1],CMDBL); /* 1: Save buffer */
1153 free(cmp[cmddep].b[1]);
1154 cmp[cmddep].b[1] = NULL;
1156 if (cmp[cmddep].b[3]) {
1157 strncpy(atmbuf,cmp[cmddep].b[3],ATMBL); /* 3: Atomic buffer! */
1158 free(cmp[cmddep].b[3]);
1159 cmp[cmddep].b[3] = NULL;
1161 if (cmp[cmddep].b[4]) {
1162 strncpy(atxbuf,cmp[cmddep].b[4],ATMBL); /* 4: eXpansion buffer */
1163 free(cmp[cmddep].b[4]);
1164 cmp[cmddep].b[4] = NULL;
1166 if (cmp[cmddep].b[5]) {
1167 strncpy(atybuf,cmp[cmddep].b[5],ATMBL); /* 5: Atom buffer copY */
1168 free(cmp[cmddep].b[5]);
1169 cmp[cmddep].b[5] = NULL;
1171 if (cmp[cmddep].b[6]) {
1172 strncpy(filbuf,cmp[cmddep].b[6],ATMBL); /* 6: Filename buffer */
1173 free(cmp[cmddep].b[6]);
1174 cmp[cmddep].b[6] = NULL;
1177 if (cmp_b[cmddep][0]) {
1178 strncpy(cmdbuf,cmp_b[cmddep][0],CMDBL); /* 0: Command buffer */
1179 free(cmp_b[cmddep][0]);
1180 cmp_b[cmddep][0] = NULL;
1182 if (cmp_b[cmddep][1]) {
1183 strncpy(savbuf,cmp_b[cmddep][1],CMDBL); /* 1: Save buffer */
1184 free(cmp_b[cmddep][1]);
1185 cmp_b[cmddep][1] = NULL;
1187 if (cmp_b[cmddep][3]) {
1188 strncpy(atmbuf,cmp_b[cmddep][3],ATMBL); /* 3: Atomic buffer! */
1189 free(cmp_b[cmddep][3]);
1190 cmp_b[cmddep][3] = NULL;
1192 if (cmp_b[cmddep][4]) {
1193 strncpy(atxbuf,cmp_b[cmddep][4],ATMBL); /* 4: eXpansion buffer */
1194 free(cmp_b[cmddep][4]);
1195 cmp_b[cmddep][4] = NULL;
1197 if (cmp_b[cmddep][5]) {
1198 strncpy(atybuf,cmp_b[cmddep][5],ATMBL); /* 5: Atom buffer copY */
1199 free(cmp_b[cmddep][5]);
1200 cmp_b[cmddep][5] = NULL;
1202 if (cmp_b[cmddep][6]) {
1203 strncpy(filbuf,cmp_b[cmddep][6],ATMBL); /* 6: Filename buffer */
1204 free(cmp_b[cmddep][6]);
1205 cmp_b[cmddep][6] = NULL;
1207 #endif /* DCMDBUF */
1209 cmddep--; /* Rise, rise */
1210 debug(F101,"&cmpop to depth","",cmddep);
1217 stripq(s) char *s; { /* Function to strip '\' quotes */
1221 for (t = s; *t != '\0'; t++) *t = *(t+1);
1226 #endif /* COMMENT */
1228 /* Convert tabs to spaces, one for one */
1232 if (*s == HT) *s = SP;
1237 /* C M N U M -- Parse a number in the indicated radix */
1240 The radix is specified in the arg list.
1241 Parses unquoted numeric strings in the given radix.
1242 Parses backslash-quoted numbers in the radix indicated by the quote:
1243 \nnn = \dnnn = decimal, \onnn = octal, \xnn = Hexadecimal.
1244 If these fail, then if a preprocessing function is supplied, that is applied
1245 and then a second attempt is made to parse an unquoted decimal string.
1246 And if that fails, the preprocessed string is passed to an arithmetic
1247 expression evaluator.
1250 -3 if no input present when required,
1251 -2 if user typed an illegal number,
1252 -1 if reparse needed,
1253 0 otherwise, with argument n set to the number that was parsed
1255 /* This is the traditional cmnum() that gets an int */
1257 cmnum(xhlp,xdef,radix,n,f) char *xhlp, *xdef; int radix, *n; xx_strp f; {
1258 CK_OFF_T z = (CK_OFF_T)0, check;
1260 x = cmnumw(xhlp,xdef,radix,&z,f);
1264 printf("?Magnitude of result too large for integer - %s\n",ckfstoa(z));
1271 This is the new cmnum() that gets a "wide" result, whatever CK_OFF_T
1272 is defined to be, normally 32 or 64 bits, depending on the platform.
1276 cmnumw(xhlp,xdef,radix,n,f)
1277 char *xhlp, *xdef; int radix; CK_OFF_T *n; xx_strp f; {
1278 int x; char *s, *zp, *zq;
1280 char lbrace, rbrace;
1281 #endif /* COMMENT */
1283 if (!xhlp) xhlp = "";
1284 if (!xdef) xdef = "";
1287 if (cmfldflgs & 1) {
1294 #endif /* COMMENT */
1296 if (radix != 10 && radix != 8) { /* Just do bases 8 and 10 */
1297 printf("cmnum: illegal radix - %d\n",radix);
1299 } /* Easy to add others but there has never been a need for it. */
1300 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1301 debug(F101,"cmnum: cmfld","",x);
1302 if (x < 0) return(x); /* Parse a field */
1305 Edit 192 - Allow any number field to be braced. This lets us include
1306 spaces in expressions, but perhaps more important lets us have user-defined
1307 functions in numeric fields.
1309 zp = brstrip(zp); /* Strip braces */
1310 if (cmfldflgs & 1 && *zp == '(') { /* Parens too.. */
1311 x = (int) strlen(atmbuf);
1313 if (*(atmbuf+x-1) == ')') {
1314 *(atmbuf+x-1) = NUL;
1319 if (chknum(zp)) { /* Check for number */
1320 if (radix == 8) { /* If it's supposed to be octal */
1321 zp = ckradix(zp,8,10); /* convert to decimal */
1322 if (!zp) return(-2);
1323 if (!strcmp(zp,"-1")) return(-2);
1325 errno = 0; /* Got one, we're done. */
1331 debug(F101,"cmnum 1st chknum ok","",*n);
1333 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1341 debug(F101,"cmnum xxesc ok","",*n);
1342 return(*zp ? -2 : 0);
1343 } else if (f) { /* If conversion function given */
1344 zq = atxbuf; /* Try that */
1346 if ((*f)(zp,&zq,&atxn) < 0) /* Convert */
1350 debug(F110,"cmnum zp 1",zp,0);
1351 if (!*zp) zp = xdef; /* Result empty, substitute default */
1352 debug(F110,"cmnum zp 2",zp,0);
1353 if (chknum(zp)) { /* Check again for decimal number */
1354 if (radix == 8) { /* If it's supposed to be octal */
1355 zp = ckradix(zp,8,10); /* convert to decimal */
1356 if (!zp) return(-2);
1357 if (!strcmp(zp,"-1")) return(-2);
1365 debug(F101,"cmnum 2nd chknum ok","",*n);
1368 } else if ((x = xxesc(&zp)) > -1) { /* Check for backslash escape */
1374 debug(F101,"cmnum xxesc 2 ok","",*n);
1375 return(*zp ? -2 : 0);
1376 } else if (f) { /* Not numeric, maybe an expression */
1380 if (radix == 8) { /* If it's supposed to be octal */
1381 zp = ckradix(zp,8,10); /* convert to decimal */
1382 if (!zp) return(-2);
1383 if (!strcmp(zp,"-1")) return(-2);
1391 debug(F101,"cmnum exp eval ok","",*n);
1395 } else { /* Not numeric */
1402 #endif /* CKCHANNELIO */
1404 /* C M O F I -- Parse the name of an output file */
1407 Depends on the external function zchko(); if zchko() not available, use
1408 cmfld() to parse output file names.
1411 -9 like -2, except message already printed,
1412 -3 if no input present when required,
1413 -2 if permission would be denied to create the file,
1414 -1 if reparse needed,
1415 0 or 1 if file can be created, with xp pointing to name.
1416 2 if given the name of an existing directory.
1419 cmofi(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1420 int x; char *s, *zq;
1423 #endif /* DOCHKVAR */
1430 if (!xhlp) xhlp = "";
1431 if (!xdef) xdef = "";
1433 if (*xhlp == NUL) xhlp = "Output file";
1436 debug(F110,"cmofi xdef",xdef,0);
1437 x = cmfld(xhlp,xdef,&s,(xx_strp)0);
1438 debug(F111,"cmofi cmfld returns",s,x);
1442 s = brstrip(s); /* Strip enclosing braces */
1443 debug(F110,"cmofi 1.5",s,0);
1450 This is really ugly. If we skip conversion the first time through,
1451 then variable names like \%a will be used as filenames (e.g. creating
1452 a file called %A in the root directory). If we DON'T skip conversion
1453 the first time through, then single backslashes used as directory
1454 separators in filenames will be misinterpreted as variable lead-ins.
1455 So we prescan to see if it has any variable references. But this
1456 module is not supposed to know anything about variables, functions,
1457 etc, so this code does not really belong here, but rather it should
1458 be at the same level as zzstring().
1461 Hmmm, this looks a lot like chkvar() except it that includes \nnn number
1462 escapes. But why? This makes commands like "mkdir c:\123" impossible.
1463 And in fact, "mkdir c:\123" creates a directory called "c:{". What's worse,
1464 rmdir(), which *does* call chkvar(), won't let us remove it. So let's at
1465 least try making cmofi() symmetrical with cmifi()...
1469 while ( (tries == 0) && (p = strchr(p,CMDQ)) ) {
1470 q = *(p+1); /* Char after backslash */
1471 if (!q) /* None, quit */
1473 if (isupper(q)) /* If letter, convert to lowercase */
1475 if (isdigit(q)) { /* If it's a digit, */
1476 tries = 1; /* assume it's a backslash code */
1480 case CMDQ: /* Double backslash */
1481 tries = 1; /* so call the conversion function */
1483 case '%': /* Variable or array reference */
1484 case '&': /* must be followed by letter */
1485 if (isalpha(*(p+2)) || (*(p+2) >= '0' && *(p+2) <= '9'))
1488 case 'm': case 'v': case '$': /* \m(), \v(), \$() */
1490 if (strchr(p+2,')'))
1493 case 'f': /* \Fname() */
1494 if (strchr(p+2,'('))
1495 if (strchr(p+2,')'))
1498 case '{': /* \{...} */
1499 if (strchr(p+2,'}'))
1502 case 'd': case 'o': /* Decimal or Octal number */
1503 if (isdigit(*(p+2)))
1506 case 'x': /* Hex number */
1507 if (isdigit(*(p+2)) ||
1508 ((*(p+2) >= 'a' && *(p+2) <= 'f') ||
1509 ((*(p+2) >= 'A' && *(p+2) <= 'F'))))
1518 if (f) { /* If a conversion function is given */
1519 char *s = p; /* See if there are any variables in */
1520 while (*s) { /* the string and if so, expand them */
1529 #endif /* COMMENT */
1535 #endif /* DOCHKVAR */
1536 if (f) { /* If a conversion function is given */
1537 zq = atxbuf; /* do the conversion. */
1539 if ((x = (*f)(s,&zq,&atxn)) < 0)
1542 if (!*s) /* Result empty, substitute default */
1545 debug(F111,"cmofi 2",s,x);
1548 dirp = tilde_expand(s); /* Expand tilde, if any, */
1549 if (*dirp != '\0') { /* right in the atom buffer. */
1550 if (setatm(dirp,1) < 0) {
1551 printf("?Name too long\n");
1556 debug(F110,"cmofi 3",s,0);
1560 printf("?Wildcards not allowed - %s\n",s);
1563 debug(F110,"cmofi 4",s,0);
1566 /* isdir() function required for this! */
1568 debug(F110,"cmofi 5: is directory",s,0);
1572 #endif /* CK_TMPDIR */
1574 if (strcmp(s,CTTNAM) && (zchko(s) < 0)) { /* OK to write to console */
1578 We don't try again because we already prescanned the string to see if
1579 if it contained anything that could be used by zzstring().
1584 #endif /* COMMENT */
1586 Note: there are certain circumstances where zchko() can give a false
1587 positive, so don't rely on it to catch every conceivable situation in
1588 which the given output file can't be created. In other words, we print
1589 a message and fail here if we KNOW the file can't be created. If we
1590 succeed but the file can't be opened, the code that tries to open the file
1591 has to print a message.
1593 debug(F110,"cmofi 6: failure",s,0);
1596 printf("?Off Limits: %s\n",s);
1599 printf("?Write permission denied - %s\n",s);
1602 #endif /* CKCHANNELIO */
1605 debug(F110,"cmofi 7: ok",s,0);
1611 /* C M I F I -- Parse the name of an existing file */
1614 This function depends on the external functions:
1615 zchki() - Check if input file exists and is readable.
1616 zxpand() - Expand a wild file specification into a list.
1617 znext() - Return next file name from list.
1618 If these functions aren't available, then use cmfld() to parse filenames.
1623 -3 if no input present when required,
1624 -2 if file does not exist or is not readable,
1625 -1 if reparse needed,
1626 0 or 1 otherwise, with:
1627 xp pointing to name,
1628 wild = 1 if name contains '*' or '?', 0 otherwise.
1631 #ifdef COMMENT /* This horrible hack has been replaced - see further down */
1633 C M I O F I -- Parse an input file OR the name of a nonexistent file.
1635 Use this when an existing file is wanted (so we get help, completion, etc),
1636 but if a file of the given name does not exist, the name of a new file is
1637 accepted. For example, with the EDIT command (edit an existing file, or
1638 create a new file). Returns -9 if file does not exist. It is up to the
1639 caller to check creatability.
1641 static int nomsg = 0;
1643 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1647 x = cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0);
1651 #endif /* COMMENT */
1654 cmifi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
1655 return(cmifi2(xhlp,xdef,xp,wild,0,NULL,f,0));
1658 cmifip() is called when we want to supply a path or path list to search
1659 in case the filename that the user gives is (a) not absolute, and (b) can't
1660 be found as given. The path string can be the name of a single directory,
1661 or a list of directories separated by the PATHSEP character, defined in
1662 ckucmd.h. Look in ckuusr.c and ckuus3.c for examples of usage.
1665 cmifip(xhlp,xdef,xp,wild,d,path,f)
1666 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; {
1667 return(cmifi2(xhlp,xdef,xp,wild,0,path,f,0));
1670 /* C M D I R -- Parse a directory name */
1673 This function depends on the external functions:
1674 isdir(s) - Check if string s is the name of a directory
1675 zchki(s) - Check if input file s exists and what type it is.
1676 If these functions aren't available, then use cmfld() to parse dir names.
1679 -9 For all sorts of reasons, after printing appropriate error message.
1681 -3 if no input present when required,
1682 -2 if out of space or other internal error,
1683 -1 if reparse needed,
1684 0 or 1, with xp pointing to name, if directory specified,
1687 cmdir(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
1689 return(cmifi2(xhlp,xdef,xp,&wild,0,NULL,f,1));
1692 /* Like CMDIR but includes PATH search */
1695 cmdirp(xhlp,xdef,xp,path,f) char *xhlp, *xdef, **xp; char * path; xx_strp f; {
1697 return(cmifi2(xhlp,xdef,xp,&wild,0,path,f,1));
1701 cmifi2() is the base filename parser called by cmifi, cmifip, cmdir, etc.
1702 Use it directly when you also want to parse a directory or device
1703 name as an input file, as in the DIRECTORY command. Call with:
1704 xhlp -- help message on ?
1705 xdef -- default response
1706 xp -- pointer to result (in our space, must be copied from here)
1707 wild -- flag set upon return to indicate if filespec was wild
1708 d -- 0 to parse files, 1 to parse files or directories
1709 Add 2 to inhibit following of symlinks.
1710 path -- search path for files
1711 f -- pointer to string processing function (e.g. to evaluate variables)
1712 dirflg -- 1 to parse *only* directories, 0 otherwise
1715 cmifi2(xhlp,xdef,xp,wild,d,path,f,dirflg)
1716 char *xhlp,*xdef,**xp; int *wild, d; char * path; xx_strp f; int dirflg; {
1717 extern int recursive, diractive, cdactive, dblquo;
1718 int i, x, itsadir, xc, expanded = 0, nfiles = 0, children = -1;
1722 char *sp = NULL, *zq, *np = NULL;
1723 char *sv = NULL, *p = NULL;
1731 /* This large array is dynamic for OS-9 -- should do for others too... */
1732 extern char **mtchs;
1735 /* OK, for UNIX too */
1736 extern char **mtchs;
1739 extern char **mtchs;
1741 extern char *mtchs[];
1746 #endif /* NOPARTIAL */
1748 if (!xhlp) xhlp = "";
1749 if (!xdef) xdef = "";
1752 makestr(&tmplastfile,NULL);
1753 #endif /* NOLASTFILE */
1754 nzxopts = 0; /* zxpand() options */
1755 debug(F101,"cmifi d","",d);
1756 if (d & 2) { /* d & 2 means don't follow symlinks */
1758 nzxopts = ZX_NOLINKS;
1760 debug(F101,"cmifi nzxopts","",nzxopts);
1765 if (path) { /* Make a copy we can poke */
1767 np = (char *) malloc(x + 1);
1773 debug(F110,"cmifi2 path",path,0);
1775 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
1778 inword = 0; /* Initialize counts & pointers */
1781 *xp = ""; /* Pointer to result string */
1782 if ((x = cmflgs) != 1) { /* Already confirmed? */
1785 x = gtword(0); /* No, get a word */
1788 x = gtword(0); /* No, get a word */
1789 #endif /* BS_DIRSEP */
1790 } else { /* If so, use default, if any. */
1791 if (setatm(xdef,1) < 0) {
1792 printf("?Default name too long\n");
1798 *xp = atmbuf; /* Point to result. */
1801 xc += cc; /* Count this character. */
1802 debug(F111,"cmifi gtword",atmbuf,xc);
1803 debug(F101,"cmifi switch x","",x);
1804 switch (x) { /* x = gtword() return code */
1806 if (gtimer() > timelimit) {
1809 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
1810 doexit(GOOD_EXIT,0);
1813 /* if (!quiet) printf("?Timed out\n"); */
1820 printf("Command or field too long\n");
1822 case -2: /* Out of space. */
1823 case -1: /* Reparse needed */
1828 if (xc == 0) /* If no input... */
1829 *xp = xdef; /* substitute the default */
1831 makestr(&tmplastfile,*xp); /* Make a copy before bstripping */
1832 #endif /* #ifndef NOLASTFILE */
1833 *xp = brstrip(*xp); /* Strip braces */
1834 if (**xp == NUL) { /* 12 mar 2001 */
1838 debug(F110,"cmifi brstrip",*xp,0);
1840 if (f) { /* If a conversion function is given */
1842 char *s = *xp; /* See if there are any variables in */
1844 while (*s) { /* the string and if so, expand them */
1846 /* debug(F111,"cmifi chkvar",*xp,x); */
1848 #endif /* DOCHKVAR */
1851 if ((*f)(*xp,&zq,&atxn) < 0) {
1863 #endif /* DOCHKVAR */
1866 if (**xp == NUL) { /* 12 mar 2001 */
1872 dirp = tilde_expand(*xp); /* Expand tilde, if any, */
1873 if (*dirp != '\0') { /* in the atom buffer. */
1874 if (setatm(dirp,1) < 0) {
1875 printf("Expanded name too long\n");
1881 debug(F110,"cmifi tilde_expand",*xp,0);
1884 if (!sv) { /* Only do this once */
1885 sv = malloc((int)strlen(*xp)+1); /* Make a safe copy */
1887 printf("?cmifi: malloc error\n");
1892 debug(F110,"cmifi sv",sv,0);
1895 /* This is to get around "cd /" failing because "too many directories match" */
1897 expanded = 0; /* Didn't call zxpand */
1899 debug(F110,"cmifi isdir 1",*xp,0);
1911 debug(F110,"cmifi isdir 2",*xp,0);
1912 #endif /* datageneral */
1916 if (!strcmp(*xp,"..")) { /* For UNIXers... */
1919 } else if (!strcmp(*xp,".")) {
1925 itsadir = isdir(*xp); /* Is it a directory? */
1926 debug(F111,"cmifi itsadir",*xp,itsadir);
1928 /* If they said "blah" where "blah.dir" is a directory... */
1929 /* change it to [.blah]. */
1932 int flag = 0; char c, * p;
1934 while ((c = *p++) && !flag)
1935 if (ckstrchr(".[]:*?<>",c))
1937 debug(F111,"cmifi VMS dirname flag",*xp,flag);
1939 ckmakmsg(tmpbuf,TMPBUFSIZ,"[.",*xp,"]",NULL);
1940 itsadir = isdir(tmpbuf);
1945 debug(F111,"cmifi VMS dirname flag itsadir",*xp,itsadir);
1947 } else if (itsadir == 1 && *(xp[0]) == '.' && *(xp[1])) {
1949 if (p = malloc(cc + 4)) {
1950 ckmakmsg(p,cc+4,"[",*xp,"]",NULL);
1953 debug(F110,"cmdir .foo",*xp,0);
1956 } else if (itsadir == 2 && !diractive) {
1957 int x; /* [FOO]BAR.DIR instead of [FOO.BAR] */
1961 x = cvtdir(*xp,p,ATMBL); /* Convert to [FOO.BAR] */
1965 debug(F110,"cmdir cvtdir",*xp,0);
1972 debug(F101,"cmifi dirflg","",dirflg);
1973 if (dirflg) { /* Parsing a directory name? */
1974 /* Yes, does it contain wildcards? */
1976 (diractive && (!strcmp(*xp,".") || !strcmp(*xp,"..")))
1978 nzxopts |= ZX_DIRONLY; /* Match only directory names */
1979 if (matchdot) nzxopts |= ZX_MATCHDOT;
1980 if (recursive) nzxopts |= ZX_RECURSE;
1981 debug(F111,"cmifi nzxopts 2",*xp,nzxopts);
1982 y = nzxpand(*xp,nzxopts);
1983 debug(F111,"cmifi nzxpand 2",*xp,y);
1989 This is to allow (e.g.) "cd foo", where FOO.DIR;1 is in the
1992 debug(F111,"cmdir itsadir",*xp,itsadir);
2000 *s != '[' && s[n-1] != ']' &&
2001 *s != '<' && s[n-1] != '>' &&
2003 ckindex("[",s,0,0,1) == 0 &&
2004 ckindex("<",s,0,0,1) == 0 &&
2005 #endif /* COMMENT */
2007 char * dirbuf = NULL;
2008 dirbuf = (char *)malloc(n+4);
2011 ckmakmsg(dirbuf,n+4,"[",s,"]",NULL);
2013 ckmakmsg(dirbuf,n+4,"[.",s,"]",NULL);
2014 itsadir = isdir(dirbuf);
2015 debug(F111,"cmdir dirbuf",dirbuf,itsadir);
2019 debug(F110,"cmdir new *xp",*xp,0);
2024 /* This is to allow CDPATH to work in VMS... */
2027 char * p; int i, j, k, d;
2029 if (p = malloc(x + 8)) {
2030 ckstrncpy(p,*xp,x+8);
2031 i = ckindex(".",p,-1,1,1);
2032 d = ckindex(".dir",p,0,0,0);
2033 j = ckindex("]",p,-1,1,1);
2035 j = ckindex(">",p,-1,1,1);
2038 k = ckindex(":",p,-1,1,1);
2039 if (i < j || i < k) i = 0;
2040 if (d < j || d < k) d = 0;
2041 /* Change [FOO]BAR or [FOO]BAR.DIR */
2043 if (j > 0 && j < n) {
2045 if (d > 0) p[d-1] = NUL;
2046 ckstrncat(p,rb,x+8);
2047 debug(F110,"cmdir xxx",p,0);
2050 debug(F111,"cmdir p",p,itsadir);
2054 debug(F110,"cmdir new *xp",*xp,0);
2061 y = (!itsadir) ? 0 : 1;
2062 debug(F111,"cmifi y itsadir",*xp,y);
2064 } else { /* Parsing a filename. */
2065 debug(F110,"cmifi *xp pre-zxpand",*xp,0);
2067 nzxopts |= (d == 0) ? ZX_FILONLY : 0; /* So always expand. */
2068 if (matchdot) nzxopts |= ZX_MATCHDOT;
2069 if (recursive) nzxopts |= ZX_RECURSE;
2070 y = nzxpand(*xp,nzxopts);
2072 /* Here we're trying to fix a problem in which a directory name is accepted */
2073 /* as a filename, but this breaks too many other things. */
2076 if (itsadir & !iswild(*xp)) {
2077 debug(F100,"cmifi dir when filonly","",0);
2078 printf("?Not a regular file: \"%s\"\n",*xp);
2083 nzxopts |= ZX_FILONLY;
2084 if (matchdot) nzxopts |= ZX_MATCHDOT;
2085 if (recursive) nzxopts |= ZX_RECURSE;
2086 y = nzxpand(*xp,nzxopts);
2089 #endif /* COMMENT */
2091 debug(F111,"cmifi y nzxpand",*xp,y);
2092 debug(F111,"cmifi y atmbuf",atmbuf,itsadir);
2095 /* domydir() calls zxrewind() so we MUST call nzxpand() here */
2096 if (!expanded && diractive) {
2097 debug(F110,"cmifi diractive catch-all zxpand",*xp,0);
2098 nzxopts |= (d == 0) ? ZX_FILONLY : (dirflg ? ZX_DIRONLY : 0);
2099 if (matchdot) nzxopts |= ZX_MATCHDOT;
2100 if (recursive) nzxopts |= ZX_RECURSE;
2101 y = nzxpand(*xp,nzxopts);
2102 debug(F111,"cmifi diractive nzxpand",*xp,y);
2106 *wild = (iswild(sv) || (y > 1)) && (itsadir == 0);
2109 if (!*wild) *wild = recursive;
2110 #endif /* RECURSIVE */
2112 debug(F111,"cmifi sv wild",sv,*wild);
2113 debug(F101,"cmifi y","",y);
2114 if (dirflg && *wild && cdactive) {
2116 printf("?Wildcard matches more than one directory\n");
2124 if (itsadir && d && !dirflg) { /* It's a directory and not wild */
2125 if (sv) free(sv); /* and it's ok to parse directories */
2128 makestr(&lastfile,tmplastfile);
2129 #endif /* NOLASTFILE */
2132 if (y == 0) { /* File was not found */
2134 dosearch = (path != NULL); /* A search path was given */
2136 dosearch = hasnopath(sv); /* Filename includes no path */
2137 debug(F111,"cmifip hasnopath",sv,dosearch);
2139 if (dosearch) { /* Search the path... */
2144 if (c == PATHSEP || c == NUL) {
2151 /* By definition of CDPATH, an empty member denotes the current directory */
2153 ckstrncpy(atmbuf,".",ATMBL);
2156 ckstrncpy(atmbuf,path,ATMBL);
2158 atmbuf[ATMBL] = NUL;
2159 /* If we have a logical name, evaluate it recursively */
2160 if (*(ptr-1) == ':') { /* Logical name ends in : */
2162 while (((n = strlen(atmbuf)) > 0) &&
2163 atmbuf[n-1] == ':') {
2165 for (p = atmbuf; *p; p++)
2166 if (islower(*p)) *p = toupper(*p);
2167 debug(F111,"cmdir CDPATH LN 1",atmbuf,n);
2169 debug(F110,"cmdir CDPATH LN 2",p,0);
2172 strncpy(atmbuf,p,ATMBL);
2173 atmbuf[ATMBL] = NUL;
2178 if (*(ptr-1) != '\\' && *(ptr-1) != '/')
2179 ckstrncat(atmbuf,"\\",ATMBL);
2182 if (*(ptr-1) != '/')
2183 ckstrncat(atmbuf,"/",ATMBL);
2186 if (*(ptr-1) != ':')
2187 ckstrncat(atmbuf,":",ATMBL);
2188 #endif /* datageneral */
2192 ckstrncat(atmbuf,sv,ATMBL);
2193 debug(F110,"cmifip add path",atmbuf,0);
2194 if (c == PATHSEP) ptr++;
2203 xc = (int) strlen(atmbuf);
2215 printf("?Off Limits: %s\n",sv);
2219 printf("?No %s match - %s\n",
2220 dirflg ? "directories" : "files", sv);
2229 printf("?Off Limits: %s\n",sv);
2232 printf("?Too many %s match - %s\n",
2233 dirflg ? "directories" : "files", sv);
2237 } else if (*wild || y > 1) {
2241 makestr(&lastfile,tmplastfile);
2242 #endif /* NOLASTFILE */
2246 /* If not wild, see if it exists and is readable. */
2248 debug(F111,"cmifi sv not wild",sv,*wild);
2250 znext(*xp); /* Get first (only?) matching file */
2251 if (dirflg) /* Maybe wild and expanded */
2252 itsadir = isdir(*xp); /* so do this again. */
2253 filesize = dirflg ? itsadir : zchki(*xp); /* Check accessibility */
2256 nfiles = zxrewind(); /* Rewind so next znext() gets 1st */
2259 nzxopts |= dirflg ? ZX_DIRONLY : 0;
2260 if (matchdot) nzxopts |= ZX_MATCHDOT;
2261 if (recursive) nzxopts |= ZX_RECURSE;
2262 nfiles = nzxpand(*xp,nzxopts);
2263 #endif /* ZXREWIND */
2265 debug(F111,"cmifi nfiles",*xp,nfiles);
2266 debug(F101,"cmifi filesize","",filesize);
2267 free(sv); /* done with this */
2269 if (dirflg && !filesize) {
2270 printf("?Not a directory - %s\n",*xp);
2273 #endif /* CKCHANNELIO */
2275 } else if (filesize == (CK_OFF_T)-3) {
2278 /* Don't show filename if we're not allowed to see it */
2279 printf("?Read permission denied\n");
2281 printf("?Read permission denied - %s\n",*xp);
2286 #endif /* CKCHANNELIO */
2287 return(xcmfdb ? -6 : -9);
2288 } else if (filesize == (CK_OFF_T)-2) {
2293 makestr(&lastfile,tmplastfile);
2294 #endif /* NOLASTFILE */
2298 printf("?File not readable - %s\n",*xp);
2301 #endif /* CKCHANNELIO */
2302 return(xcmfdb ? -6 : -9);
2304 } else if (filesize < (CK_OFF_T)0) {
2306 if (!nomsg && !xcmfdb)
2307 printf("?File not found - %s\n",*xp);
2310 #endif /* CKCHANNELIO */
2311 return(xcmfdb ? -6 : -9);
2315 makestr(&lastfile,tmplastfile);
2316 #endif /* NOLASTFILE */
2321 debug(F101,"cmifi esc, xc","",xc);
2324 printf("%s ",xdef); /* If at beginning of field */
2328 inword = cmflgs = 0;
2329 addbuf(xdef); /* Supply default. */
2330 if (setatm(xdef,0) < 0) {
2331 printf("Default name too long\n");
2335 } else { /* No default */
2340 if (**xp == '{') { /* Did user type opening brace... */
2345 } else if (dblquo && **xp == '"') { /* or doublequote? */
2346 *xp = *xp + 1; /* If so ignore it and space past it */
2352 if (f) { /* If a conversion function is given */
2354 char *s = *xp; /* See if there are any variables in */
2355 while (*s) { /* the string and if so, expand it. */
2357 #endif /* DOCHKVAR */
2360 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2365 /* reduce cc by number of \\ consumed by conversion */
2366 /* function (needed for OS/2, where \ is path separator) */
2367 cc -= (strlen(*xp) - strlen(atxbuf));
2368 #endif /* DOCHKVAR */
2370 if (!atxbuf[0]) { /* Result empty, use default */
2379 #endif /* DOCHKVAR */
2384 if (dirflg && *(*xp) == '~') {
2385 debug(F111,"cmifi tilde_expand A",*xp,cc);
2386 dirp = tilde_expand(*xp); /* Expand tilde, if any... */
2387 if (!dirp) dirp = "";
2391 xc = cc; /* Length of ~thing */
2392 xx = setatm(dirp,0); /* Copy expansion to atom buffer */
2393 debug(F111,"cmifi tilde_expand B",atmbuf,cc);
2395 printf("Expanded name too long\n");
2399 debug(F111,"cmifi tilde_expand xc","",xc);
2400 for (i = 0; i < xc; i++) {
2401 cmdchardel(); /* Back up over ~thing */
2404 xc = cc; /* How many new ones we just got */
2406 printf("%s",sp); /* Print them */
2407 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2408 bp--; /* Back up over NUL */
2417 if (!strcmp(atmbuf,"..")) {
2419 ckstrncat(cmdbuf," ",CMDBL);
2425 } else if (!strcmp(atmbuf,".")) {
2430 /* This patches a glitch when user types "./foo<ESC>" */
2431 /* in which the next two chars are omitted from the */
2432 /* expansion. There should be a better fix, however, */
2433 /* since there is no problem with "../foo<ESC>". */
2435 if (*p == '.' && *(p+1) == '/')
2438 #endif /* UNIXOROSK */
2441 *sp++ = '+'; /* Data General AOS wildcard */
2443 *sp++ = '*'; /* Others */
2444 #endif /* datageneral */
2447 if (!strchr(*xp, '.')) /* abde.e -> abcde.e* */
2448 strcat(*xp, ".*"); /* abc -> abc*.* */
2450 /* Add wildcard and expand list. */
2452 /* This kills partial completion when ESC given in path segment */
2453 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2456 #endif /* COMMENT */
2457 if (matchdot) nzxopts |= ZX_MATCHDOT;
2458 if (recursive) nzxopts |= ZX_RECURSE;
2459 y = nzxpand(*xp,nzxopts);
2461 debug(F111,"cmifi nzxpand",*xp,y);
2464 znext(filbuf); /* Get first */
2466 zxrewind(); /* Must "rewind" */
2468 nzxpand(*xp,nxzopts);
2469 #endif /* ZXREWIND */
2471 ckstrncpy(filbuf,mtchs[0],CKMAXPATH);
2475 filbuf[CKMAXPATH] = NUL;
2476 *sp = '\0'; /* Remove wildcard. */
2477 debug(F111,"cmifi filbuf",filbuf,y);
2478 debug(F111,"cmifi *xp",*xp,cc);
2485 printf("?Off Limits: %s\n",atmbuf);
2488 printf("?No %s match - %s\n",
2489 dirflg ? "directories" : "files", atmbuf);
2500 printf("?Off Limits: %s\n",atmbuf);
2503 printf("?Too many %s match - %s\n",
2504 dirflg ? "directories" : "files", atmbuf);
2507 } else if (y > 1 /* Not unique */
2509 || (y == 1 && isdir(filbuf)) /* Unique directory */
2513 /* Partial filename completion */
2516 debug(F111,"cmifi partial",filbuf,cc);
2522 min = strlen(filbuf),
2524 char localfn[CKMAXPATH+1];
2527 for (j = 1; j <= y; j++) {
2529 if (dirflg && !isdir(localfn))
2532 len2 = strlen(localfn);
2534 cur < len && cur < len2 && cur <= min;
2537 /* OS/2 or Windows, case doesn't matter */
2538 if (tolower(filbuf[cur]) != tolower(localfn[cur]))
2551 for (i = cc; (c = filbuf[i]); i++) {
2552 for (j = 1; j < y; j++)
2553 if (mtchs[j][i] != c) break;
2555 else filbuf[i] = filbuf[i+1] = NUL;
2561 /* isdir() function required for this! */
2562 if (y == 1 && isdir(filbuf)) { /* Dont we already know this? */
2564 len = strlen(filbuf);
2565 if (len > 0 && len < ATMBL - 1) {
2566 if (filbuf[len-1] != dirsep) {
2567 filbuf[len] = dirsep;
2568 filbuf[len+1] = NUL;
2572 At this point, before just doing partial completion, we should look first to
2573 see if the given directory does indeed have any subdirectories (dirflg) or
2574 files (!dirflg); if it doesn't we should do full completion. Otherwise, the
2575 result looks funny to the user and "?" blows up the command for no good
2580 filbuf[len+1] = '*';
2581 filbuf[len+2] = NUL;
2582 if (dirflg) flags = ZX_DIRONLY;
2583 children = nzxpand(filbuf,flags);
2584 debug(F111,"cmifi children",filbuf,children);
2585 filbuf[len+1] = NUL;
2586 nzxpand(filbuf,flags); /* Restore previous list */
2593 /* Add doublequotes if there are spaces in the name */
2597 x = (qflag == '}'); /* (or braces) */
2601 if (filbuf[0] != '"' && filbuf[0] != '{')
2602 k = dquote(filbuf,ATMBL,x);
2605 debug(F111,"cmifi REPAINT filbuf",filbuf,k);
2606 if (k > 0) { /* Got more characters */
2607 debug(F101,"cmifi REPAINT cc","",cc);
2608 debug(F101,"cmifi REPAINT xc","",xc);
2609 debug(F110,"cmifi REPAINT bp-cc",bp-cc,0);
2610 debug(F110,"cmifi REPAINT bp-xc",bp-xc,0);
2611 sp = filbuf + cc; /* Point to new ones */
2612 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2616 for (i = 0; i < x; i++) {
2617 cmdchardel(); /* Back up over old partial spec */
2620 sp = filbuf; /* Point to new word start */
2621 debug(F110,"cmifi erase ok",sp,0);
2623 cc = k; /* How many new ones we just got */
2624 printf("%s",sp); /* Print them */
2625 while ((*bp++ = *sp++)) ; /* Copy to command buffer */
2626 bp--; /* Back up over NUL */
2627 debug(F110,"cmifi partial cmdbuf",cmdbuf,0);
2628 if (setatm(filbuf,0) < 0) {
2629 printf("?Partial name too long\n");
2633 debug(F111,"cmifi partial atmbuf",atmbuf,cc);
2636 #endif /* NOPARTIAL */
2638 } else { /* Unique, complete it. */
2641 /* isdir() function required for this! */
2643 debug(F111,"cmifi unique",filbuf,children);
2644 if (isdir(filbuf) && children > 0) {
2646 len = strlen(filbuf);
2647 if (len > 0 && len < ATMBL - 1) {
2648 if (filbuf[len-1] != dirsep) {
2649 filbuf[len] = dirsep;
2650 filbuf[len+1] = NUL;
2657 while ((*bp++ = *sp++)) ;
2659 if (setatm(filbuf,0) < 0) {
2660 printf("?Directory name too long\n");
2664 debug(F111,"cmifi directory atmbuf",atmbuf,cc);
2666 } else { /* Not a directory or dirflg */
2667 #endif /* CK_TMPDIR */
2669 #ifndef VMS /* VMS dir names are special */
2670 #ifndef datageneral /* VS dirnames must not end in ":" */
2673 len = strlen(filbuf);
2674 if (len > 0 && len < ATMBL - 1) {
2675 if (filbuf[len-1] != dirsep) {
2676 filbuf[len] = dirsep;
2677 filbuf[len+1] = NUL;
2681 #endif /* datageneral */
2683 sp = filbuf + cc; /* Point past what user typed. */
2691 if (filbuf[0] != '"' && filbuf[0] != '{')
2692 dquote(filbuf,ATMBL,x);
2694 if (qflag || strncmp(filbuf,bp-cc,cc)) { /* Repaint? */
2698 for (i = 0; i < x; i++) {
2699 cmdchardel(); /* Back up over old partial spec */
2702 sp = filbuf; /* Point to new word start */
2703 debug(F111,"cmifi after erase sp=",sp,cc);
2705 printf("%s ",sp); /* Print the completed name. */
2709 addbuf(sp); /* Add the characters to cmdbuf. */
2710 if (setatm(filbuf,0) < 0) { /* And to atmbuf. */
2711 printf("?Completed name too long\n");
2715 inword = cmflgs = 0;
2716 *xp = brstrip(atmbuf); /* Return pointer to atmbuf. */
2717 if (dirflg && !isdir(*xp)) {
2718 printf("?Not a directory - %s\n", filbuf);
2724 makestr(&lastfile,tmplastfile);
2725 #endif /* NOLASTFILE */
2730 #endif /* CK_TMPDIR */
2735 case 3: /* Question mark - file menu wanted */
2737 printf(dirflg ? " Directory name" : " Input file specification");
2743 /* If user typed an opening quote or brace, just skip past it */
2745 if (**xp == '"' || **xp == '{') {
2751 if (f) { /* If a conversion function is given */
2753 char *s = *xp; /* See if there are any variables in */
2754 while (*s) { /* the string and if so, expand them */
2756 #endif /* DOCHKVAR */
2759 if ((x = (*f)(*xp,&zq,&atxn)) < 0) {
2764 /* reduce cc by number of \\ consumed by conversion */
2765 /* function (needed for OS/2, where \ is path separator) */
2766 cc -= (strlen(*xp) - strlen(atxbuf));
2767 #endif /* DOCHKVAR */
2774 #endif /* DOCHKVAR */
2777 debug(F111,"cmifi ? *xp, cc",*xp,cc);
2778 sp = *xp + cc; /* Insert "*" at end */
2780 *sp++ = '+'; /* Insert +, the DG wild card */
2783 #endif /* datageneral */
2786 if (! strchr(*xp, '.')) /* abde.e -> abcde.e* */
2787 strcat(*xp, ".*"); /* abc -> abc*.* */
2789 debug(F110,"cmifi ? wild",*xp,0);
2791 nzxopts |= dirflg ? ZX_DIRONLY : (d ? 0 : ZX_FILONLY);
2793 debug(F101,"cmifi matchdot","",matchdot);
2794 if (matchdot) nzxopts |= ZX_MATCHDOT;
2795 if (recursive) nzxopts |= ZX_RECURSE;
2796 y = nzxpand(*xp,nzxopts);
2801 printf(": %s\n",atmbuf);
2802 printf("%s%s",cmprom,cmdbuf);
2809 printf("?Off Limits: %s\n",atmbuf);
2812 printf("?No %s match - %s\n",
2813 dirflg ? "directories" : "files", atmbuf);
2820 printf("?Off Limits: %s\n",atmbuf);
2823 printf("?Too many %s match - %s\n",
2824 dirflg ? "directories" : "files", atmbuf);
2828 printf(", one of the following:\n");
2829 if (filhelp((int)y,"","",1,dirflg) < 0) {
2834 printf("%s%s",cmprom,cmdbuf);
2841 x = gtword(0); /* No, get a word */
2844 x = gtword(0); /* No, get a word */
2845 #endif /* BS_DIRSEP */
2850 /* C M F L D -- Parse an arbitrary field */
2853 -3 if no input present when required,
2854 -2 if field too big for buffer,
2855 -1 if reparse needed,
2856 0 otherwise, xp pointing to string result.
2858 NOTE: Global flag keepallchars says whether this routine should break on CR
2859 or LF: needed for MINPUT targets and DECLARE initializers, where we want to
2860 keep control characters if the user specifies them (March 2003). It might
2861 have been better to change the calling sequence but that was not practical.
2864 cmfld(xhlp,xdef,xp,f) char *xhlp, *xdef, **xp; xx_strp f; {
2868 inword = 0; /* Initialize counts & pointers */
2873 debug(F110,"cmfld xdef 1",xdef,0);
2875 if (!xhlp) xhlp = "";
2876 if (!xdef) xdef = "";
2877 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
2880 debug(F111,"cmfld xdef 2",xdef,cmflgs);
2881 debug(F111,"cmfld atmbuf 1",atmbuf,xc);
2883 if ((x = cmflgs) != 1) { /* Already confirmed? */
2884 x = gtword(0); /* No, get a word */
2886 if (setatm(xdef,0) < 0) { /* If so, use default, if any. */
2887 printf("?Default too long\n");
2891 *xp = atmbuf; /* Point to result. */
2892 debug(F111,"cmfld atmbuf 2",atmbuf,cmflgs);
2895 xc += cc; /* Count the characters. */
2896 debug(F111,"cmfld gtword",atmbuf,xc);
2897 debug(F101,"cmfld x","",x);
2900 printf("Command or field too long\n");
2902 case -3: /* Empty. */
2903 case -2: /* Out of space. */
2904 case -1: /* Reparse needed */
2908 debug(F111,"cmfld 1",atmbuf,xc);
2909 if (xc == 0) { /* If no input, return default. */
2910 if (setatm(xdef,0) < 0) {
2911 printf("?Default too long\n");
2915 *xp = atmbuf; /* Point to what we got. */
2916 debug(F111,"cmfld 2",atmbuf,((f) ? 1 : 0));
2917 if (f) { /* If a conversion function is given */
2918 zq = atxbuf; /* employ it now. */
2920 if ((*f)(*xp,&zq,&atxn) < 0)
2922 debug(F111,"cmfld 3",atxbuf,xc);
2923 /* Replace by new value -- for MINPUT only keep all chars */
2924 if (setatm(atxbuf,keepallchars ? 3:1) < 0) { /* 16 Mar 2003 */
2925 printf("Value too long\n");
2930 debug(F111,"cmfld 4",atmbuf,xc);
2931 if (**xp == NUL) { /* If variable evaluates to null */
2932 if (setatm(xdef,0) < 0) {
2933 printf("?Default too long\n");
2936 if (**xp == NUL) x = -3; /* If still empty, return -3. */
2938 debug(F111,"cmfld returns",*xp,x);
2941 if (xc == 0 && *xdef) {
2942 printf("%s ",xdef); /* If at beginning of field, */
2946 addbuf(xdef); /* Supply default. */
2947 inword = cmflgs = 0;
2948 if (setatm(xdef,0) < 0) {
2949 printf("?Default too long\n");
2951 } else /* Return as if whole field */
2952 return(0); /* typed, followed by space. */
2957 case 3: /* Question mark */
2958 debug(F110,"cmfld QUESTIONMARK",cmdbuf,0);
2960 printf(" Please complete this field");
2963 printf("\n%s%s",cmprom,cmdbuf);
2967 debug(F111,"cmfld gtword A x",cmdbuf,x);
2969 debug(F111,"cmfld gtword B x",cmdbuf,x);
2974 /* C M T X T -- Get a text string, including confirmation */
2977 Print help message 'xhlp' if ? typed, supply default 'xdef' if null
2978 string typed. Returns:
2980 -1 if reparse needed or buffer overflows.
2983 with cmflgs set to return code, and xp pointing to result string.
2986 cmtxt(xhlp,xdef,xp,f) char *xhlp; char *xdef; char **xp; xx_strp f; {
2992 if (!xhlp) xhlp = "";
2993 if (!xdef) xdef = "";
2999 ckstrncpy(cmdefault,xdef,CMDEFAULT); /* Copy default */
3002 debug(F101,"cmtxt cmflgs","",cmflgs);
3003 inword = 0; /* Start atmbuf counter off at 0 */
3005 if (cmflgs == -1) { /* If reparsing, */
3007 xc = (int)strlen(*xp); /* get back the total text length, */
3008 bp = *xp; /* and back up the pointers. */
3011 } else { /* otherwise, */
3012 /* debug(F100,"cmtxt: fresh start","",0); */
3013 *xp = ""; /* start fresh. */
3016 *atmbuf = NUL; /* And empty the atom buffer. */
3017 rtimer(); /* Reset timer */
3018 if ((x = cmflgs) != 1) {
3021 x = gtword(0); /* Get first word. */
3022 *xp = pp; /* Save pointer to it. */
3023 /* debug(F111,"cmtxt:",*xp,cc); */
3025 if (gtimer() > timelimit) {
3026 /* if (!quiet) printf("?Timed out\n"); */
3033 while (1) { /* Loop for each word in text. */
3034 xc += cc; /* Char count for all words. */
3035 /* debug(F111,"cmtxt gtword",atmbuf,xc); */
3036 /* debug(F101,"cmtxt x","",x); */
3039 if (gtimer() > timelimit) {
3041 extern int inserver;
3043 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3044 doexit(GOOD_EXIT,0);
3047 /* if (!quiet) printf("?Timed out\n"); */
3053 case -9: /* Buffer overflow */
3054 printf("Command or field too long\n");
3057 case -3: /* Quit/Timeout */
3059 case -2: /* Overflow */
3060 case -1: /* Deletion */
3063 xc++; /* Just count it */
3065 case 1: /* CR or LF */
3066 if (xc == 0) *xp = xdef;
3067 if (f) { /* If a conversion function is given */
3069 zq = atxbuf; /* Point to the expansion buffer */
3070 atxn = CMDBL; /* specify its length */
3071 /* debug(F111,"cmtxt calling (*f)",*xp,atxbuf); */
3072 if ((x = (*f)(*xp,&zq,&atxn)) < 0) return(-2);
3076 while (*sx++) cc++; /* (faster than calling strlen) */
3078 cc = (int)strlen(atxbuf);
3079 #endif /* COMMENT */
3080 /* Should be equal to (CMDBL - atxn) but isn't always. */
3082 if (cc < 1) { /* Nothing in expansion buffer? */
3083 *xp = xdef; /* Point to default string instead. */
3086 while (*sx++) cc++; /* (faster than calling strlen) */
3089 #endif /* COMMENT */
3090 } else { /* Expansion function got something */
3091 *xp = atxbuf; /* return pointer to it. */
3093 debug(F111,"cmtxt (*f)",*xp,cc);
3094 } else { /* No expansion function */
3096 /* Avoid a strlen() call */
3101 /* NO! xc is apparently not always set appropriately */
3103 #endif /* COMMENT */
3107 /* strlen() no longer needed */
3108 for (i = (int)strlen(xx) - 1; i > 0; i--)
3110 for (i = cc - 1; i > 0; i--)
3111 #endif /* COMMENT */
3112 if (xx[i] != SP) /* Trim trailing blanks */
3118 if (xc == 0) { /* Nothing typed yet */
3119 if (*xdef) { /* Have a default for this field? */
3120 printf("%s ",xdef); /* Yes, supply it */
3121 inword = cmflgs = 0;
3126 } else bleep(BP_WARN); /* No default */
3127 } else { /* Already in field */
3130 if (ckstrcmp(atmbuf,xdef,x,0)) { /* Matches default? */
3131 bleep(BP_WARN); /* No */
3132 } else if ((int)strlen(xdef) > x) { /* Yes */
3139 inword = cmflgs = 0;
3140 debug(F110,"cmtxt: addbuf",cmdbuf,0);
3146 case 3: /* Question Mark */
3148 printf(" Text string");
3151 printf("\n%s%s",cmprom,cmdbuf);
3155 printf("?Unexpected return code from gtword() - %d\n",x);
3162 /* C M K E Y -- Parse a keyword */
3166 table -- keyword table, in 'struct keytab' format;
3167 n -- number of entries in table;
3168 xhlp -- pointer to help string;
3169 xdef -- pointer to default keyword;
3170 f -- string preprocessing function (e.g. to evaluate variables)
3171 pmsg -- 0 = don't print error messages
3172 1 = print error messages
3173 2 = include CM_HLP keywords even if invisible
3175 4 = parse a switch (keyword possibly ending in : or =)
3176 8 = don't strip comments (used, e.g., for "help #")
3178 -3 -- no input supplied and no default available
3179 -2 -- input doesn't uniquely match a keyword in the table
3180 -1 -- user deleted too much, command reparse required
3181 n >= 0 -- value associated with keyword
3185 Front ends for cmkey2():
3186 cmkey() - The normal keyword parser
3187 cmkeyx() - Like cmkey() but suppresses error messages
3188 cmswi() - Switch parser
3191 cmkey(table,n,xhlp,xdef,f)
3192 /* cmkey */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3193 return(cmkey2(table,n,xhlp,xdef,"",f,1));
3196 cmkeyx(table,n,xhlp,xdef,f)
3197 /* cmkeyx */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3198 return(cmkey2(table,n,xhlp,xdef,"",f,0));
3201 cmswi(table,n,xhlp,xdef,f)
3202 /* cmswi */ struct keytab table[]; int n; char *xhlp, *xdef; xx_strp f; {
3203 return(cmkey2(table,n,xhlp,xdef,"",f,4));
3207 cmkey2(table,n,xhlp,xdef,tok,f,pmsg)
3208 struct keytab table[];
3215 extern int havetoken;
3216 int i, tl, y, z = 0, zz, xc, wordlen = 0, cmswitch;
3219 if (!xhlp) xhlp = "";
3220 if (!xdef) xdef = "";
3224 printf("?Keyword table missing\n");
3227 tl = (int)strlen(tok);
3229 inword = xc = cc = 0; /* Clear character counters. */
3230 cmswitch = pmsg & 4; /* Flag for parsing a switch */
3232 debug(F101,"cmkey: pmsg","",pmsg);
3233 debug(F101,"cmkey: cmflgs","",cmflgs);
3234 debug(F101,"cmkey: cmswitch","",cmswitch);
3235 /* debug(F101,"cmkey: cmdbuf","",cmdbuf);*/
3239 if ((zz = cmflgs) == 1) { /* Command already entered? */
3240 if (setatm(xdef,0) < 0) { /* Yes, copy default into atom buf */
3241 printf("?Default too long\n");
3244 rtimer(); /* Reset timer */
3245 } else { /* Otherwise get a command word */
3246 rtimer(); /* Reset timer */
3247 if (pmsg & 8) /* 8 is for parsing HELP tokens */
3250 zz = gtword((pmsg == 4) ? 1 : 0);
3253 debug(F101,"cmkey table length","",n);
3254 debug(F101,"cmkey cmflgs","",cmflgs);
3255 debug(F101,"cmkey cc","",cc);
3259 debug(F111,"cmkey gtword xc",atmbuf,xc);
3260 debug(F101,"cmkey gtword zz","",zz);
3263 case -10: /* Timeout */
3264 if (gtimer() < timelimit) {
3265 if (pmsg & 8) /* 8 is for parsing HELP tokens */
3268 zz = gtword((pmsg == 4) ? 1 : 0);
3272 extern int inserver;
3274 printf("\r\nIKSD IDLE TIMEOUT: %d sec\r\n", timelimit);
3275 doexit(GOOD_EXIT,0);
3283 printf("Command or field too long\n");
3285 case -3: /* Null Command/Quit/Timeout */
3286 case -2: /* Buffer overflow */
3287 case -1: /* Or user did some deleting. */
3288 return(cmflgs = zz);
3292 case 0: /* User terminated word with space */
3293 case 4: /* or switch ending in : or = */
3294 wordlen = cc; /* Length if no conversion */
3295 if (cc == 0) { /* Supply default if we got nothing */
3296 if ((wordlen = setatm(xdef,(zz == 4) ? 2 : 0)) < 0) {
3297 printf("?Default too long\n");
3301 if (zz == 1 && cc == 0) /* Required field missing */
3304 if (f) { /* If a conversion function is given */
3306 zq = atxbuf; /* apply it */
3309 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3310 debug(F110,"cmkey atxbuf after *f",atxbuf,0);
3311 if (!*p2) /* Supply default if we got nothing */
3313 ckstrncpy(ppvnambuf,atmbuf,PPVLEN);
3314 if ((wordlen = setatm(p2,(zz == 4) ? 2 : 0)) < 0) {
3315 printf("Evaluated keyword too long\n");
3320 This bit lets us save more than one "word".
3321 For example, "define \%x echo one two three", "\%x".
3322 It works too, but it breaks labels, and therefore
3323 WHILE and FOR loops, etc.
3325 if (p2[wordlen] >= SP) {
3327 while (*p2 == SP) p2++;
3335 #ifdef COMMENT /* ^^^ */
3336 if (cmswitch && *atmbuf != '/') {
3339 printf("?Not a switch - %s\n",atmbuf);
3344 #endif /* COMMENT */
3347 for (i = 0; i < wordlen; i++) {
3348 if (atmbuf[i] == ':' || atmbuf[i] == '=') {
3349 brkchar = atmbuf[i];
3357 /* This was an effective optimization but it breaks sometimes on labels. */
3358 if (tl && !isalpha(atmbuf[0])) { /* Precheck for token */
3359 for (i = 0; i < tl; i++) { /* Save function call to ckstrchr */
3360 if (tok[i] == atmbuf[0]) {
3361 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3362 ungword(); /* Put back the following word */
3363 return(-5); /* Special return code for token */
3367 #endif /* TOKPRECHECK */
3369 y = lookup(table,atmbuf,n,&z); /* Look up word in the table */
3370 debug(F111,"cmkey lookup",atmbuf,y);
3371 debug(F101,"cmkey zz","",zz);
3372 debug(F101,"cmkey cmflgs","",cmflgs);
3373 debug(F101,"cmkey crflag","",crflag);
3375 case -3: /* Nothing to look up */
3377 case -2: /* Ambiguous */
3381 printf("?Ambiguous - %s\n",atmbuf);
3385 case -1: /* Not found at all */
3388 for (i = 0; i < tl; i++) /* Check for token */
3389 if (tok[i] == *atmbuf) { /* Got one */
3390 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3391 ungword(); /* Put back the following word */
3392 return(-5); /* Special return code for token */
3395 #endif /* TOKPRECHECK */
3397 if (tl == 0) { /* No tokens were included */
3399 /* In OS/2 and Windows, allow for a disk letter like DOS */
3400 if (isalpha(*atmbuf) && *(atmbuf+1) == ':')
3403 if ((pmsg & 1) && !quiet) {
3405 printf("?No keywords match - %s\n",atmbuf); /* cmkey */
3407 return(cmflgs = -9);
3409 if (cmflgs == 1 || cmswitch) /* cmkey2 or cmswi */
3410 return(cmflgs = -6);
3412 return(cmflgs = -2);
3413 /* The -6 code is to let caller try another table */
3418 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3419 #endif /* CK_RECALL */
3422 cmkwflgs = table[z].flgs;
3427 case 2: /* User terminated word with ESC */
3428 debug(F101,"cmkey Esc cc","",cc);
3430 if (*xdef != NUL) { /* Nothing in atmbuf */
3431 printf("%s ",xdef); /* Supply default if any */
3436 if (setatm(xdef,0) < 0) {
3437 printf("?Default too long\n");
3440 inword = cmflgs = 0;
3441 debug(F111,"cmkey: default",atmbuf,cc);
3443 debug(F101,"cmkey Esc pmsg","",0);
3446 Chained FDBs... The idea is that this function might not have a default,
3447 but the next one might. But if it doesn't, there is no way to come back to
3448 this one. To be revisited later...
3450 if (xcmfdb) /* Chained fdb -- try next one */
3452 #endif /* COMMENT */
3453 if (pmsg & (1|4)) { /* So for now just beep */
3459 if (f) { /* If a conversion function is given */
3461 zq = atxbuf; /* apply it */
3464 if ((*f)(atmbuf,&zq,&atxn) < 0)
3468 if (setatm(pp,0) < 0) {
3469 printf("Evaluated keyword too long\n");
3473 y = lookup(table,atmbuf,n,&z); /* Something in atmbuf */
3474 debug(F111,"cmkey lookup y",atmbuf,y);
3475 debug(F111,"cmkey lookup z",atmbuf,z);
3476 if (y == -2 && z >= 0 && z < n) { /* Ambiguous */
3478 int j, k, len = 9999; /* Do partial completion */
3479 /* Skip past any abbreviations in the table */
3480 for ( ; z < n; z++) {
3481 if ((table[z].flgs & CM_ABR) == 0)
3483 if (!(table[z].flgs & CM_HLP) || (pmsg & 2))
3486 debug(F111,"cmkey partial z",atmbuf,z);
3487 debug(F111,"cmkey partial n",atmbuf,n);
3488 for (j = z+1; j < n; j++) {
3489 debug(F111,"cmkey partial j",table[j].kwd,j);
3490 if (ckstrcmp(atmbuf,table[j].kwd,cc,0))
3492 if (table[j].flgs & CM_ABR)
3494 if ((table[j].flgs & CM_HLP) && !(pmsg & 2))
3496 k = ckstrpre(table[z].kwd,table[j].kwd);
3497 debug(F111,"cmkey partial k",table[z].kwd,k);
3499 len = k; /* Length of longest common prefix */
3501 debug(F111,"cmkey partial len",table[z].kwd,len);
3502 if (len != 9999 && len > cc) {
3503 ckstrncat(atmbuf,table[z].kwd+cc,ATMBL);
3505 printf("%s",atmbuf+cc);
3506 ckstrncat(cmdbuf,atmbuf+cc,CMDBL);
3510 #endif /* NOPARTIAL */
3513 } else if (y == -3) {
3516 } else if (y == -1) { /* Not found */
3517 if ((pmsg & 1) && !quiet) {
3519 printf("?No keywords match - \"%s\"\n",atmbuf);
3525 If we found it, but it's a help-only keyword and the "help" bit is not
3526 set in pmsg, then not found.
3528 debug(F101,"cmkey flgs","",table[z].flgs);
3529 if (test(table[z].flgs,CM_HLP) && ((pmsg & 2) == 0)) {
3530 if ((pmsg & 1) && !quiet) {
3532 printf("?No keywords match - %s\n",atmbuf);
3538 See if the keyword just found has the CM_ABR bit set in its flgs field, and
3539 if so, search forwards in the table for a keyword that has the same kwval
3540 but does not have CM_ABR (or CM_INV?) set, and then expand using the full
3541 keyword. WARNING: This assumes that (a) keywords are in alphabetical order,
3542 and (b) the CM_ABR bit is set only if the the abbreviated keyword is a true
3543 abbreviation (left substring) of the full keyword.
3545 if (test(table[z].flgs,CM_ABR)) {
3547 for (zz = z+1; zz < n; zz++)
3548 if ((table[zz].kwval == table[z].kwval) &&
3549 (!test(table[zz].flgs,CM_ABR)) &&
3550 (!test(table[zz].flgs,CM_INV))) {
3555 xp = table[z].kwd + cc;
3556 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3569 if (test(table[z].flgs,CM_NOR)) no_recall = 1;
3570 #endif /* CK_RECALL */
3571 cmkwflgs = table[z].flgs;
3576 if (cmswitch && test(table[z].flgs,CM_ARG)) {
3577 bp--; /* Replace trailing space with : */
3589 debug(F110,"cmkey: addbuf",cmdbuf,0);
3592 case 3: /* User typed "?" */
3593 if (f) { /* If a conversion function is given */
3595 zq = atxbuf; /* do the conversion now. */
3598 if ((*f)(atmbuf,&zq,&atxn) < 0) return(-2);
3599 if (setatm(pp,0) < 0) {
3600 printf("?Evaluated keyword too long\n");
3604 y = lookup(table,atmbuf,n,&z); /* Look up what we have so far. */
3607 Strictly speaking if the main keyword table search fails,
3608 then we should look in the token table if one is given.
3609 But in practice, tokens are also included in the main
3613 if ((pmsg & 1) && !quiet) {
3615 printf(" No keywords match\n");
3621 /* This is to allow ?-help to work immediately after a token */
3622 /* without having to type an intermediate space */
3624 for (i = 0; i < tl; i++) /* Check for token */
3625 if (tok[i] == *atmbuf) { /* Got one */
3626 debug(F000,"cmkey token:",atmbuf,*atmbuf);
3627 ungword(); /* Put back the following word */
3628 cmflgs = 3; /* Force help next time around */
3629 return(-5); /* Special return code for token */
3632 #endif /* COMMENT */
3635 printf(" One of the following:\n");
3637 printf(" %s, one of the following:\n",xhlp);
3640 x = pmsg & (2|4); /* See kwdhelp() comments */
3641 if (atmbuf[0]) /* If not at beginning of field */
3642 x |= 1; /* also show invisibles */
3643 kwdhelp(table,n,atmbuf,"","",1,x);
3648 if (tl > 0 && topcmd != XXHLP) /* This is bad... */
3649 printf("or a macro name (\"do ?\" for a list) ");
3652 if (*atmbuf == NUL && !havetoken) {
3654 printf("or the token %c\n",*tok);
3656 printf("or one of the tokens: %s\n",ckspread(tok));
3658 printf("%s%s", cmprom, cmdbuf);
3663 printf("\n%d - Unexpected return code from gtword\n",zz);
3664 return(cmflgs = -2);
3666 zz = (pmsg & 8) ? gtword(4) : gtword((pmsg == 4) ? 1 : 0);
3667 debug(F111,"cmkey gtword zz",atmbuf,zz);
3672 chktok(tlist) char *tlist; {
3675 while (*p != NUL && *p != *atmbuf) p++;
3676 return((*p) ? (int) *p : 0);
3679 /* Routines for parsing and converting dates and times */
3681 #define isdatesep(c) (ckstrchr(" -/._",c))
3683 #define CMDATEBUF 1024
3684 char cmdatebuf[CMDATEBUF+4] = { NUL, NUL };
3685 static char * cmdatebp = cmdatebuf;
3686 char * cmdatemsg = NULL;
3688 static struct keytab timeunits[] = {
3689 { "days", TU_DAYS, 0 },
3690 { "months", TU_MONTHS, 0 },
3691 { "weeks", TU_WEEKS, 0 },
3692 { "wks", TU_WEEKS, 0 },
3693 { "years", TU_YEARS, 0 },
3694 { "yrs", TU_YEARS, 0 }
3696 static int nunits = (sizeof(timeunits) / sizeof(struct keytab));
3703 static struct keytab symdaytab[] = {
3704 { "now", SYM_NOW, 0 },
3705 { "today", SYM_TODA, 0 },
3706 { "tomorrow", SYM_TOMO, 0 },
3707 { "yesterday", SYM_YEST, 0 }
3709 static int nsymdays = (sizeof(symdaytab) / sizeof(struct keytab));
3711 static struct keytab daysofweek[] = {
3714 { "Saturday", 6, 0 },
3716 { "Thursday", 4, 0 },
3717 { "Tuesday", 2, 0 },
3718 { "Wednesday", 3, 0 }
3721 static struct keytab usatz[] = { /* RFC 822 timezones */
3722 { "cdt", 5, 0 }, /* Values are GMT offsets */
3734 static int nusatz = (sizeof(usatz) / sizeof(struct keytab));
3737 /* C M C V T D A T E -- Converts free-form date to standard form. */
3741 s = pointer to free-format date, time, or date and time.
3742 t = 0: return time only if time was given in s.
3743 t = 1: always return time (00:00:00 if no time given in s).
3744 t = 2: allow time to be > 24:00:00.
3747 Pointer to "yyyymmdd hh:mm:ss" (local date-time) on success.
3751 Before final release the following long lines should be wrapped.
3752 Until then we leave them long since wrapping them wrecks EMACS's
3756 /* asctime pattern */
3757 static char * atp1 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [0-9][0-9][0-9][0-9]";
3759 /* asctime pattern with timezone */
3760 static char * atp2 = "[A-Z][a-z][a-z] [A-Z][a-z][a-z] [ 0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] [A-Z][A-Z][A-Z] [0-9][0-9][0-9][0-9]";
3762 #define DATEBUFLEN 127
3765 #define isleap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
3766 static int mdays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
3774 #define DELTABUF 256
3775 static char deltabuf[DELTABUF];
3776 static char * deltabp = deltabuf;
3779 cmdelta(yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss)
3780 int yy, mo, dd, hh, mm, ss, sign, dyy, dmo, ddd, dhh, dmm, dss;
3782 int zyy, zmo, zdd, zhh, zmm, zss;
3783 long t1, t2, t3, t4;
3784 long d1 = 0, d2, d3;
3785 char datebuf[DATEBUFLEN+1];
3789 debug(F101,"cmdelta yy","",yy);
3790 debug(F101,"cmdelta mo","",mo);
3791 debug(F101,"cmdelta dd","",dd);
3792 debug(F101,"cmdelta hh","",hh);
3793 debug(F101,"cmdelta mm","",mm);
3794 debug(F101,"cmdelta ss","",ss);
3795 debug(F101,"cmdelta sign","",sign);
3796 debug(F101,"cmdelta dyy","",dyy);
3797 debug(F101,"cmdelta dmo","",dmo);
3798 debug(F101,"cmdelta ddd","",ddd);
3799 debug(F101,"cmdelta dhh","",dhh);
3800 debug(F101,"cmdelta dmm","",dmm);
3801 debug(F101,"cmdelta dss","",dss);
3805 if (yy < 0 || yy > 9999) {
3806 makestr(&cmdatemsg,"Base year out of range");
3807 debug(F111,"cmdelta",cmdatemsg,-1);
3810 if (mo < 1 || mo > 12) {
3811 makestr(&cmdatemsg,"Base month out of range");
3812 debug(F111,"cmdelta",cmdatemsg,-1);
3815 if (dd < 1 || dd > mdays[mo]) {
3816 makestr(&cmdatemsg,"Base day out of range");
3817 debug(F111,"cmdelta",cmdatemsg,-1);
3820 if (hh < 0 || hh > 23) {
3821 makestr(&cmdatemsg,"Base hour out of range");
3822 debug(F111,"cmdelta",cmdatemsg,-1);
3825 if (mm < 0 || mm > 59) {
3826 makestr(&cmdatemsg,"Base minute out of range");
3827 debug(F111,"cmdelta",cmdatemsg,-1);
3830 if (ss < 0 || ss > 60) {
3831 makestr(&cmdatemsg,"Base second out of range");
3832 debug(F111,"cmdelta",cmdatemsg,-1);
3835 sign = (sign < 0) ? -1 : 1;
3843 } else if (sign < 0) {
3852 mo = 12 - (dmo - mo);
3858 if (yy > 9999 || yy < 0) {
3859 makestr(&cmdatemsg,"Result year out of range");
3860 debug(F111,"cmdelta",cmdatemsg,-1);
3864 sprintf(datebuf,"%04d%02d%02d %02d:%02d:%02d",yy,mo,dd,hh,mm,ss);
3866 debug(F111,"cmdelta mjd",datebuf,d1);
3868 t1 = hh * 3600 + mm * 60 + ss; /* Base time to secs since midnight */
3869 t2 = dhh * 3600 + dmm * 60 + dss; /* Delta time, ditto */
3870 t3 = t1 + (sign * t2); /* Get sum (or difference) */
3872 d2 = (sign * ddd); /* Delta days */
3875 t4 = t3 % 86400L; /* Fractional part of day */
3876 if (t4 < 0) { /* If negative */
3877 d2--; /* one less delta day */
3878 t4 += 86400L; /* get positive seconds */
3880 hh = (int) (t4 / 3600L);
3881 mm = (int) (t4 % 3600L) / 60;
3882 ss = (int) (t4 % 3600L) % 60;
3884 sprintf(datebuf,"%s %02d:%02d:%02d", mjd2date(d1+d2),hh,mm,ss);
3888 len = strlen(datebuf);
3889 k = deltabp - (char *)deltabuf; /* Space used */
3890 n = DELTABUF - k - 1; /* Space left */
3891 if (n < len) { /* Not enough? */
3892 deltabp = deltabuf; /* Wrap around */
3895 ckstrncpy(deltabp,datebuf,n);
3903 /* Convert Delta Time to Seconds */
3906 delta2sec(s,result) char * s; long * result; {
3907 long ddays = 0L, zz;
3908 int dsign = 1, dhours = 0, dmins = 0, dsecs = 0, units;
3909 int state = NEED_DAYS;
3910 char *p, *p2, *p3, c = 0;
3916 if ((int)strlen(s) > 63)
3918 ckstrncpy(buf,s,64);
3921 if (*p != '+' && *p != '-')
3926 while (*p == SP) /* Skip intervening spaces */
3929 while (state) { /* FSA to parse delta time */
3930 if (state < 0 || !isdigit(*p))
3932 p2 = p; /* Get next numeric field */
3933 while (isdigit(*p2))
3935 c = *p2; /* And break character */
3936 *p2 = NUL; /* Terminate the number */
3937 switch (state) { /* Interpret according to state */
3938 case NEED_DAYS: /* Initial */
3939 if ((c == '-') || /* VMS format */
3940 ((c == 'd' || c == 'D')
3941 && !isalpha(*(p2+1)))) { /* Days */
3945 else /* if anything is left */
3946 state = NEED_HRS; /* now we want hours. */
3947 } else if (c == ':') { /* delimiter is colon */
3948 dhours = atoi(p); /* so it's hours */
3949 state = NEED_MINS; /* now we want minutes */
3950 } else if (!c) { /* end of string */
3951 dhours = atoi(p); /* it's still hours */
3952 state = 0; /* and we're done */
3953 } else if (isalpha(c) || c == SP) {
3954 if (c == SP) { /* It's a keyword? */
3955 p2++; /* Skip spaces */
3958 } else { /* or replace first letter */
3961 p3 = p2; /* p2 points to beginning of keyword */
3962 while (isalpha(*p3)) /* Find end of keyword */
3964 c = *p3; /* NUL it out so we can look it up */
3965 if (*p3) /* p3 points to keyword terminator */
3967 if ((units = lookup(timeunits,p2,nunits,NULL)) < 0)
3969 *p2 = NUL; /* Re-terminate the number */
3971 while (*p3 == SP) /* Point at field after units */
3986 } else { /* Anything else */
3987 state = -1; /* is an error */
3990 case NEED_HRS: /* Looking for hours */
4001 case NEED_MINS: /* Looking for minutes */
4012 case NEED_SECS: /* Looking for seconds */
4023 case NEED_FRAC: /* Fraction of second */
4024 if (!c && rdigits(p)) {
4033 if (c) /* next field if any */
4039 /* if days > 24854 and sizeof(long) == 32 we overflow */
4041 zz = ddays * 86400L;
4042 if (zz < 0L) /* This catches it */
4044 zz += dhours * 3600L + dmins * 60L + dsecs;
4052 cmcvtdate(s,t) char * s; int t; {
4053 int x, i, j, k, hh, mm, ss, ff, pmflag = 0, nodate = 0, len, dow;
4054 int units, isgmt = 0, gmtsign = 0, d = 0, state = 0, nday;
4055 int kn = 0, ft[8], isletter = 0, f2len = 0;
4057 int zhh = 0; /* Timezone adjustments */
4061 int dsign = 1; /* Delta-time adjustments */
4070 char * fld[8], * p = "", * p2, * p3; /* Assorted buffers and pointers */
4072 char * year = NULL, * month = NULL, * day = NULL;
4073 char * hour = "00", * min = "00", * sec = "00";
4076 char xbuf[DATEBUFLEN+1];
4077 char ybuf[DATEBUFLEN+1];
4078 char zbuf[DATEBUFLEN+1];
4079 char yyyymmdd[YYYYMMDD];
4084 char timbuf[16], *tb, cc;
4085 char * dp = NULL; /* Result pointer */
4090 while (*s == SP) s++; /* Gobble any leading blanks */
4091 if (isalpha(*s)) /* Remember if 1st char is a letter */
4095 debug(F110,"cmcvtdate",s,len);
4096 if (len == 0) { /* No arg - return current date-time */
4100 if (len > DATEBUFLEN) { /* Check length of arg */
4101 makestr(&cmdatemsg,"Date-time string too long");
4102 debug(F111,"cmcvtdate",cmdatemsg,-1);
4105 hh = 0; /* Init time to 00:00:00.0 */
4112 if (*p) { /* Init time to current time */
4113 x = ckstrncpy(dbuf,p,26);
4115 hh = atoi(&dbuf[11]);
4116 mm = atoi(&dbuf[14]);
4117 ss = atoi(&dbuf[17]);
4120 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD); /* Init date to current date */
4121 ckstrncpy(yearbuf,yyyymmdd,5);
4122 ckstrncpy(monbuf,&yyyymmdd[4],3);
4123 ckstrncpy(daybuf,&yyyymmdd[6],3);
4127 nday = atoi(daybuf);
4128 ckstrncpy(xbuf,s,DATEBUFLEN); /* Make a local copy we can poke */
4129 s = xbuf; /* Point to it */
4135 /* Special preset formats... */
4137 if (len >= 14) { /* FTP MDTM all-numeric date */
4139 c = s[14]; /* e.g. 19980615100045.014 */
4144 ckstrncpy(yyyymmdd,s,8+1);
4150 x = 0; /* Becomes > 0 for asctime format */
4151 if (isalpha(s[0])) {
4152 if (len == 24) { /* Asctime format? */
4153 /* Sat Jul 14 15:57:32 2001 */
4154 x = ckmatch(atp1,s,0,0);
4155 debug(F111,"cmcvtdate asctime",s,x);
4156 } else if (len == 28) { /* Or Asctime plus timezone? */
4157 /* Sat Jul 14 15:15:39 EDT 2001 */
4158 x = ckmatch(atp2,s,0,0);
4159 debug(F111,"cmcvtdate asctime+timezone",s,x);
4162 if (x > 0) { /* Asctime format */
4164 strncpy(yearbuf,s + len - 4,4);
4166 for (i = 0; i < 3; i++)
4169 if ((xx = lookup(cmonths,tmpbuf,12,NULL)) < 0) {
4170 makestr(&cmdatemsg,"Invalid month");
4171 debug(F111,"cmcvtdate",cmdatemsg,-1);
4174 debug(F101,"cmcvtdate asctime month","",xx);
4175 monbuf[0] = (xx / 10) + '0';
4176 monbuf[1] = (xx % 10) + '0';
4178 daybuf[0] = (s[8] == ' ' ? '0' : s[8]);
4182 for (i = 11; i < 19; i++)
4185 ckmakmsg(zbuf,18,yearbuf,monbuf,daybuf,xbuf);
4186 debug(F110,"cmcvtdate asctime ok",zbuf,0);
4192 n = ckmakmsg(ybuf,DATEBUFLEN-4,zbuf," ",NULL,NULL);
4197 ckstrncpy(xbuf,ybuf,DATEBUFLEN);
4203 /* Check for day of week */
4206 while (*p == SP) p++;
4212 if (*p2 == ',' || *p2 == SP || !*p2) {
4213 cc = *p2; /* Save break char */
4214 *p2 = NUL; /* NUL it out */
4215 p3 = p2; /* Remember this spot */
4216 if ((dow = lookup(daysofweek,p,7,NULL)) > -1) {
4217 debug(F111,"cmcvtdate dow",p,dow);
4219 if (cc == ',' || cc == SP) { /* Point to next field */
4221 while (*s == SP) s++;
4224 debug(F111,"cmcvtdate dow new p",p,dow);
4226 } else if (isalpha(*p) && cc == ',') {
4227 makestr(&cmdatemsg,"Unrecognized day of week");
4228 debug(F111,"cmcvtdate",cmdatemsg,-1);
4238 len = strlen(s); /* Update length */
4239 debug(F111,"cmcvtdate s",s,len);
4241 debug(F111,"cmcvtdate dow",s,dow);
4242 if (dow > -1) { /* Have a day-of-week number */
4244 zz = mjd(zzndate()); /* Get today's MJD */
4245 debug(F111,"cmcvtdate zz","",zz);
4246 j = (((int)(zz % 7L)) + 3) % 7; /* Today's day-of-week number */
4247 debug(F111,"cmcvtdate j","",j);
4248 hh = 0; /* Init time to midnight */
4252 ckstrncpy(yyyymmdd,zzndate(),YYYYMMDD);
4255 n = dow - j; /* Days from now */
4258 if (n < 0) n += 7; /* Add to MJD */
4260 ckstrncpy(yyyymmdd,mjd2date(zz),YYYYMMDD); /* New date */
4263 debug(F111,"cmcvtdate A",yyyymmdd,len);
4264 if (len == 0) { /* No more fields after this */
4265 ckmakmsg(zbuf,18,yyyymmdd," 00:00:00",NULL,NULL);
4270 if (rdigits(p) && len < 8) /* Next field is time? */
4271 goto dotime; /* If so go straight to time section */
4275 else if (isdigit(*(p+1)) && (*(p+2) == ':'))
4279 debug(F111,"cmcvtdate B s",s,dow);
4280 debug(F111,"cmcvtdate B p",p,dow);
4282 if (*s == '+' || *s == '-') { /* Delta time only - skip ahead. */
4288 What is the purpose of this? It breaks parsing of email dates like
4289 "Wed, 13 Feb 2002 17:43:02 -0800 (PST)". Removing this code fixes the
4290 problem and Kermit still passes the 'dates' script.
4291 - fdc, Sat Nov 26 10:52:45 2005.
4294 /* Day of week given followed by something that is not a time */
4295 /* or a delta so it can't be valid */
4296 makestr(&cmdatemsg,"Invalid tokens after day of week");
4297 debug(F111,"cmcvtdate fail",cmdatemsg,-1);
4300 #endif /* COMMENT */
4302 /* Handle "today", "yesterday", "tomorrow", and +/- n units */
4304 if (ckstrchr("TtYyNn",s[0])) {
4305 int i, k, n, minus = 0;
4309 debug(F111,"cmcvtdate mjd",s,jd);
4311 /* Symbolic date: TODAY, TOMORROW, etc...? */
4313 s2 = s; /* Find end of keyword */
4315 while (isalpha(*s2)) { /* and get its length */
4319 c = *s2; /* Zap but save delimiter */
4321 k = lookup(symdaytab,s,nsymdays,NULL); /* Look up keyword */
4322 *s2 = c; /* Replace delimiter */
4323 if (k < 0) /* Keyword not found */
4326 while (*s3 == SP) /* Skip whitespace */
4328 if (*s3 == '_' || *s3 == ':')
4331 switch (k) { /* Have keyword */
4332 case SYM_NOW: /* NOW */
4333 ckstrncpy(ybuf,ckdate(),DATEBUFLEN);
4334 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4336 if (*s3) { /* No overwriting current time. */
4337 ckstrncat(ybuf," ",DATEBUFLEN);
4338 ckstrncat(ybuf,s3,DATEBUFLEN);
4341 default: /* Yesterday, Today, and Tomorrow */
4342 if (k == SYM_TOMO) { /* TOMORROW */
4343 strncpy(ybuf,mjd2date(jd+1),8);
4344 } else if (k == SYM_YEST) { /* YESTERDAY */
4345 strncpy(ybuf,mjd2date(jd-1),8);
4346 } else { /* TODAY */
4347 strncpy(ybuf,ckdate(),8);
4349 strncpy(ybuf+8," 00:00:00",DATEBUFLEN-8); /* Default time is 0 */
4350 ckstrncpy(yyyymmdd,ybuf,YYYYMMDD);
4352 if (*s3) { /* If something follows keyword... */
4353 if (isdigit(*s3)) { /* Time - overwrite default time */
4354 strncpy(ybuf+8,s+i,DATEBUFLEN-8);
4355 } else { /* Something else, keep default time */
4356 ckstrncat(ybuf," ",DATEBUFLEN); /* and append */
4357 ckstrncat(ybuf,s3,DATEBUFLEN); /* whatever we have */
4361 s = ybuf; /* Point to rewritten date-time */
4362 len = strlen(s); /* Update length */
4363 isletter = 0; /* Cancel this */
4366 /* Regular free-format non-symbolic date */
4370 debug(F111,"cmcvtdate NORMAL",s,len);
4371 debug(F111,"cmcvtdate dow",s,dow);
4372 if (yyyymmdd[0] && !year) {
4373 ckstrncpy(yearbuf,yyyymmdd,5);
4374 ckstrncpy(monbuf,&yyyymmdd[4],3);
4375 ckstrncpy(daybuf,&yyyymmdd[6],3);
4379 nday = atoi(daybuf);
4381 if (isdigit(s[0])) { /* Time without date? */
4384 debug(F111,"cmcvtdate NORMAL X1",s,len);
4386 } else if (len > 1 && isdigit(s[1]) && s[2] == ':') {
4387 debug(F111,"cmcvtdate NORMAL X2",s,len);
4389 } else if (rdigits(s) && len < 8) {
4390 debug(F111,"cmcvtdate NORMAL X3",s,len);
4394 if (len >= 8 && isdigit(*s)) { /* Check first for yyyymmdd* */
4395 debug(F111,"cmcvtdate NORMAL A",s,len);
4397 s[8] = NUL; /* Isolate first 8 characters */
4399 /* Have valid time separator? */
4400 p2 = cc ? ckstrchr(" Tt_-:",cc) : NULL;
4402 ckstrncpy(yyyymmdd,s,YYYYMMDD); /* Valid separator */
4404 s += 8; /* or time not given */
4405 if (cc) s++; /* Keep date */
4406 p = s; /* and go handle time */
4410 makestr(&cmdatemsg,"Numeric date too long");
4412 makestr(&cmdatemsg,"Invalid date-time separator");
4413 debug(F111,"cmcvtdate",cmdatemsg,-1);
4417 s[8] = cc; /* Put this back! */
4419 debug(F111,"cmcvtdate NORMAL non-yyyymmdd",s,len);
4421 /* Free-format date -- figure it out */
4424 if (*s && !isdigit(*s)) {
4425 makestr(&cmdatemsg,"Unrecognized word in date");
4426 debug(F111,"cmcvtdate",cmdatemsg,-1);
4429 #endif /* COMMENT */
4430 for (i = 0; i < 8; i++) /* Field types */
4432 fld[i = 0] = (p = s); /* First field */
4433 while (*p) { /* Get next two fields */
4434 if (isdatesep(*p)) { /* Have a date separator */
4437 } else if (i == 1 && *p != datesep) {
4438 makestr(&cmdatemsg,"Inconsistent date separators");
4439 debug(F111,"cmcvtdate",cmdatemsg,-1);
4442 *p++ = NUL; /* Replace by NUL */
4443 if (*p) { /* Now we're at the next field */
4444 while (*p == SP) p++; /* Skip leading spaces */
4445 if (!*p) break; /* Make sure we still have something */
4446 if (i == 2) /* Last one? */
4448 fld[++i] = p; /* No, record pointer to this one */
4452 } else if ((*p == 'T' || *p == 't') && isdigit(*(p+1))) { /* Time */
4455 } else if (*p == ':') {
4456 if (i == 0 && p == s) {
4459 } else if (i != 0) { /* After a date */
4460 if (i == 2) { /* OK as date-time separator (VMS) */
4465 makestr(&cmdatemsg,"Too few fields in date");
4467 makestr(&cmdatemsg,"Misplaced time separator");
4468 debug(F111,"cmcvtdate",cmdatemsg,-1);
4471 nodate = 1; /* Or without a date */
4476 if (p > s && i == 0) /* Make sure we have a date */
4477 nodate = 1; /* No date. */
4479 if (nodate && dow > -1) { /* Have implied date from DOW? */
4480 goto dotime; /* Use, use that, go do time. */
4482 } else if (nodate) { /* No date and no implied date */
4483 char *tmp = NULL; /* Substitute today's date */
4488 makestr(&cmdatemsg,"Problem supplying current date");
4489 debug(F111,"cmcvtdate",cmdatemsg,-1);
4492 ckstrncpy(dbuf,tmp,26); /* Reformat */
4493 if (dbuf[8] == SP) dbuf[8] = '0';
4494 fld[0] = dbuf+8; /* dd */
4496 fld[1] = dbuf+4; /* mmm */
4498 fld[2] = dbuf+20; /* yyyy */
4500 hh = atoi(&dbuf[11]);
4501 mm = atoi(&dbuf[14]);
4502 ss = atoi(&dbuf[17]);
4503 p = s; /* Back up source pointer to reparse */
4505 makestr(&cmdatemsg,"Too few fields in date");
4506 debug(F111,"cmcvtdate",cmdatemsg,-1);
4509 /* Have three date fields - see what they are */
4511 for (k = 0, j = 0; j < 3; j++) { /* Get number of non-numeric fields */
4512 ft[j] = rdigits(fld[j]);
4513 debug(F111,"cmcvtdate fld",fld[j],j);
4517 kn = k; /* How many numeric fields */
4518 month = NULL; /* Strike out default values */
4522 if (k == 2 && ft[2] > 0) { /* Jul 20, 2001 */
4524 xx = strlen(fld[1]);
4526 if (xx > 0) if (p3[xx-1] == ',') {
4531 } else p3[xx-1] = ',';
4534 if (k > 1) { /* We can have only one non-numeric */
4536 makestr(&cmdatemsg,"Unrecognized word in date");
4537 else if (!ft[2] && isdigit(*(fld[2])))
4538 makestr(&cmdatemsg,"Invalid date-time separator");
4540 makestr(&cmdatemsg,"Too many non-numeric fields in date");
4541 debug(F111,"cmcvtdate",cmdatemsg,-1);
4546 } else if (!ft[1]) {
4548 } else if (!ft[2]) {
4549 makestr(&cmdatemsg,"Non-digit in third date field");
4550 debug(F111,"cmcvtdate",cmdatemsg,-1);
4556 if ((x = lookup(cmonths,fld[k],12,NULL)) < 0) {
4557 makestr(&cmdatemsg,"Unknown month");
4558 debug(F111,"cmcvtdate",cmdatemsg,-1);
4561 sprintf(tmpbuf,"%02d",x);
4564 f2len = strlen(fld[2]); /* Length of 3rd field */
4566 if (k == 0) { /* monthname dd, yyyy */
4569 } else if (((int)strlen(fld[0]) == 4)) { /* yyyy-xx-dd */
4573 month = fld[1]; /* yyyy-mm-dd */
4574 } else if (f2len == 4) { /* xx-xx-yyyy */
4576 if (month) { /* dd-name-yyyy */
4578 } else { /* xx-xx-yyyy */
4582 if (((f0 > 12) && (f1 <= 12)) || (f1 <= 12 && f0 == f1)) {
4583 day = fld[0]; /* mm-dd-yyyy */
4585 } else if ((f0 <= 12) && (f1 > 12)) {
4586 if (!rdigits(fld[1])) {
4587 makestr(&cmdatemsg,"Day not numeric");
4588 debug(F111,"cmcvtdate",cmdatemsg,-1);
4591 day = fld[1]; /* dd-mm-yyyy */
4596 makestr(&cmdatemsg,"Day or month out of range");
4598 makestr(&cmdatemsg,"Day and month are ambiguous");
4599 debug(F111,"cmcvtdate",cmdatemsg,-1);
4603 } else if ((f2len < 4) && /* dd mmm yy (RFC822) */
4604 !rdigits(fld[1]) && /* middle field is monthname */
4609 makestr(&cmdatemsg,"Too few digits in year");
4610 debug(F111,"cmcvtdate",cmdatemsg,-1);
4613 tmpyear = atoi(fld[2]);
4614 if (tmpyear < 50) /* RFC 2822 windowing */
4616 else /* This includes 3-digit years. */
4618 year = ckitoa(tmpyear);
4620 } else if ((f2len < 4) && (k < 0) && ((int)strlen(fld[0]) < 4)) {
4621 makestr(&cmdatemsg,"Ambiguous numeric date");
4622 debug(F111,"cmcvtdate",cmdatemsg,-1);
4624 } else if ((f2len > 4) && ft[2]) {
4625 makestr(&cmdatemsg,"Too many digits in year");
4626 debug(F111,"cmcvtdate",cmdatemsg,-1);
4629 makestr(&cmdatemsg,"Unexpected date format");
4630 debug(F111,"cmcvtdate",cmdatemsg,-1);
4634 sprintf(tmpbuf,"%02d",x); /* 2-digit numeric month */
4640 state = 4 = fractions of seconds
4644 if (isletter && (s == p)) {
4645 makestr(&cmdatemsg,"Unknown date-time word");
4646 debug(F111,"cmcvtdate",cmdatemsg,-1);
4649 if (!year && yyyymmdd[0]) {
4650 debug(F110,"cmcvtdate dotime yyyymmdd",yyyymmdd,0);
4651 for (i = 0; i < 4; i++)
4652 yearbuf[i] = yyyymmdd[i];
4654 monbuf[0] = yyyymmdd[4];
4655 monbuf[1] = yyyymmdd[5];
4657 daybuf[0] = yyyymmdd[6];
4658 daybuf[1] = yyyymmdd[7];
4661 nday = atoi(daybuf);
4666 makestr(&cmdatemsg,"Internal error - date not defaulted");
4667 debug(F111,"cmcvtdate",cmdatemsg,-1);
4670 /* Get here with day, month, and year set */
4671 debug(F110,"cmcvtdate dotime day",day,0);
4672 debug(F110,"cmcvtdate dotime month",month,0);
4673 debug(F110,"cmcvtdate dotime year",year,0);
4674 debug(F110,"cmcvtdate dotime s",s,0);
4675 debug(F110,"cmcvtdate dotime p",p,0);
4677 if (x > 12 || x < 1) {
4678 makestr(&cmdatemsg,"Month out of range");
4679 debug(F111,"cmcvtdate",cmdatemsg,-1);
4684 if (x == 2) if (isleap(atoi(year))) i++;
4685 if (nday > i || nday < 1) {
4686 makestr(&cmdatemsg,"Day out of range");
4687 debug(F111,"cmcvtdate",cmdatemsg,-1);
4690 if (!*p && t == 0) {
4691 sprintf(zbuf,"%04d%02d%02d",atoi(year),atoi(month),nday);
4695 if (*p == '+' || *p == '-') { /* GMT offset without a time */
4696 hh = 0; /* so default time to 00:00:00 */
4699 goto cmtimezone; /* and go do timezone */
4701 if (*p && !isdigit(*p) && *p != ':') {
4702 makestr(&cmdatemsg,"Invalid time");
4703 debug(F111,"cmcvtdate",cmdatemsg,-1);
4706 sprintf(yyyymmdd,"%s%s%02d",year,month,nday); /* for tz calculations... */
4708 state = 1; /* Initialize time-parsing FSA */
4710 mm = 0; /* minutes */
4711 ss = 0; /* seconds */
4712 ff = -1; /* fraction */
4713 d = 0; /* Digit counter */
4714 p2 = p; /* Preliminary digit count... */
4715 while (isdigit(*p2)) {
4720 makestr(&cmdatemsg,"Too many time digits");
4721 debug(F111,"cmcvtdate",cmdatemsg,-1);
4724 d = (d & 1 && *p2 != ':') ? 1 : 0; /* Odd implies leading '0' */
4726 while (*p) { /* Get the time, if any */
4727 if (isdigit(*p)) { /* digit */
4734 hh = hh * 10 + (*p - '0');
4736 case 2: /* Minutes */
4737 mm = mm * 10 + (*p - '0');
4739 case 3: /* Seconds */
4740 ss = ss * 10 + (*p - '0');
4742 case 4: /* Fraction of second */
4744 ff = (*p > '4') ? 1 : 0;
4747 } else if (*p == ':') { /* Colon */
4751 makestr(&cmdatemsg,"Too many time fields");
4752 debug(F111,"cmcvtdate",cmdatemsg,-1);
4755 } else if (*p == '.') {
4760 makestr(&cmdatemsg,"Improper fraction");
4761 debug(F111,"cmcvtdate",cmdatemsg,-1);
4764 } else if (*p == SP) { /* Space */
4765 while (*p && (*p == SP)) /* position to first nonspace */
4768 } else if (isalpha(*p)) { /* AM/PM/Z or timezone */
4770 } else if (*p == '+' || *p == '-') { /* GMT offset */
4773 makestr(&cmdatemsg,"Invalid time characters");
4774 debug(F111,"cmcvtdate",cmdatemsg,-1);
4779 if (!*p) /* If nothing left */
4780 goto xcmdate; /* go finish up */
4782 /* At this point we have HH, MM, SS, and FF */
4783 /* Now handle the rest: AM, PM, and/or timezone info */
4785 if (!ckstrcmp(p,"am",2,0)) { /* AM/PM... */
4788 } else if (!ckstrcmp(p,"a.m.",4,0)) {
4791 } else if (!ckstrcmp(p,"pm",2,0)) {
4794 } else if (!ckstrcmp(p,"p.m.",4,0)) {
4798 if (pmflag && hh < 12) /* If PM was given */
4799 hh += 12; /* add 12 to the hour */
4801 /* Now handle timezone */
4804 debug(F110,"cmcvtdate timezone",p,0);
4806 zhh = 0; /* GMT offset HH */
4807 zmm = 0; /* GMT offset MM */
4808 gmtsign = 0; /* Sign of GMT offset */
4809 isgmt = 0; /* 1 if time is GMT */
4811 while (*p && *p == SP) /* Gobble spaces */
4813 if (!*p) /* If nothing left */
4814 goto xcmdate; /* we're done */
4816 if (isalpha(*p)) { /* Something left */
4817 int zone = 0; /* Alphabetic must be timezone */
4818 p2 = p; /* Isolate timezone */
4825 p = p2; /* Have timezone, look it up */
4826 zone = lookup(usatz,p,nusatz,NULL);
4827 debug(F111,"cmcvtdate timezone alpha",p,zone);
4829 if (zone < 0) { /* Not found */
4830 makestr(&cmdatemsg,"Unknown timezone");
4831 debug(F111,"cmcvtdate",cmdatemsg,-1);
4834 isgmt++; /* All dates are GMT from here down */
4835 if (zone != 0) { /* But not this one so make it GMT */
4836 hh += zone; /* RFC 822 timezone: EST etc */
4837 debug(F101,"cmcvtdate hh + zone","",hh);
4838 if (hh > 23) { /* Offset crosses date boundary */
4841 jd = mjd(yyyymmdd); /* Get MJD */
4842 jd += hh / 24; /* Add new day(s) */
4843 hh = hh % 24; /* and convert back to yyyymmdd */
4844 ckstrncpy(yyyymmdd,mjd2date(jd),YYYYMMDD);
4845 debug(F111,"cmcvtdate zone-adjusted date",yyyymmdd,hh);
4846 for (i = 0; i < 4; i++)
4847 yearbuf[i] = yyyymmdd[i];
4849 monbuf[0] = yyyymmdd[4];
4850 monbuf[1] = yyyymmdd[5];
4852 daybuf[0] = yyyymmdd[6];
4853 daybuf[1] = yyyymmdd[7];
4856 nday = atoi(daybuf);
4861 p = p3; /* Put back whatever we poked above */
4864 } else if (*p == '+' || *p == '-') { /* GMT/UTC offset */
4866 debug(F110,"cmcvtdate timezone GMT offset",p,0);
4867 gmtsign = (*p == '+') ? -1 : 1;
4870 while (*p == SP) p++;
4873 while (isdigit(*p)) { /* Count digits */
4877 if (d != 4) { /* Strict RFC [2]822 */
4878 isgmt = 0; /* If not exactly 4 digits */
4879 p = p3; /* it's not a GMT offset. */
4880 goto delta; /* So treat it as a delta time. */
4882 d = (d & 1 && *p != ':') ? 1 : 0; /* Odd implies leading '0' */
4884 debug(F111,"cmcvtdate GMT offset sign",p,gmtsign);
4885 debug(F101,"cmcvtdate GMT offset d","",d);
4888 if (isdigit(*p)) { /* digit */
4895 zhh = zhh * 10 + (*p - '0');
4898 zmm = zmm * 10 + (*p - '0');
4900 default: /* Ignore seconds or fractions */
4903 } else if (*p == ':') { /* Colon */
4906 } else if (*p == SP || *p == '(') {
4909 p = p3; /* Maybe it's not a GMT offset. */
4910 goto delta; /* So treat it as a delta time. */
4915 debug(F110,"cmcvtdate source string after timezone",p,0);
4917 if (*p) { /* Anything left? */
4919 while (*p2 == SP) /* Skip past spaces */
4921 if (*p2 == '(') { /* RFC-822 comment? */
4922 int pc = 1; /* paren counter */
4930 } else if (*p2 == ')') {
4935 while (*p2 == SP) /* Skip past spaces */
4937 if (!*p2) /* Anything left? */
4938 *p = NUL; /* No, erase comment */
4940 if (!*p2) /* Anything left? */
4941 goto xcmdate; /* No, done. */
4945 debug(F110,"cmcvtdate delta yyyymmdd",yyyymmdd,0);
4946 debug(F110,"cmcvtdate delta year",year,0);
4947 debug(F110,"cmcvtdate delta p",p,0);
4949 if (*p == '+' || *p == '-') { /* Delta time */
4950 int state = NEED_DAYS; /* Start off looking for days */
4952 dsign = 1; /* Get sign */
4955 while (*p == SP) /* Skip intervening spaces */
4957 while (state) { /* FSA to parse delta time */
4958 if (state < 0 || !isdigit(*p)) {
4959 makestr(&cmdatemsg,"Invalid delta time");
4960 debug(F111,"cmcvtdate",cmdatemsg,-1);
4963 p2 = p; /* Get next numeric field */
4964 while (isdigit(*p2))
4966 c = *p2; /* And break character */
4967 *p2 = NUL; /* Terminate the number */
4969 switch (state) { /* Interpret according to state */
4970 case NEED_DAYS: /* Initial */
4971 if ((c == '-') || /* VMS format */
4972 ((c == 'd' || c == 'D')
4973 && !isalpha(*(p2+1)))) { /* Days */
4977 else /* if anything is left */
4978 state = NEED_HRS; /* now we want hours. */
4979 } else if ((c == 'W' || c == 'w') && !isalpha(*(p2+1))) {
4980 ddays = atoi(p) * 7; /* weeks... */
4985 } else if ((c == 'M' || c == 'm') && !isalpha(*(p2+1))) {
4986 dmonths = atoi(p); /* months... */
4991 } else if ((c == 'Y' || c == 'y') && !isalpha(*(p2+1))) {
4992 dyears = atoi(p); /* years... */
4997 } else if (c == ':') { /* delimiter is colon */
4998 dhours = atoi(p); /* so it's hours */
4999 state = NEED_MINS; /* now we want minutes */
5000 } else if (!c) { /* end of string */
5001 dhours = atoi(p); /* it's still hours */
5002 state = 0; /* and we're done */
5003 } else if (isalpha(c) || c == SP) {
5004 if (c == SP) { /* It's a keyword? */
5005 p2++; /* Skip spaces */
5008 } else { /* or replace first letter */
5011 p3 = p2; /* p2 points to beginning of keyword */
5012 while (isalpha(*p3)) /* Find end of keyword */
5014 c = *p3; /* NUL it out so we can look it up */
5015 if (*p3) /* p3 points to keyword terminator */
5017 units = lookup(timeunits,p2,nunits,NULL);
5019 makestr(&cmdatemsg,"Invalid units in delta time");
5020 debug(F111,"cmcvtdate",cmdatemsg,-1);
5023 *p2 = NUL; /* Re-terminate the number */
5025 while (*p3 == SP) /* Point at field after units */
5033 ddays = atoi(p) * 7;
5048 } else { /* Anything else */
5049 state = -1; /* is an error */
5052 case NEED_HRS: /* Looking for hours */
5053 debug(F000,"cmcvtdate NEED_HRS",p,c);
5064 case NEED_MINS: /* Looking for minutes */
5075 case NEED_SECS: /* Looking for seconds */
5086 case NEED_FRAC: /* Fraction of second */
5087 if (!c && rdigits(p)) {
5096 if (c) /* next field if any */
5102 makestr(&cmdatemsg,"Extraneous material at end");
5103 debug(F111,"cmcvtdate",cmdatemsg,-1);
5110 if ((t != 2 && hh > 24) || hh < 0) { /* Hour range check */
5111 makestr(&cmdatemsg,"Invalid hours");
5112 debug(F111,"cmcvtdate",cmdatemsg,-1);
5115 if (mm > 59) { /* Minute range check */
5116 makestr(&cmdatemsg,"Invalid minutes");
5117 debug(F111,"cmcvtdate",cmdatemsg,-1);
5120 if (ff > 0) { /* Fraction of second? */
5124 } else if (mm < 59) {
5128 } else if (hh < 24) {
5134 /* Must add a day -- leave ff at 1... */
5135 /* (DO SOMETHING ABOUT THIS LATER) */
5137 if (ss > 60) { /* Seconds range check */
5138 makestr(&cmdatemsg,"Invalid seconds"); /* 60 is ok because of */
5139 debug(F111,"cmcvtdate",cmdatemsg,-1); /* Leap Second. */
5142 if ((mm < 0 || ss < 0) ||
5143 (t != 2 && (ss > 0 || mm > 0) && hh > 23)) {
5144 makestr(&cmdatemsg,"Invalid minutes or seconds");
5145 debug(F111,"cmcvtdate",cmdatemsg,-1);
5148 debug(F110,"cmcvtdate year",year,0);
5149 debug(F110,"cmcvtdate month",month,0);
5150 debug(F101,"cmcvtdate nday","",nday);
5151 debug(F101,"cmcvtdate hh","",hh);
5152 debug(F101,"cmcvtdate mm","",mm);
5153 debug(F101,"cmcvtdate ss","",ss);
5154 debug(F101,"cmcvtdate gmtsign","",gmtsign);
5155 debug(F101,"cmcvtdate zhh","",zhh);
5156 debug(F101,"cmcvtdate zmm","",zmm);
5157 debug(F101,"cmcvtdate isgmt","",isgmt);
5160 /* Handle timezone -- first convert to GMT */
5162 zdd = 0; /* Days changed */
5163 if (isgmt && (zmm || zhh)) { /* If GMT offset given */
5164 long sec1, sec2, zz;
5165 sec1 = ss + 60 * mm + 3600 * hh;
5166 sec2 = gmtsign * (60 * zmm + 3600 * zhh);
5170 zdd = 0L - (sec1 / 86400L);
5171 sec1 = sec1 % 86400L;
5172 } else if (sec1 > 86400L) {
5173 zdd = sec1 / 86400L;
5174 sec1 = sec1 % 86400L;
5180 debug(F101,"cmcvtdate NEW hh","",hh);
5181 debug(F101,"cmcvtdate NEW mm","",mm);
5182 debug(F101,"cmcvtdate NEW dd","",zdd);
5184 /* At this point hh:mm:ss is in GMT and zdd is the calendar adjustment */
5187 #endif /* ZLOCALTIME */
5189 if (yyyymmdd[0] && !year) {
5190 ckstrncpy(yearbuf,yyyymmdd,5);
5191 ckstrncpy(monbuf,&yyyymmdd[4],3);
5192 ckstrncpy(daybuf,&yyyymmdd[6],3);
5196 nday = atoi(daybuf);
5198 sprintf(zbuf,"%04d%02d%02d %02d:%02d:%02d", /* SAFE */
5199 atoi(year),atoi(month),nday,hh,mm,ss
5204 /* Now convert from GMT to local time */
5206 if (isgmt) { /* If GMT convert to local time */
5207 debug(F110,"cmcvtdate GMT 1",dp,0);
5208 if (zdd) { /* Apply any calendar adjustment */
5211 sprintf(zbuf,"%s %02d:%02d:%02d",mjd2date(zz),hh,mm,ss);
5213 debug(F110,"cmcvtdate GMT 2",dp,0);
5214 if ((p = zlocaltime(dp))) {
5215 debug(F110,"cmcvtdate asctime zlocaltime",p,0);
5216 if (p) ckstrncpy(zbuf,p,18);
5218 debug(F110,"cmcvtdate GMT 3",dp,0);
5219 for (i = 0; i < 4; i++)
5229 nday = atoi(daybuf);
5236 #endif /* ZLOCALTIME */
5240 debug(F101,"cmcvtdate hour","",hh);
5241 debug(F101,"cmcvtdate minute","",mm);
5242 debug(F101,"cmcvtdate second","",ss);
5246 makestr(&cmdatemsg,NULL);
5250 debug(F110,"cmcvtdate base ",dp,0);
5251 debug(F101,"cmcvtdate delta sign","",dsign);
5252 debug(F101,"cmcvtdate delta yrs ","",dyears);
5253 debug(F101,"cmcvtdate delta mos ","",dmonths);
5254 debug(F101,"cmcvtdate delta days","",ddays);
5255 debug(F101,"cmcvtdate delta hrs ","",dhours);
5256 debug(F101,"cmcvtdate delta mins","",dmins);
5257 debug(F101,"cmcvtdate delta secs","",dsecs);
5260 if (!(dp = cmdelta(atoi(year),
5263 dsign, dyears, dmonths, ddays, dhours, dmins, dsecs))) {
5264 debug(F111,"cmcvtdate",cmdatemsg,-1);
5269 xcvtdate: /* Exit point for success */
5273 debug(F110,"cmcvtdate xcvtdate dp",dp,0);
5274 if (!dp) dp = ""; /* Shouldn't happen */
5275 if (!*dp) return(NULL); /* ... */
5277 debug(F111,"cmcvtdate result",dp,len);
5278 k = cmdatebp - (char *)cmdatebuf; /* Space used */
5279 n = CMDATEBUF - k - 1; /* Space left */
5280 if (n < len) { /* Not enough? */
5281 cmdatebp = cmdatebuf; /* Wrap around */
5284 ckstrncpy(cmdatebp,dp,n);
5286 cmdatebp += len + 1;
5292 cmvdate(d) char * d; { /* Verify date-time */
5295 if ((int)strlen(d) != 17) return(0);
5296 for (i = 0; i < 8; i++) { if (!isdigit(d[i])) return(0); }
5297 if (!isdigit(d[9]) || !isdigit(d[10]) ||
5298 !isdigit(d[12]) || !isdigit(d[13]) ||
5299 !isdigit(d[15]) || !isdigit(d[16]))
5301 if (!ckstrchr(" Tt_-:",d[8])) return(0);
5302 if (d[11] != ':' && d[14] != ':') return(0);
5306 /* c m d i f f d a t e -- Get difference between two date-times */
5309 cmdiffdate(d1,d2) char * d1, * d2; {
5310 char d1buf[9], d2buf[9];
5311 char x1buf[18], x2buf[18];
5314 int hh1 = 0, mm1 = 0, ss1 = 0;
5315 int hh2 = 0, mm2 = 0, ss2 = 0;
5318 long jd1, jd2, jd, f1, f2, fx;
5319 static char result[24], *rp;
5321 debug(F110,"cmdiffdate d1 A",d1,0);
5322 debug(F110,"cmdiffdate d2 A",d2,0);
5324 if (!(p = cmcvtdate(d1,1))) /* Convert dates to standard format */
5326 ckstrncpy(x1buf,p,18);
5329 if (!(p = cmcvtdate(d2,1)))
5331 ckstrncpy(x2buf,p,18);
5334 debug(F110,"cmdiffdate d1 B",d1,0);
5335 debug(F110,"cmdiffdate d2 B",d2,0);
5336 if (!cmvdate(d1) || !cmvdate(d2))
5339 hh1 = atoi(&d1[9]); /* Get hours, minutes, and seconds */
5340 mm1 = atoi(&d1[12]); /* for first date */
5341 ss1 = atoi(&d1[15]);
5342 ckstrncpy(d1buf,d1,9);
5344 hh2 = atoi(&d2[9]); /* ditto for second date */
5345 mm2 = atoi(&d2[12]);
5346 ss2 = atoi(&d2[15]);
5347 ckstrncpy(d2buf,d2,9);
5349 jd1 = mjd(d1buf); /* Get the two Julian dates */
5351 f1 = ss1 + 60 * mm1 + 3600 * hh1; /* Convert first time to seconds */
5353 f2 = ss2 + 60 * mm2 + 3600 * hh2; /* Ditto for second time */
5354 debug(F101,"cmdiffdate jd1","",jd1);
5355 debug(F101,"cmdiffdate f1","",f1);
5356 debug(F101,"cmdiffdate jd2","",jd2);
5357 debug(F101,"cmdiffdate f2","",f2);
5359 if (jd2 > jd1 || (jd1 == jd2 && f2 > f1)) {
5361 if (f1 > f2) {jd2--; f2 += 86400L;}
5366 if (f2 > f1) {jd1--; f1 += 86400L;}
5370 debug(F111,"cmdiffdate sign jd",sign<0?"-":"+",jd);
5371 debug(F101,"cmdiffdate fx","",fx);
5373 hh = (int) (fx / 3600L); /* Convert seconds to hh:mm:ss */
5375 mm = (int) (fx % 3600L) / 60L;
5376 ss = (int) (fx % 3600L) % 60L;
5378 rp = result; /* Format the result */
5379 *rp++ = (sign < 0) ? '-' : '+';
5380 if (jd != 0 && hh+mm+ss == 0) {
5381 sprintf(rp,"%ldd",jd);
5382 } else if (jd == 0) {
5384 sprintf(rp,"%d:%02d",hh,mm);
5386 sprintf(rp,"%d:%02d:%02d",hh,mm,ss);
5389 sprintf(rp,"%ldd%d:%02d",jd,hh,mm);
5391 sprintf(rp,"%ldd%d:%02d:%02d",jd,hh,mm,ss);
5393 debug(F110,"cmdiffdate result",result,0);
5394 return((char *)result);
5398 /* s h u f f l e d a t e -- Rearrange date string */
5402 A date string in standard format: yyyymmdd hh:mm:ss (time optional).
5404 1: Reformat date to yyyy-mmm-dd (mmm = English month abbreviation).
5405 2: Reformat date to dd-mmm-yyyy (mmm = English month abbreviation).
5406 3: Reformat as numeric yyyymmddhhmmss.
5407 4: Reformat in asctime() format Sat Nov 26 11:10:34 2005
5409 Pointer to result if args valid, otherwise original arg pointer.
5412 shuffledate(p,opt) char * p; int opt; {
5413 extern char * wkdays[];
5416 static char obuf[48];
5421 if (!*p) p = ckdate();
5422 if (opt < 1 || opt > 4)
5425 if (len < 8 || len > 31) return(p);
5426 if (opt == 4) { /* Asctime format (26 Nov 2005) */
5429 ckstrncpy(ibuf,p,31);
5431 while (k >= 0 && ibuf[k] == CR || ibuf[k] == LF)
5433 while (k >= 0 && ibuf[k] == SP || ibuf[k] == HT)
5435 if (k < 9) ckstrncpy(&ibuf[8]," 00:00:00",9);
5437 z = mjd(p); /* Convert to modified Julian date */
5441 k = 6 - ((int)z + 3) % 7;
5443 k = ((int)z + 3) % 7; /* Day of week */
5446 obuf[0] = s[0]; /* Day of week */
5449 obuf[3] = SP; /* Space */
5452 mm = atoi(&ibuf[4]); /* Month */
5453 s = moname[mm-1]; /* Name of month */
5456 obuf[4] = s[0]; /* Month */
5459 obuf[7] = SP; /* Space */
5460 if (p[6] == '0') /* Date of month */
5465 ckstrncpy(&obuf[10],&p[8],10); /* Time */
5466 obuf[19] = SP; /* Space */
5467 obuf[20] = p[0]; /* Year */
5472 return((char *)obuf);
5475 ckstrncpy(obuf,p,48);
5476 /* yyyymmdd hh:mm:ss */
5477 /* 01234567890123456 */
5478 /* yyyymmddhhmmss */
5481 obuf[10] = obuf[12];
5482 obuf[11] = obuf[13];
5483 obuf[12] = obuf[15];
5484 obuf[13] = obuf[16];
5486 return((char *)obuf);
5488 ckstrncpy(ibuf,p,32);
5489 c = ibuf[4]; /* Warning: not Y10K compliant */
5494 if (yy < 1 || yy > 9999)
5499 if (!rdigits(&ibuf[4]))
5501 mm = atoi(&ibuf[4]);
5502 if (mm < 1 || mm > 12)
5507 if (!rdigits(&ibuf[6]))
5509 dd = atoi(&ibuf[6]);
5511 if (dd < 1 || mm > 31)
5513 /* IGNORE WARNINGS ABOUT moname[] REFS OUT OF RANGE - it's prechecked. */
5516 sprintf(obuf,"%04d-%s-%02d%s",yy,moname[mm-1],dd,&ibuf[8]);
5519 sprintf(obuf,"%02d-%s-%04d%s",dd,moname[mm-1],yy,&ibuf[8]);
5521 return((char *)obuf);
5525 /* C K C V T D A T E -- Like cmcvtdate(), but returns string. */
5526 /* For use by date-related functions */
5527 /* See calling conventions for cmcvtdate() above. */
5530 ckcvtdate(p,t) char * p; int t; {
5532 if (!(s = cmcvtdate(p,t)))
5533 return("<BAD_DATE_OR_TIME>"); /* \fblah() error message */
5539 /* C M D A T E -- Parse a date and/or time */
5542 Accepts date in various formats. If the date is recognized,
5543 this routine returns 0 or greater with the result string pointer
5544 pointing to a buffer containing the date as "yyyymmdd hh:mm:ss".
5547 cmdate(xhlp,xdef,xp,quiet,f) char *xhlp, *xdef, **xp; int quiet; xx_strp f; {
5549 char *o, *s, *zq, *dp;
5552 if (!xhlp) xhlp = "";
5553 if (!xdef) xdef = "";
5554 if (!*xhlp) xhlp = "Date and/or time";
5557 rc = cmfld(xhlp,xdef,&s,(xx_strp)0);
5558 debug(F101,"cmdate cmfld rc","",rc);
5561 debug(F110,"cmdate 1",s,0);
5562 o = s; /* Remember what they typed. */
5564 debug(F110,"cmdate 2",s,0);
5567 if (f) { /* If a conversion function is given */
5569 zq = atxbuf; /* do the conversion. */
5572 if ((x = (*f)(s,&zq,&atxn)) < 0) return(-2);
5575 if (setatm(pp,0) < 0) {
5576 if (!quiet) printf("?Evaluated date too long\n");
5581 dp = cmcvtdate(s,1);
5583 if (!quiet) printf("?%s\n",cmdatemsg);
5590 #ifdef CK_RECALL /* Command-recall functions */
5592 /* C M R I N I -- Initialize or change size of command recall buffer */
5597 if (recall && in_recall) { /* Free old storage, if any */
5598 for (i = 0; i < cm_recall; i++) {
5607 cm_recall = n; /* Set new size */
5608 rlast = current = -1; /* Initialize pointers */
5610 recall = (char **)malloc((cm_recall + 1) * sizeof(char *));
5613 for (i = 0; i < cm_recall; i++) {
5616 in_recall = 1; /* Recall buffers init'd */
5621 /* C M A D D N E X T -- Force addition of next command */
5625 if (on_recall && in_recall) { /* Even if it doesn't come */
5626 force_add = 1; /* from the keyboard */
5632 /* C M G E T C M D -- Find most recent matching command */
5635 cmgetcmd(s) char * s; {
5637 for (i = current; i >= 0; i--) { /* Search backward thru history list */
5638 if (!recall[i]) continue; /* This one's null, skip it */
5639 if (ckmatch(s,recall[i],0,1)) /* Match? */
5640 return(recall[i]); /* Yes, return pointer */
5642 return(NULL); /* No match, return NULL pointer */
5644 #endif /* CK_RECALL */
5646 /* A D D C M D -- Add a command to the recall buffer */
5649 addcmd(s) char * s; {
5650 int len = 0, nq = 0;
5653 extern int learning;
5654 #endif /* CKLEARN */
5656 if (xcmdsrc) /* Only for interactive commands */
5659 if (!newcmd) /* The command has been here already */
5660 return; /* so ignore it. */
5661 newcmd = 0; /* It's new but do this only once. */
5667 if (len < 1) /* Don't save empty commands */
5671 while (*p) { if (*p++ == '?') nq++; } /* Count question marks */
5674 if (learning) /* If a learned script is active */
5675 learncmd(s); /* record this command. */
5676 #endif /* CKLEARN */
5678 debug(F010,"CMD(P)",s,0); /* Maybe record it in the debug log */
5681 if (ckxlogging) { /* Maybe record it in syslog */
5682 if (ckxsyslog >= SYSLG_CX || ckxsyslog >= SYSLG_CM)
5683 cksyslog(SYSLG_CX, 1, "command", s, NULL);
5685 #endif /* CKSYSLOG */
5690 if (on_recall && /* Command recall is on? */
5691 cm_recall > 0 && /* Recall buffer size is > 0? */
5692 !no_recall) { /* Not not saving this command? */
5694 if (!force_add && rlast > -1) /* If previous command was identical */
5695 if (!strcmp(s,recall[rlast])) /* don't add another copy */
5698 force_add = 0; /* Reset now in case it was set */
5700 if (rlast >= cm_recall - 1) { /* Recall buffer full? */
5702 if (recall[0]) { /* Discard oldest command */
5706 for (i = 0; i < rlast; i++) { /* The rest */
5707 recall[i] = recall[i+1]; /* move back */
5709 rlast--; /* Now we have one less */
5711 rlast++; /* Index of last command in buffer */
5712 current = rlast; /* Also now the current command */
5713 if (current >= cm_recall) { /* Shouldn't happen */
5714 printf("?Command history error\n"); /* but if it does */
5715 on_recall = 0; /* turn off command saving */
5717 } else if (nq > 0) { /* Have at least one question mark */
5718 recall[current] = malloc(len+nq+1);
5719 if (recall[current]) {
5720 p = recall[current];
5728 #endif /* COMMENT */
5729 } else { /* Normal case, just copy */
5730 recall[current] = malloc(len+1);
5731 if (recall[current])
5732 ckstrncpy(recall[current],s,len+1);
5735 #endif /* CK_RECALL */
5741 /* C M H I S T O R Y */
5746 for (i = 0; i <= current; i++) {
5747 printf(" %s\n", recall[i]);
5748 if (++lc > (cmd_rows - 2)) { /* Screen full? */
5749 if (!askmore()) /* Do more-prompting... */
5758 savhistory(s,disp) char *s; int disp; {
5762 fp = fopen(s, disp ? "a" : "w");
5767 for (i = 0; i <= current; i++)
5768 fprintf(fp,"%s\n", recall[i]);
5772 #endif /* CK_RECALL */
5775 /* apparently not used */
5777 cmgetlc(s) char * s; { /* Get leading char */
5779 while ((c = *s++) <= SP) {
5785 #endif /* COMMENT */
5788 /* C M C F M -- Parse command confirmation (end of line) */
5792 -2: User typed anything but whitespace or newline
5794 0: Confirmation was received
5799 debug(F101,"cmcfm: cmflgs","",cmflgs);
5800 debug(F110,"cmcfm: atmbuf",atmbuf,0);
5801 inword = xc = cc = 0;
5803 setatm("",0); /* (Probably unnecessary) */
5805 while (cmflgs != 1) {
5811 printf("Command or field too long\n");
5816 case 1: /* End of line */
5821 printf("?Not confirmed - %s\n",atmbuf);
5825 break; /* Finish up below */
5829 continue; /* or fall thru. */
5832 if (xc == 0) /* If no chars typed, continue, */
5833 continue; /* else fall thru. */
5834 /* else fall thru... */
5836 case 3: /* Question mark */
5841 printf("?Not confirmed - %s\n",atmbuf);
5846 "\n Press the Return or Enter key to confirm the command\n");
5847 printf("%s%s",cmprom,cmdbuf);
5857 /* The following material supports chained parsing functions. */
5858 /* See ckucmd.h for FDB and OFDB definitions. */
5860 struct OFDB cmresult = { /* Universal cmfdb result holder */
5861 NULL, /* Address of succeeding FDB struct */
5862 0, /* Function code */
5863 NULL, /* String result */
5864 0, /* Integer result */
5865 (CK_OFF_T)0 /* Wide result */
5869 cmfdbi(p,fc,s1,s2,s3,n1,n2,f,k,nxt) /* Initialize an FDB */
5872 char * s1, * s2, * s3;
5889 /* C M F D B -- Parse a field with several possible functions */
5892 cmfdb(fdbin) struct FDB * fdbin; {
5894 extern int x_ifnum; /* IF NUMERIC - disables warnings */
5896 struct FDB * in = fdbin;
5897 struct OFDB * out = &cmresult;
5899 CK_OFF_T w = (CK_OFF_T)0;
5900 char *s, *xp, *m = NULL;
5905 out->fcode = -1; /* Initialize output struct */
5906 out->fdbaddr = NULL;
5907 out->sresult = NULL;
5910 Currently we make one trip through the FDBs. So if the user types Esc or
5911 Tab at the beginning of a field, only the first FDB is examined for a
5912 default. If the user types ?, help is given only for one FDB. We should
5913 search through the FDBs for all matching possibilities -- and in particular
5914 display the pertinent context-sensitive help for each function, rather than
5915 the only the first one that works, and then rewind the FDB pointer so we
5916 are not locked out of the earlier ones.
5919 while (1) { /* Loop through the chain of FDBs */
5924 debug(F101,"cmfdb in->fcode","",in->fcode);
5925 switch (in->fcode) { /* Current parsing function code */
5928 if (r != 10 && r != 8) r = 10;
5930 x_ifnum = 1; /* Disables warning messages */
5932 x = cmnum(in->hlpmsg,in->dflt,r,&n,in->spf);
5936 debug(F101,"cmfdb cmnum","",x);
5937 if (x < 0) errbits |= 1;
5939 case _CMNUW: /* Wide cmnum - 24 Dec 2005 */
5941 if (r != 10 && r != 8) r = 10;
5943 x_ifnum = 1; /* Disables warning messages */
5945 x = cmnumw(in->hlpmsg,in->dflt,r,&w,in->spf);
5949 debug(F101,"cmfdb cmnumw","",w);
5950 if (x < 0) errbits |= 1;
5953 x = cmofi(in->hlpmsg,in->dflt,&s,in->spf);
5954 debug(F101,"cmfdb cmofi","",x);
5955 if (x < 0) errbits |= 2;
5958 x = cmifi2(in->hlpmsg,
5967 debug(F101,"cmfdb cmifi2 x","",x);
5968 debug(F101,"cmfdb cmifi2 n","",n);
5969 if (x < 0) errbits |= 4;
5972 cmfldflgs = in->ndata1;
5973 x = cmfld(in->hlpmsg,in->dflt,&s,in->spf);
5974 debug(F101,"cmfdb cmfld","",x);
5975 if (x < 0) errbits |= 8;
5978 x = cmtxt(in->hlpmsg,in->dflt,&s,in->spf);
5979 debug(F101,"cmfdb cmtxt","",x);
5980 if (x < 0) errbits |= 16;
5983 x = cmkey2(in->kwdtbl,
5985 in->hlpmsg,in->dflt,in->sdata,in->spf,in->ndata2);
5986 debug(F101,"cmfdb cmkey","",x);
5987 if (x < 0) errbits |= ((in->ndata2 & 4) ? 32 : 64);
5991 debug(F101,"cmfdb cmcfm","",x);
5992 if (x < 0) errbits |= 128;
5995 debug(F101,"cmfdb - unexpected function code","",in->fcode);
5996 printf("?cmfdb - unexpected function code: %d\n",in->fcode);
5998 debug(F101,"cmfdb x","",x);
5999 debug(F101,"cmfdb cmflgs","",cmflgs);
6000 debug(F101,"cmfdb crflag","",crflag);
6001 debug(F101,"cmfdb qmflag","",qmflag);
6002 debug(F101,"cmfdb esflag","",esflag);
6004 if (x > -1) { /* Success */
6005 out->fcode = in->fcode; /* Fill in output struct */
6008 out->nresult = (in->fcode == _CMKEY) ? x : n;
6010 out->kflags = (in->fcode == _CMKEY) ? cmkwflgs : 0;
6011 debug(F111,"cmfdb out->nresult",out->sresult,out->nresult);
6012 debug(F111,"cmfdb out->wresult",out->sresult,out->wresult);
6015 /* debug(F111,"cmfdb cmdbuf & crflag",cmdbuf,crflag); */
6019 return(x); /* and return */
6021 in = in->nxtfdb; /* Failed, get next parsing function */
6024 if (!in) { /* No more */
6025 debug(F101,"cmfdb failure x","",x);
6026 debug(F101,"cmfdb failure errbits","",errbits);
6035 /* Make informative messages for a few common cases */
6037 case 4+32: m = "Does not match filename or switch"; break;
6038 case 4+64: m = "Does not match filename or keyword"; break;
6039 case 1+32: m = "Not a number or valid keyword"; break;
6040 case 1+64: m = "Not a number or valid switch"; break;
6041 default: m = "Not valid in this position";
6043 printf("?%s: \"%s\"\n",m, atmbuf);
6047 if (x != -2 && x != -6 && x != -9 && x != -3) /* Editing or somesuch */
6048 return(x); /* Go back and reparse */
6049 pp = np = bp = xp; /* Back up pointers */
6050 cmflgs = -1; /* Force a reparse */
6053 if (!askflag) { /* If not executing ASK-class cmd... */
6055 if (crflag) { /* If CR was typed, put it back */
6056 pushc = LF; /* But as a linefeed */
6057 } else if (qmflag) { /* Ditto for Question mark */
6059 } else if (esflag) { /* and Escape or Tab */
6069 C M I O F I -- Parse an input file OR the name of a nonexistent file.
6071 Replaces the commented-out version above. This one actually works and
6072 has the expected straightforward interface.
6075 cmiofi(xhlp,xdef,xp,wild,f) char *xhlp, *xdef, **xp; int *wild; xx_strp f; {
6078 cmfdbi(&f1,_CMIFI,xhlp,xdef,"",0,0,f,NULL,&f2);
6079 cmfdbi(&f2,_CMOFI,"","","",0,0,f,NULL,NULL);
6084 printf("?Filename required\n");
6087 *wild = cmresult.nresult;
6088 *xp = cmresult.sresult;
6092 /* G T W O R D -- Gets a "word" from the command input stream */
6095 Usage: retcode = gtword(brk);
6096 brk = 0 for normal word breaks (space, CR, Esc, ?)
6097 brk = 1 to add ':' and '=' (for parsing switches). These characters
6098 act as break characters only if the first character of the field
6099 is slash ('/'), i.e. switch introducer.
6100 brk = 4 to not strip comments (used only for "help #" and "help ;").
6103 -10 Timelimit set and timed out
6104 -9 if input was too long
6105 -4 if end of file (e.g. pipe broken)
6107 -2 if command buffer overflows
6108 -1 if user did some deleting
6109 0 if word terminates with SP or tab
6112 3 if ... ? (question mark)
6113 4 if ... : or = and called with brk != 0
6116 pp pointing to beginning of word in buffer
6117 bp pointing to after current position
6118 atmbuf containing a copy of the word
6119 cc containing the number of characters in the word copied to atmbuf
6123 ungword() { /* Unget a word */
6124 debug(F101,"ungword cmflgs","",cmflgs);
6125 if (ungw) return(0);
6132 /* Un-un-get word. Undo ungword() if it has been done. */
6136 debug(F010,"unungw atmbuf",atmbuf,0);
6145 gtword(brk) int brk; {
6146 int c; /* Current char */
6147 int quote = 0; /* Flag for quote character */
6148 int echof = 0; /* Flag for whether to echo */
6149 int comment = 0; /* Flag for in comment */
6150 char *cp = NULL; /* Comment pointer */
6151 int eintr = 0; /* Flag for syscall interrupted */
6152 int bracelvl = 0; /* nested brace counter [jrs] */
6153 int iscontd = 0; /* Flag for continuation */
6154 int realtty = 0; /* Stdin is really a tty */
6156 char lastchar = NUL;
6157 char prevchar = NUL;
6158 char lbrace, rbrace;
6159 int dq = 0; /* Doublequote flag */
6160 int dqn = 0; /* and count */
6168 extern int inserver;
6170 extern int kstartactive;
6173 extern int termtype; /* DG terminal type flag */
6174 extern int con_reads_mt; /* Console read asynch is active */
6175 if (con_reads_mt) connoi_mt(); /* Task would interfere w/cons read */
6176 #endif /* datageneral */
6181 debug(F101,"gtword brk","",brk);
6182 debug(F101,"gtword cmfldflgs","",cmfldflgs);
6183 debug(F101,"gtword swarg","",swarg);
6184 debug(F101,"gtword dpx","",dpx);
6185 debug(F101,"gtword echof","",echof);
6187 debug(F101,"gtword askflag","",askflag);
6188 debug(F101,"gtword timelimit","",timelimit);
6192 debug(F101,"gtword cmdadl","",cmdadl);
6193 #endif /* CK_AUTODL */
6195 #endif /* NOLOCAL */
6199 #endif /* COMMENT */
6201 realtty = is_a_tty(0); /* Stdin is really a tty? */
6203 if (cmfldflgs & 1) {
6214 if (swarg) { /* No leading space for switch args */
6218 if (ungw) { /* Have a word saved? */
6220 /* Experimental code to allow ungetting multiple words. */
6221 /* See comments in ckmkey2() above. */
6223 if (np > pp) pp = np;
6224 while (*pp == SP) pp++;
6229 if ((x = setatm(pp,2)) < 0) {
6230 printf("?Saved word too long\n");
6237 while (*p2 == SP) p2++;
6245 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6251 You would think the following should be:
6252 while (*pp == SP) pp++;
6253 but you would be wrong -- making this change breaks GOTO.
6255 while (*pp++ == SP) ;
6256 if (setatm(pp,2) < 0) {
6257 printf("?Saved word too long\n");
6262 debug(F010,"gtword ungw return atmbuf",atmbuf,0);
6266 pp = np; /* Start of current field */
6271 debug(F110,"gtword cmdbuf",cmdbuf,0);
6272 debug(F110,"gtword bp",bp,0);
6273 debug(F110,"gtword pp",pp,0);
6276 #endif /* COMMENT */
6278 /* If we are reparsing we have to recount any braces or doublequotes */
6286 else if (c == rbrace)
6288 else if (dq && c == '"')
6291 while (bp < cmdbuf+CMDBL) { /* Big get-a-character loop */
6292 echof = 0; /* Assume we don't echo because */
6293 chsrc = 0; /* character came from reparse buf. */
6296 #endif /* BS_DIRSEP */
6299 if (!c) { /* If no char waiting in reparse buf */
6308 )) /* Get from tty, set echo flag */
6310 c = cmdgetc(timelimit); /* Read a command character. */
6312 debug(F101,"gtword c","",c);
6315 if (timelimit && c < -1) { /* Timed out */
6321 The following allows packet recognition in the command parser.
6322 Presently it works only for Kermit packets, and if our current protocol
6323 happens to be anything besides Kermit, we simply force it to Kermit.
6324 We don't use the APC mechanism here for mechanical reasons, and also
6325 because this way, it works even with minimally configured interactive
6326 versions. Add Zmodem later...
6329 if ((!local && cmdadl) /* Autodownload enabled? */
6331 || TELOPT_SB(TELOPT_KERMIT).kermit.me_start
6332 #endif /* IKS_OPTION */
6335 k = kstart((CHAR)c); /* Kermit S or I packet? */
6338 if (k < 0) { /* Minus-Protocol? */
6340 goto noserver; /* Need server mode for this */
6342 ksign = 1; /* Remember */
6343 k = 0 - k; /* Convert to actual protocol */
6344 justone = 1; /* Flag for protocol module */
6345 #endif /* NOSERVER */
6348 k--; /* Adjust kstart's return value */
6350 extern int protocol, g_proto;
6353 protocol = PROTO_K; /* Crude... */
6354 sstate = ksign ? 'x' : 'v';
6362 #endif /* NOSERVER */
6363 #endif /* CK_AUTODL */
6366 chsrc = 1; /* Remember character source is tty. */
6370 if (inserver && c < 0) { /* End of session? */
6371 debug(F111,"gtword c < 0","exiting",c);
6372 return(-4); /* Cleanup and terminate */
6377 if (c < 0) { /* Error */
6378 if (c == -3) { /* Empty word? */
6379 if (blocklvl > 0) /* In a block */
6380 continue; /* so keep looking for block end */
6382 return(-3); /* Otherwise say we got nothing */
6383 } else { /* Not empty word */
6384 return(-4); /* So some kind of i/o error */
6389 if (c == -3) /* Empty word... */
6396 if (c == EOF) { /* This can happen if stdin not tty. */
6399 Some operating and/or C runtime systems return EINTR for no good reason,
6400 when the end of the standard input "file" is encountered. In cases like
6401 this, we get into an infinite loop; hence the eintr counter, which is reset
6402 to 0 upon each call to this routine.
6404 debug(F101,"gtword EOF","",errno);
6405 if (errno == EINTR && ++eintr < 4) /* When bg'd process is */
6406 continue; /* fg'd again. */
6410 c &= cmdmsk; /* Strip any parity bit */
6413 /* Now we have the next character */
6415 isesc = (c == ESC); /* A real ESC? */
6417 if (!firstnb && c > SP) { /* First nonblank */
6419 if (c == '"') /* Starts with doublequote */
6422 if (c == '"') /* Count doublequotes */
6425 if (quote && (c == CR || c == LF)) { /* Enter key following quote */
6426 *bp++ = CMDQ; /* Double it */
6430 if (quote == 0) { /* If this is not a quoted character */
6432 case CMDQ: /* Got the quote character itself */
6433 if (!comment && quoting)
6434 quote = 1; /* Flag it if not in a comment */
6436 case FF: /* Formfeed. */
6437 c = NL; /* Replace with newline */
6438 cmdclrscn(); /* Clear the screen */
6440 case HT: /* Horizontal Tab */
6441 if (comment) /* If in comment, */
6442 c = SP; /* substitute space */
6443 else /* otherwise */
6444 c = ESC; /* substitute ESC (for completion) */
6446 case ';': /* Trailing comment */
6448 if (! (brk & 4) ) { /* If not keeping comments */
6449 if (inword == 0 && quoting) { /* If not in a word */
6450 comment = 1; /* start a comment. */
6451 cp = bp; /* remember where it starts. */
6456 if (!kstartactive && /* Not in possible Kermit packet */
6457 !comment && c == SP) { /* Space not in comment */
6458 *bp++ = (char) c; /* deposit in buffer if not already */
6459 /* debug(F101,"gtword echof 2","",echof); */
6462 cmdecho((char) c, 0); /* Echo what was typed. */
6468 cmdecho((char) c, 0); /* Echo what was typed. */
6473 if (inword == 0) { /* If leading, gobble it. */
6476 } else { /* If terminating, return. */
6477 if ((!dq && ((*pp != lbrace) || (bracelvl == 0))) ||
6478 (dq && dqn > 1 && *(bp-2) == '"')) {
6481 if (setatm(pp,0) < 0) {
6482 printf("?Field too long error 1\n");
6483 debug(F111,"gtword too long #1",pp,strlen(pp));
6487 inword = cmflgs = 0;
6495 /* debug(F101,"gtword bracelvl++","",bracelvl); */
6497 if (c == rbrace && bracelvl > 0) {
6499 /* debug(F101,"gtword bracelvl--","",bracelvl); */
6503 if ((c == '=' || c == ':') &&
6505 !kstartactive && !comment && brk /* && (firstnb == '/') */
6507 *bp++ = (char) c; /* Switch argument separator */
6508 /* debug(F111,"gtword switch argsep",cmdbuf,brk); */
6511 cmdecho((char) c, 0); /* Echo what was typed. */
6517 cmdecho((char) c, 0); /* Echo what was typed. */
6522 if ((*pp != lbrace) || (bracelvl == 0)) {
6525 if (setatm(pp,2) < 0) { /* ^^^ */
6526 printf("?Field too long error 1\n");
6527 debug(F111,"gtword too long #1",pp,strlen(pp));
6530 inword = cmflgs = 0;
6535 if (c == LF || c == CR) { /* CR or LF. */
6537 cmdnewl((char)c); /* echo it. */
6544 /* Trim trailing comment and whitespace */
6546 if (comment) { /* Erase comment */
6547 while (bp >= cp) /* Back to comment pointer */
6550 pp = bp; /* Adjust other pointers */
6551 inword = 0; /* and flags */
6555 qq = inword ? pp : (char *)cmdbuf;
6556 /* Erase trailing whitespace */
6557 while (bp > qq && (*(bp-1) == SP || *(bp-1) == HT)) {
6559 /* debug(F000,"erasing","",*bp); */
6562 lastchar = (bp > qq) ? *(bp-1) : NUL;
6563 prevchar = (bp > qq+1) ? *(bp-2) : NUL;
6565 if (linebegin && blocklvl > 0) /* Blank line in {...} block */
6568 linebegin = 1; /* At beginning of next line */
6569 iscontd = prevchar != CMDQ &&
6570 (lastchar == '-' || lastchar == lbrace);
6571 debug(F101,"gtword iscontd","",iscontd);
6573 if (iscontd) { /* If line is continued... */
6574 if (chsrc) { /* If reading from tty, */
6575 if (*(bp-1) == lbrace) { /* Check for "begin block" */
6576 *bp++ = SP; /* Insert a space for neatness */
6577 blocklvl++; /* Count block nesting level */
6578 } else { /* Or hyphen */
6579 bp--; /* Overwrite the hyphen */
6581 *bp = NUL; /* erase the dash, */
6582 continue; /* and go back for next char now. */
6584 } else if (blocklvl > 0) { /* No continuation character */
6585 if (chsrc) { /* But we're in a "block" */
6586 *bp++ = ','; /* Add comma */
6590 } else { /* No continuation, end of command. */
6591 *bp = NUL; /* Terminate the command string. */
6592 if (comment) { /* If we're in a comment, */
6593 comment = 0; /* Say we're not any more, */
6594 *cp = NUL; /* cut it off. */
6596 np = bp; /* Where to start next field. */
6598 if (setatm(pp,0) < 0) { /* Copy field to atom buffer */
6599 debug(F111,"gtword too long #2",pp,strlen(pp));
6600 printf("?Field too long error 2\n");
6603 inword = 0; /* Not in a word any more. */
6605 /* debug(F110,"gtword","crflag is set",0); */
6608 #endif /* CK_RECALL */
6613 #endif /* CK_RECALL */
6620 This section handles interactive help, completion, editing, and history.
6621 Rearranged as a switch statement executed only if we're at top level since
6622 there is no need for any of this within command files and macros: Aug 2000.
6623 Jun 2001: Even if at top level, skip this if the character was fetched from
6624 the reparse or recall buffer, or if stdin is redirected.
6626 if ((xcmdsrc == 0 /* Only at top level */
6628 || askflag /* or user is typing ASK response */
6630 ) && chsrc != 0 && realtty) { /* from the real keyboard */
6632 /* Use ANSI / VT100 up and down arrow keys for command recall. */
6640 #ifdef USE_ARROWKEYS
6642 #endif /* USE_ARROWKEYS */
6644 ) { /* A real ESC was typed */
6646 msleep(200); /* Wait 1/5 sec */
6647 x = cmdconchk(); /* Was it followed by anything? */
6648 debug(F101,"Arrowkey ESC cmdconchk","",x);
6650 if (x > 1) { /* If followed by at least 2 chars */
6652 c2 = cmdgetc(0); /* Get the first one */
6653 debug(F101,"Arrowkey ESC c2","",c2);
6655 if (c2 != '[' && c2 != 'O') { /* If not [ or O */
6656 pushc = c2; /* Push it and take the ESC solo */
6658 c2 = cmdgetc(0); /* Get the second one */
6659 debug(F101,"Arrowkey ESC c3","",c2);
6666 case 'B': /* Down */
6670 case 'C': /* Right */
6671 case 'D': /* Left */
6674 #endif /* NORECALL */
6675 c = BEL; /* We don't use these yet */
6683 case '?': /* ?-Help */
6685 if (askflag) /* No help in ASK response */
6692 cmdecho((char) c, 0);
6694 if (setatm(pp,0) < 0) {
6695 debug(F111,"gtword too long ?",pp,strlen(pp));
6696 printf("?Too long\n");
6703 case ESC: /* Esc or Tab completion */
6706 if (setatm(pp,0) < 0) {
6707 debug(F111,"gtword too long Esc",pp,strlen(pp));
6708 printf("?Too long\n");
6718 case BS: /* Character deletion */
6720 if (bp > cmdbuf) { /* If still in buffer... */
6721 cmdchardel(); /* erase it. */
6722 bp--; /* point behind it, */
6723 if (*bp == lbrace) bracelvl--; /* Adjust brace count */
6724 if (*bp == rbrace) bracelvl++;
6725 if ((*bp == SP) && /* Flag if current field gone */
6726 (*pp != lbrace || bracelvl == 0))
6728 *bp = NUL; /* Erase character from buffer. */
6729 } else { /* Otherwise, */
6731 cmres(); /* and start parsing a new command. */
6732 *bp = *atmbuf = NUL;
6737 return(cmflgs = -1);
6739 case LDEL: /* ^U, line deletion */
6740 while ((bp--) > cmdbuf) {
6744 cmres(); /* Restart the command. */
6745 *bp = *atmbuf = NUL;
6747 return(cmflgs = -1);
6749 case WDEL: /* ^W, word deletion */
6750 if (bp <= cmdbuf) { /* Beep if nothing to delete */
6753 *bp = *atmbuf = NUL;
6754 return(cmflgs = -1);
6757 /* Back up over any trailing nonalphanums */
6758 /* This is dependent on ASCII collating sequence */
6759 /* but isalphanum() is not available everywhere. */
6763 ((*bp > '9') && (*bp < '@')) ||
6764 ((*bp > 'Z') && (*bp < 'a')) ||
6771 /* Now delete back to rightmost remaining nonalphanum */
6772 for ( ; (bp >= cmdbuf) && (*bp) ; bp--) {
6774 (*bp > '9' && *bp < '@') ||
6775 (*bp > 'Z' && *bp < 'a') ||
6783 return(cmflgs = -1);
6785 case RDIS: { /* ^R, redisplay */
6788 printf("\n%s",cmprom);
6790 while ((cx = *cpx++)) {
6799 printf("%s ",lastfile);
6803 inword = cmflgs = 0;
6804 addbuf(lastfile); /* Supply default. */
6805 if (setatm(lastfile,0) < 0) {
6806 printf("Last name too long\n");
6810 } else { /* No default */
6814 #endif /* NOLASTFILE */
6818 if (on_recall && /* Reading commands from keyboard? */
6819 (cm_recall > 0) && /* Saving commands? */
6820 (c == C_UP || c == C_UP2)) { /* Go up one */
6821 if (last_recall == 2 && current > 0)
6823 if (current < 0) { /* Nowhere to go, */
6827 if (recall[current]) { /* We have a previous command */
6828 while ((bp--) > cmdbuf) { /* Erase current line */
6832 ckstrncpy(cmdbuf,recall[current],CMDBL);
6835 write(fileno(stdout), "\r", 1);
6836 printf("%s%s",cmprom,cmdbuf);
6838 printf("\r%s%s",cmprom,cmdbuf);
6843 return(cmflgs = -1); /* Force a reparse */
6845 if (on_recall && /* Reading commands from keyboard? */
6846 (cm_recall > 0) && /* Saving commands? */
6847 (c == C_DN)) { /* Down one */
6849 if (last_recall == 1)
6851 if (current + x > rlast) { /* Already at bottom, beep */
6855 current += x; /* OK to go down */
6856 if (recall[current]) {
6857 while ((bp--) > cmdbuf) { /* Erase current line */
6861 ckstrncpy(cmdbuf,recall[current],CMDBL);
6864 write(fileno(stdout), "\r", 1);
6865 printf("%s%s",cmprom,cmdbuf);
6867 printf("\r%s%s",cmprom,cmdbuf);
6870 return(cmflgs = -1); /* Force reparse */
6873 #endif /* CK_RECALL */
6876 if (c < SP && quote == 0) { /* Any other unquoted control char */
6877 if (!chsrc) { /* If cmd file, point past it */
6882 continue; /* continue, don't put in buffer */
6884 linebegin = 0; /* Not at beginning of line */
6887 cmdecho((char) c, 0); /* Echo what was typed. */
6895 if (echof || (echostars && chsrc))
6897 cmdecho((char) c, 0); /* Echo what was typed. */
6899 } else { /* This character was quoted. */
6901 quote = 0; /* Unset the quote flag. */
6902 /* debug(F000,"gtword quote 0","",c); */
6903 /* Quote character at this level is only for SP, ?, and controls */
6904 /* If anything else was quoted, leave quote in, and let */
6905 /* the command-specific parsing routines handle it, e.g. \007 */
6906 if (c > 32 && c != '?' && c != RUB && chsrc != 0) {
6907 /* debug(F000,"gtword quote 1","",c); */
6908 *bp++ = CMDQ; /* Deposit \ if it came from tty */
6909 qf = 0; /* and don't erase it from screen */
6910 linebegin = 0; /* Not at beginning of line */
6913 This is a hack to handle "cd \" or "cd foo\" on OS/2 and similar systems.
6914 If we were called from cmdir() and the previous character was the quote
6915 character, i.e. backslash, and this character is the command terminator,
6916 then we stuff an extra backslash into the buffer without echoing, then
6917 we stuff the carriage return back in again, and go back and process it,
6918 this time with the quote flag off.
6920 } else if (dirnamflg && (c == CR || c == LF || c == SP)) {
6921 /* debug(F000,"gtword quote 2","",c); */
6923 linebegin = 0; /* Not at beginning of line */
6924 *bp = (c == SP ? SP : CR);
6926 #endif /* BS_DIRSEP */
6930 cmdecho((char) c, qf); /* Echo what was typed. */
6935 if (echof) cmdecho((char) c, qf); /* Now echo quoted character */
6937 /* debug(F111,"gtword quote",cmdbuf,c); */
6940 if (echof) cmdecho((char) c,quote); /* Echo what was typed. */
6941 #endif /* COMMENT */
6942 if (!comment) inword = 1; /* Flag we're in a word. */
6943 if (quote) continue; /* Don't deposit quote character. */
6944 if (c != NL) { /* Deposit command character. */
6945 *bp++ = (char) c; /* and make sure there is a NUL */
6947 *bp = NUL; /* after it */
6948 #endif /* COMMENT */
6950 } /* End of big while */
6952 printf("?Command too long, maximum length: %d.\n",CMDBL);
6957 /* Utility functions */
6959 /* A D D B U F -- Add the string pointed to by cp to the command buffer */
6962 addbuf(cp) char *cp; {
6964 while ((*cp != NUL) && (bp < cmdbuf+CMDBL)) {
6965 *bp++ = *cp++; /* Copy and */
6966 len++; /* count the characters. */
6968 *bp++ = SP; /* Put a space at the end */
6969 *bp = NUL; /* Terminate with a null */
6970 np = bp; /* Update the next-field pointer */
6972 return(len); /* Return the length */
6975 /* S E T A T M -- Deposit a token in the atom buffer. */
6977 Break on space, newline, carriage return, or NUL.
6979 cp = Pointer to string to copy to atom buffer.
6980 fcode = 0 means break on whitespace or EOL.
6981 fcode = 1 means don't break on space.
6982 fcode = 2 means break on space, ':', or '='.
6983 fcode = 3 means copy the whole string.
6984 Null-terminate the result.
6985 Return length of token, and also set global "cc" to this length.
6986 Return -1 if token was too long.
6989 setatm(cp,fcode) char *cp; int fcode; {
6990 char *ap, *xp, *dqp = NULL, lbrace, rbrace;
6991 int bracelvl = 0, dq = 0;
6996 if (cmfldflgs & 1) { /* Handle grouping */
7003 cc = 0; /* Character counter */
7004 ap = atmbuf; /* Address of atom buffer */
7008 while (*s++) n++; /* Save a call to strlen */
7011 printf("?Command buffer overflow\n");
7014 /* debug(F111,"setatm",cp,n); */
7015 if (cp == ap) { /* In case source is atom buffer */
7016 xp = atybuf; /* make a copy */
7018 strncpy(xp,ap,ATMBL); /* so we can copy it back, edited. */
7022 while ((*xp++ = *s++)) ; /* We already know it's big enough */
7024 #endif /* COMMENT */
7026 *ap = NUL; /* Zero the atom buffer */
7027 if (fcode == 1) { /* Trim trailing blanks */
7028 while (--n >= 0 && cp[n] == SP)
7032 while (*cp == SP) { /* Trim leading spaces */
7036 if (*cp == '"') { /* Starts with doublequote? */
7043 else if (*cp == rbrace)
7047 if (bracelvl == 0) {
7049 if (*cp == SP || *cp == HT) {
7051 if (*(cp-1) == '"' && *(cp-2) != CMDQ) {
7056 } else if ((*cp == SP || *cp == HT) && fcode != 1 && fcode != 3)
7058 if ((fcode == 2) && (*cp == '=' || *cp == ':')) break;
7059 if ((fcode != 3) && (*cp == LF || *cp == CR)) break;
7064 *ap = NUL; /* Terminate the string. */
7065 /* debug(F111,"setatm result",atmbuf,cc); */
7066 return(cc); /* Return length. */
7070 These functions attempt to hide system dependencies from the mainline
7071 code in gtword(). Dummy arg for cmdgetc() needed for compatibility with
7072 coninc(), ttinc(), etc, since a pointer to this routine can be passed in
7073 place of those to tn_doop().
7075 No longer static. Used by askmore(). Fri Aug 20 15:03:34 1999.
7077 #define CMD_CONINC /* How we get keyboard chars */
7080 cmdgetc(timelimit) int timelimit; { /* Get a character from the tty. */
7083 extern int inserver;
7086 extern int x_logged;
7087 #endif /* CK_LOGIN */
7089 static int got_cr = 0;
7091 int tx = 0, is_tn = 0;
7099 debug(F111,"cmdgetc()","pushc",pushc);
7102 if (xcmfdb && c == '?') /* Don't echo ? twice if chaining. */
7109 c = dgncinb(0,&ch,1); /* -1 is EOF, -2 TO,
7110 * -c is AOS/VS error */
7111 if (c == -2) { /* timeout was enabled? */
7112 resto(channel(0)); /* reset timeouts */
7113 c = dgncinb(0,&ch,1); /* retry this now! */
7115 if (c < 0) return(-4); /* EOF or some error */
7116 else c = (int) ch & 0177; /* Get char without parity */
7119 #else /* Not datageneral */
7123 (!local && inserver) ||
7128 is_tn = !pushc && !local && sstelnet;
7131 c = coninc(timelimit > 0 ? 1 : 0);
7133 /* This is likely to break the asktimeout... */
7134 c = coninc(timelimit);
7135 #endif /* COMMENT */
7136 /* debug(F101,"cmdgetc coninc","",c); */
7138 if (c >= 0 && is_tn) { /* Server-side Telnet */
7141 /* debug(F111,"gtword IAC","c",c); */
7143 if ((tx = tn_doop((CHAR)(c & 0xff),ckxech,coninc)) == 0) {
7145 } else if (tx <= -1) { /* I/O error */
7146 /* If there was a fatal I/O error then ttclos() */
7147 /* has been called and the next GETNEXTCH attempt */
7148 /* will be !is_tn since ttclos() sets sstelnet = 0 */
7149 doexit(BAD_EXIT,-1); /* (or return(-4)? */
7150 } else if (tx == 1) { /* ECHO change */
7151 ckxech = dpx = 1; /* Get next char */
7153 } else if (tx == 2) { /* ECHO change */
7154 ckxech = dpx = 0; /* Get next char */
7156 } else if (tx == 3) { /* Quoted IAC */
7157 c = 255; /* proceeed with it. */
7160 else if (tx == 4) { /* IKS State Change */
7163 #endif /* IKS_OPTION */
7164 else if (tx == 6) { /* Remote Logout */
7165 doexit(GOOD_EXIT,0);
7167 goto GETNEXTCH; /* Unknown, get next char */
7172 if (!TELOPT_U(TELOPT_BINARY)) {
7174 /* This means the sender is violating Telnet */
7175 /* protocol because we received two CRs in a */
7176 /* row without getting either LF or NUL. */
7177 /* This will not solve the problem but it */
7178 /* will at least allow two CRs to do something */
7179 /* whereas before the user would have to guess */
7180 /* to send LF or NUL after the CR. */
7181 debug(F100,"gtword CR telnet error","",0);
7184 debug(F100,"gtword skipping CR","",0);
7185 got_cr = 1; /* Remember a CR was received */
7189 debug(F100,"gtword CR to LF","",0);
7194 if (!TELOPT_U(TELOPT_BINARY)) {
7196 debug(F100,"gtword LF","",0);
7200 debug(F100,"gtword skipping LF","",0);
7206 if (!TELOPT_U(TELOPT_BINARY) && got_cr) {
7208 debug(F100,"gtword NUL to LF","",0);
7210 debug(F100,"gtword NUL","",0);
7216 if ( !TELOPT_U(TELOPT_BINARY) && got_cr ) {
7217 /* This means the sender is violating Telnet */
7218 /* protocol because we received two CRs in a */
7219 /* row without getting either LF or NUL. */
7220 /* This will not solve the problem but it */
7221 /* will at least allow two CRs to do something */
7222 /* whereas before the user would have to guess */
7223 /* to send LF or NUL after the CR. */
7224 debug(F100,"gtword CR telnet error","",0);
7226 got_cr = 1; /* Remember a CR was received */
7228 /* debug(F100,"gtword CR to LF","",0); */
7234 /* debug(F100,"gtword skipping LF","",0); */
7241 /* debug(F100,"gtword skipping NUL","",0); */
7245 debug(F100,"gtword NUL","",0);
7246 #endif /* COMMENT */
7249 #endif /* COMMENT */
7251 case ETX: /* Ctrl-C... */
7252 case EOT: /* EOT = EOF */
7256 #endif /* CK_LOGIN */
7272 #endif /* CMD_CONINC */
7280 #endif /* CMD_CONINC */
7282 /* debug(F101,"cmdgetc getc","",c); */
7288 #endif /* CMD_CONINC */
7289 c = getchar(); /* RTU doesn't discard the ^Z */
7293 #endif /* datageneral */
7294 return(c); /* Return what we got */
7297 /* #ifdef USE_ARROWKEYS */
7299 /* Mechanism to use for peeking into stdin buffer */
7301 #ifndef USE_FILE_CNT /* stdin->__cnt */
7302 #ifndef USE_FILE__CNT /* Note: two underscores */
7303 #ifdef HPUX /* HPUX 7-11 */
7306 #define USE_FILE__CNT
7310 #ifdef ANYSCO /* SCO UNIX, OSR5, Unixware, etc */
7311 #ifndef OLD_UNIXWARE /* But not Unixware 1.x or 2.0 */
7312 #ifndef UNIXWARE2 /* or 2.1.0 */
7313 #define USE_FILE__CNT
7314 #endif /* UNIXWARE2 */
7315 #endif /* OLD_UNIXWARE */
7318 #endif /* USE_FILE__CNT */
7319 #endif /* USE_FILE_CNT */
7321 #ifndef USE_FILE_R /* stdin->_r */
7322 #ifndef USE_FILE_CNT
7323 #ifndef USE_FILE__CNT
7324 #ifdef BSD44 /* {Free,Open,Net}BSD, BSDI */
7327 #endif /* USE_FILE__CNT */
7328 #endif /* USE_FILE_CNT */
7329 #endif /* USE_FILE_R */
7331 #ifndef USE_FILE_R /* stdin->_cnt */
7332 #ifndef USE_FILE_CNT
7333 #ifndef USE_FILE__CNT
7334 #define USE_FILE_CNT /* Everybody else (but Linux) */
7335 #endif /* USE_FILE__CNT */
7336 #endif /* USE_FILE_CNT */
7337 #endif /* USE_FILE_R */
7343 How many characters are waiting to be read at the console? Normally
7344 conchk() would tell us, but in Unix and VMS cmdgetc() uses stdio getchar(),
7345 thus bypassing coninc()/conchk(), so we have to peek into the stdin buffer,
7346 which is totally nonportable. Which is why this routine is, at least for
7347 now, used only for checking for arrow-key sequences from the keyboard after
7348 an ESC was read. Wouldn't it be nice if the stdio package had a function
7349 that returned the number of bytes waiting to be read from its buffer?
7350 Returns 0 or greater always.
7355 y = pushc ? 1 : 0; /* Have command character pushed? */
7357 x = conchk(); /* Check device-driver buffer */
7360 #ifdef CMD_CONINC /* See cmdgetc() */
7361 x = conchk(); /* Check device-driver buffer */
7363 #else /* CMD_CONINC */
7365 /* Here we must look inside the stdin buffer - highly platform dependent */
7367 #ifdef _IO_file_flags /* Linux */
7368 x = (int) ((stdin->_IO_read_end) - (stdin->_IO_read_ptr));
7369 debug(F101,"cmdconchk _IO_file_flags","",x);
7370 #else /* _IO_file_flags */
7371 #ifdef USE_FILE_CNT /* Traditional */
7373 debug(F101,"cmdconchk (*stdin)->_cnt","",(*stdin)->_cnt);
7377 debug(F101,"cmdconchk NOARROWKEYS x","",0);
7379 debug(F101,"cmdconchk stdin->_cnt","",stdin->_cnt);
7381 #endif /* NOARROWKEYS */
7383 if (x == 0) x = conchk();
7385 #else /* USE_FILE_CNT */
7386 #ifdef USE_FILE__CNT /* HP-UX */
7387 debug(F101,"cmdconchk stdin->__cnt","",stdin->__cnt);
7389 if (x == 0) x = conchk();
7391 #else /* USE_FILE_CNT */
7392 #ifdef USE_FILE_R /* FreeBSD, OpenBSD, etc */
7393 debug(F101,"cmdconchk stdin->_r","",stdin->_r);
7395 if (x == 0) x = conchk();
7398 /* Fill in any others here... */
7400 #endif /* USE_FILE_R */
7401 #endif /* USE_FILE__CNT */
7402 #endif /* USE_FILE_CNT */
7403 #endif /* _IO_file_flags */
7404 #endif /* CMD_CONINC */
7408 /* #endif */ /* USE_ARROWKEYS */
7412 cmdclrscn() { /* Clear the screen */
7416 static VOID /* What to echo at end of command */
7421 #endif /* CK_ANSIC */
7425 extern int inserver;
7426 if (inserver && c == LF)
7431 putchar(c); /* c is the terminating character */
7433 #ifdef WINTCP /* what is this doing here? */
7434 if (c == CR) putchar(NL);
7438 A.A. Chernov, who sent in changes for FreeBSD, said we also needed this
7439 for SVORPOSIX because "setup terminal by termios and curses does
7440 not convert \r to \n, so additional \n needed in newline function." But
7441 it is also very likely to result in unwanted blank lines.
7444 if (c == CR) putchar(NL);
7448 /* OS2 no longer needs this as all CR are converted to NL in coninc() */
7449 /* This eliminates the ugly extra blank lines discussed above. */
7451 if (c == CR) putchar(NL);
7453 #endif /* COMMENT */
7455 if (c == CR) putchar(NL);
7458 if (c == CR) putchar(NL);
7461 if (c == CR) putchar(NL);
7462 #endif /* datageneral */
7464 if (c == CR) putchar(NL);
7467 if (c == CR) putchar(NL);
7468 #endif /* STRATUS */
7472 cmdchardel() { /* Erase a character from the screen */
7478 /* DG '\b' is EM (^y or \031) */
7480 /* Erase a character from non-DG screen, */
7481 dgncoub(1,"\010 \010",3);
7483 #endif /* datageneral */
7496 cmdecho(char c, int quote)
7498 cmdecho(c,quote) char c; int quote;
7499 #endif /* CK_ANSIC */
7507 c = (char)echostars;
7510 /* Echo tty input character c */
7516 putchar((CHAR) (isprint(c) ? c : '^' ));
7518 putchar((CHAR) ((c >= SP && c < DEL) ? c : '^'));
7519 #endif /* isprint */
7524 if (quote==1 && c==CR) putchar((CHAR) NL);
7530 /* Return pointer to current position in command buffer. */
7545 /* X X E S C -- Interprets backslash codes */
7546 /* Returns the int value of the backslash code if it is > -1 and < 256 */
7547 /* and updates the string pointer to first character after backslash code. */
7548 /* If the argument is invalid, leaves pointer unchanged and returns -1. */
7551 xxesc(s) char **s; { /* Expand backslash escapes */
7552 int x, y, brace, radix; /* Returns the int value */
7553 char hd = '9'; /* Highest digit in radix */
7556 p = *s; /* pointer to beginning */
7557 if (!p) return(-1); /* watch out for null pointer */
7558 x = *p++; /* character at beginning */
7559 if (x != CMDQ) return(-1); /* make sure it's a backslash code */
7561 x = *p; /* it is, get the next character */
7562 if (x == '{') { /* bracketed quantity? */
7563 p++; /* begin past bracket */
7567 switch (x) { /* Start interpreting */
7568 case 'd': /* Decimal radix indicator */
7570 p++; /* Just point past it and fall thru */
7571 case '0': /* Starts with digit */
7573 case '2': case '3': case '4': case '5':
7574 case '6': case '7': case '8': case '9':
7575 radix = 10; /* Decimal */
7576 hd = '9'; /* highest valid digit */
7578 case 'o': /* Starts with o or O */
7580 radix = 8; /* Octal */
7581 hd = '7'; /* highest valid digit */
7582 p++; /* point past radix indicator */
7584 case 'x': /* Starts with x or X */
7586 radix = 16; /* Hexadecimal */
7587 p++; /* point past radix indicator */
7589 default: /* All others */
7591 *s = p+1; /* Treat as quote of next char */
7595 #endif /* COMMENT */
7597 /* For OS/2, there are "wide" characters required for the keyboard
7598 * binding, i.e \644 and similar codes larger than 255 (byte).
7599 * For this purpose, give up checking for < 256. If someone means
7600 * \266 should result in \26 followed by a "6" character, he should
7601 * always write \{26}6 anyway. Now, return only the lower byte of
7602 * the result, i.e. 10, but eat up the whole \266 sequence and
7603 * put the wide result 266 into a global variable. Yes, that's not
7604 * the most beautiful programming style but requires the least
7605 * amount of changes to other routines.
7607 if (*p == '{') { /* Sun May 11 20:00:40 2003 */
7608 brace = 1; /* Allow {} after radix indicator */
7611 if (radix <= 10) { /* Number in radix 8 or 10 */
7613 (*p) && (*p >= '0') && (*p <= hd)
7615 && (y < 5) && (x*radix < KMSIZE);
7616 /* the maximum needed value \8196 is 4 digits long */
7617 /* while as octal it requires \1377, i.e. 5 digits */
7619 && (y < 3) && (x*radix < 256);
7622 x = x * radix + (int) *p - 48;
7625 wideresult = x; /* Remember wide result */
7628 if (y == 0 || x > 255) { /* No valid digits? */
7629 *s = p; /* point after it */
7630 return(-1); /* return failure. */
7632 } else if (radix == 16) { /* Special case for hex */
7633 if ((x = unhex(*p++)) < 0) { *s = p - 1; return(-1); }
7634 if ((y = unhex(*p++)) < 0) { *s = p - 2; return(-1); }
7635 x = ((x << 4) & 0xF0) | (y & 0x0F);
7638 if ((y = unhex(*p)) >= 0) {
7640 wideresult = ((x << 4) & 0xFF0) | (y & 0x0F);
7641 x = wideresult & 255;
7645 if (brace && *p == '}' && x > -1) /* Point past closing brace, if any */
7647 *s = p; /* Point to next char after sequence */
7648 return(x); /* Return value of sequence */
7651 int /* Convert hex string to int */
7656 #endif /* CK_ANSIC */
7659 if (x >= '0' && x <= '9') /* 0-9 is offset by hex 30 */
7661 else if (x >= 'A' && x <= 'F') /* A-F offset by hex 37 */
7663 else if (x >= 'a' && x <= 'f') /* a-f offset by hex 57 */
7664 return(x - 0x57); /* (obviously ASCII dependent) */
7668 /* L O O K U P -- Lookup the string in the given array of strings */
7671 Call this way: v = lookup(table,word,n,&x);
7673 table - a 'struct keytab' table.
7674 word - the target string to look up in the table.
7675 n - the number of elements in the table.
7676 x - address of an integer for returning the table array index,
7677 or NULL if you don't need a table index.
7679 The keyword table must be arranged in ascending alphabetical order;
7680 alphabetic case doesn't matter but letters are treated as lowercase
7681 for purposes of ordering; thus "^" and "_" come *before* the letters,
7684 Returns the keyword's associated value (zero or greater) if found,
7685 with the variable x set to the keyword-table index. If is lookup()
7686 is not successful, it returns:
7688 -3 if nothing to look up (target was null),
7692 A match is successful if the target matches a keyword exactly, or if
7693 the target is a prefix of exactly one keyword. It is ambiguous if the
7694 target matches two or more keywords from the table.
7696 Lookup() is the critical routine in scripts and so is optimized with a
7697 simple static cache plus some other tricks. Maybe it could be improved
7698 further with binary search or hash techniques but I doubt it since most
7699 keyword tables are fairly short.
7702 #ifdef USE_LUCACHE /* Lookup cache */
7703 extern int lusize; /* (initialized in ckuus5.c) */
7704 extern char * lucmd[];
7707 extern struct keytab * lutab[];
7712 #endif /* USE_LUCACHE */
7715 lookup(table,cmd,n,x) char *cmd; struct keytab table[]; int n, *x; {
7718 int v, len, cmdlen = 0;
7719 char c = NUL, c1, *s;
7721 /* Get 1st char of search object, if it's null return -3. */
7723 if (!cmd || n < 1) /* Defense de nullarg */
7725 c1 = *cmd; /* First character */
7726 if (!c1) /* Make sure there is one */
7728 if (isupper(c1)) /* If letter make it lowercase */
7731 #ifdef USE_LUCACHE /* lookup() cache */
7733 lucalls++; /* Count this lookup() call */
7734 for (i = 0; i < m; i++) { /* Loop thru cache */
7735 if (*(lucmd[i]) == c1) { /* Same as 1st char of search item? */
7736 if (lutab[i] == table) { /* Yes - same table too? */
7737 if (!strcmp(cmd,lucmd[i])) { /* Yes - compare */
7738 if (x) *x = luidx[i]; /* Match - return index */
7739 luhits++; /* Count cache hit */
7740 return(luval[i]); /* Return associated value */
7745 #endif /* USE_LUCACHE */
7747 /* Not null, not in cache, look it up */
7750 while (*s++) cmdlen++; /* Length of target */
7752 Quick binary search to find last table entry whose first character is
7753 lexically less than the first character of the search object. This is
7754 the starting point of the next loop, which must go in sequence since it
7755 compares adjacent table entries.
7757 if (n < 5) { /* Not worth it for small tables */
7763 while (lo+2 < hi && ++count < 12) {
7764 i = lo + ((hi - lo) / 2);
7765 c = *(table[i].kwd);
7766 if (isupper(c)) c = tolower(c);
7773 i = (c < c1) ? lo+1 : lo;
7775 if (i > 0) xxhits++;
7776 #endif /* USE_LUCACHE */
7778 for ( ; i < n-1; i++) {
7781 #endif /* USE_LUCACHE */
7783 c = *(table[i].kwd);
7785 if (isupper(c)) c = tolower(c);
7787 /* The following is a big performance booster but makes it */
7788 /* absolutely essential that all lookup() tables are in order. */
7790 if (c > c1) /* Leave early if past our mark */
7794 /* Use LOG DEBUG to check */
7797 if (ckstrcmp(table[i].kwd,table[i+1].kwd,0,0) > 0) {
7798 printf("TABLE OUT OF ORDER [%s] [%s]\n",
7799 table[i].kwd,table[i+1].kwd);
7809 if ((len == cmdlen && !ckstrcmp(table[i].kwd,cmd,len,0)) ||
7810 ((v = !ckstrcmp(table[i].kwd,cmd,cmdlen,0)) &&
7811 ckstrcmp(table[i+1].kwd,cmd,cmdlen,0))) {
7813 return(table[i].kwval);
7817 if (v) { /* Ambiguous */
7818 if (x) *x = i; /* Set index of first match */
7823 /* Last (or only) element */
7825 if (!ckstrcmp(table[n-1].kwd,cmd,cmdlen,0)) {
7827 /* debug(F111,"lookup",table[i].kwd,table); */
7828 return(table[n-1].kwval);
7835 Like lookup, but requires a full (but case-independent) match
7836 and does NOT require the table to be in order.
7839 xlookup(table,cmd,n,x) struct keytab table[]; char *cmd; int n, *x; {
7841 int len, cmdlen, one = 0;
7842 register char c, c2, * s, * s2;
7844 if (!cmd) cmd = ""; /* Check args */
7845 if (!*cmd || n < 1) return(-3);
7847 c = *cmd; /* First char of string to look up */
7848 if (!*(cmd+1)) { /* Special handling for 1-char names */
7856 while (*s++) cmdlen++;
7864 for (i = 0; i < n; i++) {
7865 s = table[i].kwd; /* This entry */
7867 if (!*s) continue; /* Empty table entry */
7869 if (isupper(c2)) c2 = tolower(c2);
7870 if (c != c2) continue; /* First char doesn't match */
7871 if (one) { /* Name is one char long */
7875 return(table[i].kwval); /* So is table entry */
7877 } else { /* Otherwise do string comparison */
7880 while (*s2++) len++;
7881 if (len == cmdlen && !ckstrcmp(s,cmd,-1,0)) {
7883 return(table[i].kwval);
7890 /* Reverse lookup */
7893 rlookup(table,n,x) struct keytab table[]; int n, x; {
7895 for (i = 0; i < n; i++) {
7896 if (table[i].kwval == x)
7897 return(table[i].kwd);