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