/* * Copyright (c) 1991 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie the rights * to redistribute these changes. */ /* ci -- command interpreter * * Usage (etc.) * * HISTORY * 22-Nov-85 Glenn Marcy (gm0w) at Carnegie-Mellon University * Removed checks for VIRTUE window manager. If they don't like * it then they can fix the more program. * * 08-May-85 Steven Shafer (sas) at Carnegie-Mellon University * Increased MAXENTRIES and MAXHELPS from 200 to 400. * * 30-Apr-85 Steven Shafer (sas) at Carnegie-Mellon University * Adapted for 4.2 UNIX. Added calls to check for * using window manager of VIRTUE. * * 29-Apr-85 Steven Shafer (sas) at Carnegie-Mellon University * Added two small bug fixes (courtesy of Richard Cohn). * * 14-Aug-84 Steven Shafer (sas) at Carnegie-Mellon University * Added fflush(stdout) after printing prompt, before asking for input line. * * 01-Jul-83 Steven Shafer (sas) at Carnegie-Mellon University * Bug fix: whitespace now required before ">filename" and not permitted * within or after filename. * * 06-Jun-83 Steven Shafer (sas) at Carnegie-Mellon University * Bug fix: added line to initialize "redirected" to 0. * * 20-May-83 Steven Shafer (sas) at Carnegie-Mellon University * Added quiet bits CINOSEM, CINOFILE, CIFIRSTEQUAL to allow user to * have special characters ; > = treated as normal data (except = * after first argument, which still means "variable assignment"). * Also added output redirection via >filename on input line. * * 07-Mar-83 Dave McKeown (dmm) at Carnegie-Mellon University * (Slight alterations by Steve Shafer.) * Made cidepth a global, used for recursive and nested calls to * ci(), and accessable to the user. Added '@x' command, similar * to '^x' except that the previous command interpreter name is * remembered and after 'x' is executed, the previous command * interpreter is reinvoked. Users who plan to use this feature * must save the name of the previous ci in global variable * 'ciprev' after exit from the ci(). ie. * ci(.........); * strcpy(ciprev,"ci-name"); * Added ci state CICMDNOINDENT to allow for no indentation of the * command line prompt based on cidepth. * Reduced amount of indentation on source code. * Bug: the "depth" argument is now a no-op, retained for backward * compatibility. Cidepth is initialized to zero, and incremented * upon invocation of a ci(). If cidepth is <1 then you are not * in a ci() instantiation. * * 21-Feb-83 Steven Shafer (sas) at Carnegie-Mellon University * Added up-arrow (^) command (and variable cinext). ^x is used when * you have a ci program in which one command invokes ci with a * new set of commands (i.e. a subsystem of the program). Inside the * subsystem, ^x will exit the subsystem, and cause the main level * to execute the command line "x" before reading more input lines. * The cinext variable is used to implement this. Cinext can also be * used by any user code which desires to force ci to execute a * specific command before reading more input from the current file. * * 16-Jul-82 Steven Shafer (sas) at Carnegie-Mellon University * Added extra code in _ci_help to eliminate duplicate help file * names. This way, if several directories are specified and there * is are files with the same name in more than one directory, only * the first of each file will be included in the help list. * * It would have been nice to do this after the qsort instead of * before (in ci_help). However, qsort does not ensure that * "equivalent" entries are kept in the same relative * order; thus there would be no way to ensure that the * file being used was the first such file found. * * 07-Jul-82 William Chiles (wpc) at Carnegie-Mellon University * Modified so that "!" invokes shell commands from the type of * shell specified by the environment variable SHELL. If SHELL * is not defined the standard shell is used. * * 21-Sep-81 Steven Shafer (sas) at Carnegie-Mellon University * Increased LINELENGTH (input buffer length) to 1100 to satisfy * voracious requirements of a certain user whose name I won't mention * but whose initials are "faa". * * 08-Oct-80 Steven Shafer (sas) at Carnegie-Mellon University * Added class variables: ci_tclass cases in ci_show and ci_set. * Also added CICMDFPEEK in addition to existing CISETPEEK. * * 22-May-80 Steven Shafer (sas) at Carnegie-Mellon University * Ci now sorts help topics into alphabetical order. Some interrupt * handling has been added, but there are bugs, for example, when * you interrupt "*" (the listing of command names). The right thing * happens, but bogus messages are printed. * * 16-Apr-80 Steven Shafer (sas) at Carnegie-Mellon University * Ci now prints lists of names with prstab(). This uses multiple * columns when appropriate. * * 12-Mar-80 Steven Shafer (sas) at Carnegie-Mellon University * Added code to skip over leading blanks and tabs in the argument list * when executing commands, and setting and displaying variables. * Also fixed meta-help, which mysteriously disappeared. * * 19-Feb-80 Steven Shafer (sas) at Carnegie-Mellon University * Added "if (0) del();" to force del() routine to be loaded. This is * the safest way I know of to define the external int "_del_". If you * don't believe it, think again about separately compiled files. * * 28-Jan-80 Steven Shafer (sas) at Carnegie-Mellon University * Created. Patterned (somewhat) after ci() on PDP-11. * */ #include #include #include #include char *index(),*getenv(),*rindex(); extern char _argbreak; long atol(); double atof(); static int ci_help(), ci_show(); static int _ci_sho(), _ci_set(), ci_set(); /************************* *** M A C R O S *** *************************/ #define LINELENGTH 1100 /* max length of input line */ #define MAXENTRIES 400 /* max entries in entry list */ #define MAXHELPS 400 /* max help files available */ #define METAHELP "/usr/lib/ci.help" /* standard help file */ /********************************************* *** G L O B A L V A R I A B L E S *** *********************************************/ int ciquiet = 0; /* init globals */ int ciexit = 0; int cidepth = 0; int ciback = 0; /* for use in '@' command */ FILE *ciinput; char cinext[LINELENGTH] = ""; char ciprev[LINELENGTH] = ""; static char *delchoice[] = { /* breakpoint choices */ "abort abort command file", "breakpoint break to tty, then resume command file", 0}; /************************************* *** M A I N R O U T I N E *** *************************************/ ci (prompt,fil,depth,list,helppath,cmdfpath) char *prompt; /* prompt message */ FILE *fil; /* input file */ int depth; /* recursion depth */ CIENTRY *list; /* entry list */ char *helppath; /* search list for help files */ char *cmdfpath; /* search list for command files */ { FILE *savfile; /* input file for calling instance of ci */ int savquiet, savexit; /* globals for calling instance of ci */ char *p,*q,*cmd,*arg; /* temps for parsing input */ int i; /* temp */ char line[LINELENGTH]; /* input line buffer */ int firststmt; /* temp */ char *equals,*star; /* index of = and * in input line */ char cfnam[200]; /* name of command file */ char *name[MAXENTRIES]; /* name list for entries */ char *vname[MAXENTRIES]; /* name list for just variables */ int vnum[MAXENTRIES]; /* correspondence list for variables */ int nv; /* number of variables */ int helpcmd; /* "help" command index */ FILE *newfile; /* command file just opened */ char bprompt[100]; /* breakpoint prompt */ char *tname[MAXENTRIES]; /* temp name list */ int tnum; /* # entries in tname */ char *Shell; /* holds SHELL value from .login */ int redirected; /* 1 iff currently redirected output */ FILE savestdout; /* place to save normal std. output */ FILE *outfile; /* current output file */ char *outname; /* output file name */ /* force del() routine to be declared */ if (0) del(); /* save globals on stack */ cidepth++; /* bump the global depth, first CI() is 1 */ savquiet = ciquiet; savexit = ciexit; savfile = ciinput; ciexit = 0; /* don't exit until this is set */ ciinput = (fil ? fil : stdin); /* new input file */ /* construct name lists for stablk */ nv = 0; for (i=0; list[i].ci_etyp != ci_tend; i++) { name[i] = list[i].ci_enam; if (list[i].ci_etyp != ci_tcmd) { /* is variable */ vname[nv] = name[i]; vnum[nv] = i; nv++; } } helpcmd = i++; /* force-feed "help" onto list */ name[helpcmd] = "help"; name[i] = 0; vname[nv] = 0; /* loop for input lines */ redirected = 0; while (!ciexit) { if (*cinext) { /* get line from ^ command */ if (ciback) { sprintf(line,"%s;%s",cinext,ciprev); ciback = 0; } else { strcpy (line,cinext); } strcpy (cinext,""); p = line; } else { /* else read file */ if ((ciinput == stderr) || (ciinput == stdin) || (!(ciquiet&CICMDFPROMPT))) { if (!(ciquiet &CICMDNOINDENT)) { for (i=1; i'); if (outname) { if (outname == p || *(outname+1) == 0 || ((*(outname-1) != ' ') && (*(outname-1) != '\t'))) { outname = 0; } else { for (q=outname+1; *q && (*q != ' ') && (*q != '\t'); q++) ; if (*q) outname = 0; } } if (outname && !(ciquiet&CINOFILE)) { *outname++ = 0; outfile = fopen (outname,"w"); if (outfile == 0) { printf ("ci: Can't create output file %s\n",outname); p = ""; } else { fflush (stdout); savestdout = *stdout; *stdout = *outfile; redirected = 1; } } } /* loop for each command */ firststmt = 1; /* first time through loop */ while ((!ciexit) && (((ciquiet&CINOSEM) && firststmt) || *(cmd=nxtarg(&p,";")) || _argbreak)) { if (ciquiet & CINOSEM) { cmd = p; firststmt = 0; } switch (*cmd) { /* what kind of line? */ case ':': /* : comment */ case 0: /* null line */ break; case '!': /* ! shell command */ cmd = skipover (cmd+1," "); if ((Shell = getenv("SHELL")) == 0) Shell = "sh"; if (*cmd) runp (Shell, Shell, "-c", cmd, 0); else runp (Shell, Shell, 0); if (!(ciquiet&CISHEXIT)) printf ("Back to %s\n",prompt); break; case '?': /* ? help */ cmd = skipover (cmd+1," "); ci_help (cmd,helppath); break; case '<': /* < command file */ arg = cmd + 1; cmd = nxtarg (&arg,0); /* parse name */ if (*cmd == 0) printf ("ci: missing filename\n"); else { if (cmdfpath) newfile = fopenp (cmdfpath,cmd,cfnam,"r"); else newfile = fopen (cmd,"r"); if (newfile == 0) printf ("ci: can't open command file %s\n",cmd); else { if (!(ciquiet&CICMDFECHO)) printf ("\n"); ci (prompt,newfile,cidepth,list,helppath,cmdfpath); fclose (newfile); if (!(ciquiet&CICMDFEXIT)) printf ("End of file\n\n"); } } break; case '^': /* exit and do command */ case '@': if (cidepth > 1) { if (*cmd == '@') ciback = 1; if (_argbreak == ';') *(cmd+strlen(cmd)) = ';'; ciexit = 1; cmd = skipover(cmd+1," "); strcpy (cinext,cmd); } else printf ("ci: ^ not allowed at top level of ci\n"); break; default: /* list cmds, etc. */ equals = index (cmd,'='); if (equals == cmd) cmd++; if (equals) { if (*(equals+1) == 0) *equals = 0; else *equals = ' '; } arg = cmd; /* parse first word */ cmd = nxtarg (&arg,0); if ((ciquiet&CIFIRSTEQUAL) && equals && equals>arg) { *equals = '='; /* if user doesn't want extra =, kill */ equals = 0; } star = index (cmd,'*'); if (star) *star = 0; if (star && equals) { /* list vars */ printf ("\n"); for (i=0; vname[i]; i++) { if (stlmatch (vname[i],cmd)) { ci_show (list[vnum[i]],arg,CIPEEK); } DELBREAK; } printf ("\n"); } else if (star) { /* list cmds */ printf ("\n"); tnum = 0; for (i=0;name[i]; i++) { if ((i==helpcmd || list[i].ci_etyp == ci_tcmd) && stlmatch (name[i],cmd)) { tname[tnum++] = name[i]; } } tname[tnum] = 0; prstab (tname); if (_del_) {_DELNOTE_} printf ("\n"); } else if (equals) { /* set var */ i = stablk (cmd,vname,0); if (i >= 0) ci_set (list[vnum[i]],skipover(arg," \t")); } else { i = stablk (cmd,name,0); if (i == helpcmd) ci_help (arg,helppath); else if (i >= 0) { if (list[i].ci_etyp == ci_tcmd) { (* (int(*)()) (list[i].ci_eptr)) (skipover(arg," \t")); } else ci_show (list[i],skipover(arg," \t"),CISHOW); } } } /* end of command */ /* DEL trapping */ if (_del_) { if (ciinput == stdin) { DELCLEAR; /* already at tty level */ } else { _del_ = 0; i = getstab ("INTERRUPT: abort or breakpoint?",delchoice,"abort"); if (i == 0) ciexit = 1; /* abort */ else { /* breakpoint */ sprintf (bprompt,"Breakpoint for %s",prompt); ci (bprompt,0,cidepth,list,helppath,cmdfpath); } } } /* end of loop for commands */ } /* end of loop for lines of input file */ if (redirected) { fflush (stdout); fclose (stdout); *stdout = savestdout; redirected = 0; } } /* restore globals */ cidepth --; /* update current depth */ ciinput = savfile; ciquiet = savquiet; ciexit = savexit; } /******************************************** *** P R I N T H E L P F I L E *** ********************************************/ static int _h_found; /* how many matching names? */ static char **_h_list; /* list of matching names */ static char (*_h_nlist)[20]; /* list of filename part of names */ static int _ci_help (filspec) /* called by searchp to expand filspec, adding names to _h_list */ char *filspec; { register int i,j,result; char dir[200]; result = expand (filspec, _h_list + _h_found, MAXHELPS - _h_found); if (result > 0) { for (i=0; i= 0) { f = fopen (fnames[i],"r"); if (f == 0) printf ("Yikes! Can't open help file %s\n",fnames[i]); else { printf ("\n"); runp("more","more",fnames[i],0); if (_del_) {_DELNOTE_} printf ("\n"); fclose (f); } } } } else { /* request topic list */ printf ("\nHelp is available for these topics:\n"); prstab (nptr); if (_del_) {_DELNOTE_} printf ("\n"); } for (i=0; i<_h_found; i++) free (fnames[i]); } } /********************************************************* *** S H O W V A L U E O F V A R I A B L E *** *********************************************************/ static ci_show (entry,arg,mode) CIENTRY entry; /* entry to display */ char *arg; /* arg for variable procedures */ CIMODE mode; /* mode (CIPEEK or CISHOW) */ { if (entry.ci_etyp == ci_tproc) { /* procedure */ (* (int(*)()) (entry.ci_eptr)) (mode,arg); } else if (entry.ci_etyp == ci_tclass) { /* class variable */ (* (int(*)()) (entry.ci_eptr)) (mode,arg,entry.ci_evar,entry.ci_enam); } else { printf ("%-14s \t",entry.ci_enam); _ci_sho (entry.ci_etyp, entry.ci_eptr); printf ("\n"); } } static _ci_sho (etype,eptr) ci_type etype; ci_union *eptr; { int i; unsigned int u; switch (etype) { case ci_tint: printf ("%d",eptr->ci_uint); break; case ci_tshort: printf ("%d",eptr->ci_ushort); break; case ci_tlong: printf ("%D",eptr->ci_ulong); break; case ci_toct: if (eptr->ci_uoct) printf ("0"); printf ("%o",eptr->ci_uoct); break; case ci_thex: if (eptr->ci_uhex) printf ("0x"); printf ("%x",eptr->ci_uhex); break; case ci_tdouble: printf ("%g",eptr->ci_udouble); break; case ci_tfloat: printf ("%g",eptr->ci_ufloat); break; case ci_tbool: if (eptr->ci_ubool) printf ("yes"); else printf ("no"); break; case ci_tstring: printf ("%s",(char *)eptr); break; case ci_tcint: printf ("%d",*(eptr->ci_ucint.ci_ival)); break; case ci_tcshort: printf ("%d",*(eptr->ci_ucshort.ci_sval)); break; case ci_tclong: printf ("%D",*(eptr->ci_uclong.ci_lval)); break; case ci_tcoct: u = *(eptr->ci_ucoct.ci_uval); if (u) printf ("0"); printf ("%o",u); break; case ci_tchex: u = *(eptr->ci_uchex.ci_uval); if (u) printf ("0x"); printf ("%x",u); break; case ci_tcdouble: printf ("%g",*(eptr->ci_ucdouble.ci_dval)); break; case ci_tcfloat: printf ("%g",*(eptr->ci_ucfloat.ci_fval)); break; case ci_tcbool: i = *(eptr->ci_ucbool.ci_bval); if (i) printf ("yes"); else printf ("no"); break; case ci_tcchr: i = *(eptr->ci_ucchr.ci_cval); printf ("%c",eptr->ci_ucchr.ci_cleg[i]); break; case ci_tcstring: printf ("%s",eptr->ci_ucstring.ci_pval); break; case ci_tctab: i = *(eptr->ci_ucstab.ci_tval); printf ("%s",eptr->ci_ucstab.ci_ttab[i]); break; case ci_tcsearch: i = *(eptr->ci_ucsearch.ci_tval); printf ("%s",eptr->ci_ucsearch.ci_ttab[i]); break; default: printf ("Yeek! Illegal cientry type %d!\n",(int) etype); } } /************************************************************* *** A S S I G N V A L U E T O V A R I A B L E *** *************************************************************/ static ci_set (entry,arg) CIENTRY entry; char *arg; { if (entry.ci_etyp == ci_tproc) { /* variable procedure */ (* (int(*)()) (entry.ci_eptr)) (CISET,arg); } else if (entry.ci_etyp == ci_tclass) { /* class variable */ (* (int(*)()) (entry.ci_eptr)) (CISET,arg,entry.ci_evar,entry.ci_enam); } else { _ci_set (entry.ci_etyp, entry.ci_eptr, arg); if (!(ciquiet & (((ciinput==stdin)||(ciinput==stderr)) ? CISETPEEK : CICMDFPEEK))) ci_show (entry,arg,CIPEEK); } } static _ci_set (etype,eptr,arg) ci_type etype; ci_union *eptr; char *arg; { int i; unsigned int u; char *p; if (etype == ci_tstring) { strcpy ((char *)eptr,arg); return; } if (etype == ci_tcstring) { strarg (&arg, ";", eptr->ci_ucstring.ci_pmsg, eptr->ci_ucstring.ci_pval,eptr->ci_ucstring.ci_pval); return; } p = arg; /* parse first word */ arg = nxtarg (&p,0); switch (etype) { case ci_tint: eptr->ci_uint = atoi (arg); break; case ci_tshort: eptr->ci_ushort = atoi (arg); break; case ci_tlong: eptr->ci_ulong = atol (arg); break; case ci_toct: eptr->ci_uoct = atoo (arg); break; case ci_thex: if (stlmatch(arg,"0x") || stlmatch(arg,"0X")) arg += 2; eptr->ci_uhex = atoh (arg); break; case ci_tdouble: eptr->ci_udouble = atof (arg); break; case ci_tfloat: eptr->ci_ufloat = atof (arg); break; case ci_tbool: eptr->ci_ubool = (index("yYtT",*arg) != 0); break; case ci_tcint: *(eptr->ci_ucint.ci_ival) = intarg (&arg,0,eptr->ci_ucint.ci_imsg,eptr->ci_ucint.ci_imin, eptr->ci_ucint.ci_imax,*(eptr->ci_ucint.ci_ival)); break; case ci_tcshort: *(eptr->ci_ucshort.ci_sval) = shortarg (&arg,0,eptr->ci_ucshort.ci_smsg,eptr->ci_ucshort.ci_smin, eptr->ci_ucshort.ci_smax,*(eptr->ci_ucshort.ci_sval)); break; case ci_tclong: *(eptr->ci_uclong.ci_lval) = longarg (&arg,0,eptr->ci_uclong.ci_lmsg,eptr->ci_uclong.ci_lmin, eptr->ci_uclong.ci_lmax,*(eptr->ci_uclong.ci_lval)); break; case ci_tcoct: *(eptr->ci_ucoct.ci_uval) = octarg (&arg,0,eptr->ci_ucoct.ci_umsg,eptr->ci_ucoct.ci_umin, eptr->ci_ucoct.ci_umax,*(eptr->ci_ucoct.ci_uval)); break; case ci_tchex: *(eptr->ci_uchex.ci_uval) = hexarg (&arg,0,eptr->ci_uchex.ci_umsg,eptr->ci_uchex.ci_umin, eptr->ci_uchex.ci_umax,*(eptr->ci_uchex.ci_uval)); break; case ci_tcdouble: *(eptr->ci_ucdouble.ci_dval) = doublearg (&arg,0,eptr->ci_ucdouble.ci_dmsg,eptr->ci_ucdouble.ci_dmin, eptr->ci_ucdouble.ci_dmax,*(eptr->ci_ucdouble.ci_dval)); break; case ci_tcfloat: *(eptr->ci_ucfloat.ci_fval) = floatarg (&arg,0,eptr->ci_ucfloat.ci_fmsg,eptr->ci_ucfloat.ci_fmin, eptr->ci_ucfloat.ci_fmax,*(eptr->ci_ucfloat.ci_fval)); break; case ci_tcbool: *(eptr->ci_ucbool.ci_bval) = boolarg (&arg,0,eptr->ci_ucbool.ci_bmsg,*(eptr->ci_ucbool.ci_bval)); break; case ci_tcchr: *(eptr->ci_ucchr.ci_cval) = chrarg (&arg,0,eptr->ci_ucchr.ci_cmsg,eptr->ci_ucchr.ci_cleg, eptr->ci_ucchr.ci_cleg[*(eptr->ci_ucchr.ci_cval)]); break; case ci_tctab: *(eptr->ci_ucstab.ci_tval) = stabarg (&arg,0,eptr->ci_ucstab.ci_tmsg,eptr->ci_ucstab.ci_ttab, eptr->ci_ucstab.ci_ttab[*(eptr->ci_ucstab.ci_tval)]); break; case ci_tcsearch: *(eptr->ci_ucsearch.ci_tval) = searcharg (&arg,0,eptr->ci_ucsearch.ci_tmsg, eptr->ci_ucsearch.ci_ttab, eptr->ci_ucsearch.ci_ttab[*(eptr->ci_ucsearch.ci_tval)]); break; default:; } }