1536 lines
30 KiB
C
1536 lines
30 KiB
C
#include "fx.h"
|
|
#include <setjmp.h>
|
|
#include <stdarg.h>
|
|
#ifdef ARCS_SA
|
|
#include <libsc.h>
|
|
#else
|
|
#include <sys/signal.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#ifdef _STANDALONE
|
|
# undef stdin
|
|
# define stdin 0 /* sa fgets takes a filedescr */
|
|
# undef stdout
|
|
# define stdout (void*)1L
|
|
# undef putchar /* use actual routine for standalone */
|
|
#endif
|
|
|
|
/* references to this generated by menucvt.awk; there used to be
|
|
* a seperate copy of the string for each menu. */
|
|
const char menu_title[] = "please choose one (? for help, .. to quit this menu)";
|
|
|
|
/*
|
|
* input routines. prompting, aborting, etc. since the basic input
|
|
* mechanism is via gets(), all input is cooked ala unix. there are
|
|
* 2 kinds of prompts: prompts for a menu choice, and prompts for
|
|
* informational parameters.
|
|
*/
|
|
|
|
/*
|
|
* structure for control of prompts and quits
|
|
*/
|
|
typedef struct callrec
|
|
{
|
|
char *name; /* name for prompting at this level */
|
|
jmp_buf lab; /* quit label at this level */
|
|
int argc; char **argv; /* argument list at this level */
|
|
struct callrec *chain; /* previous level */
|
|
} CR;
|
|
CR *chead; /* current level of control */
|
|
|
|
ITEM name_item; /* current item named by nameitem() */
|
|
MENU *cmenu; /* current menu used by nameitem() */
|
|
MENU *rmenu; /* root of menus used by nameitem() */
|
|
MENU *pmenu; /* last menu named by nameitem() */
|
|
MENU *prmenu; /* prompt menu */
|
|
ITEM *dot_item, *dotdot_item, *help_item;
|
|
|
|
/* used in standalone.c also (gross, knows jmp_buf's type). */
|
|
typedef int64_t *_jmp;
|
|
_jmp sig_jmp;
|
|
|
|
/*
|
|
* macros to get funcval fields from item
|
|
*/
|
|
FUNCVAL **funcvallist;
|
|
# define MENUFIELD(t) ((*(funcvallist + (t)->value))->menu)
|
|
# define FUNCFIELD(t) ((*(funcvallist + (t)->value))->func)
|
|
# define HELPFIELD(t) ((*(funcvallist + (t)->value))->helpstrings)
|
|
|
|
extern FUNCVAL *scsi_funcvallist[];
|
|
|
|
/* Pointers to the . .. and help functions for the current driver */
|
|
extern ITEM scsi_dot_item, scsi_dotdot_item, scsi_help_item;
|
|
extern MENU scsi_fx_menu;
|
|
extern MENU *rmenu, *cmenu;
|
|
extern int nomenus; /* see fx.c, -c option */
|
|
|
|
static void printc(CR *cp);
|
|
static void menu_mprint(MENU *m);
|
|
|
|
/*
|
|
* flush buffered output if any
|
|
*/
|
|
void
|
|
flushoutp(void)
|
|
{
|
|
fflush(stdout);
|
|
}
|
|
|
|
/*
|
|
* read a line, and split into args. the given buf is divided into
|
|
* a data part and an arg pointers part. pass back argc and argv.
|
|
*/
|
|
void
|
|
mgetargs(char *line, uint len, int *_argc, char ***_argv)
|
|
{
|
|
register char **av; register int n;
|
|
|
|
ggets(line, len);
|
|
|
|
/* put arg pointers at the end of data */
|
|
av = (char **)(line + strlen(line) + 1);
|
|
n = (__psint_t)av % sizeof *av;
|
|
if( n != 0 )
|
|
av = (char **)((char *)av - n) + 1;
|
|
*_argv = av;
|
|
|
|
av = (char **)(line + len);
|
|
av = (char **)((char *)av - n);
|
|
|
|
/* split the line using the arg pointer part */
|
|
*_argc = strbreak(line, *_argv, av - *_argv);
|
|
}
|
|
|
|
/*
|
|
* get an input string. strip leading and trailing blanks
|
|
* Exits on EOF. Note that it now goes through the 'graceful exit'
|
|
* path that check if anything should be flushed, etc.
|
|
*/
|
|
void
|
|
ggets(char *buf, int len)
|
|
{
|
|
register char *cp = buf;
|
|
|
|
if(fgets(buf, len, stdin) == NULL) {
|
|
newline();
|
|
exit_func();
|
|
}
|
|
|
|
while(*cp && *cp != '\n') {
|
|
if(cp != buf || !isspace(*cp)) /* ignore leading space */
|
|
len--;
|
|
cp++;
|
|
}
|
|
|
|
*cp = '\0';
|
|
|
|
while (cp > buf && isspace(cp[-1]))
|
|
*--cp = '\0';
|
|
}
|
|
|
|
/*
|
|
* split a line into args, with simple quoting. return the actual
|
|
* number of args.
|
|
*/
|
|
uint
|
|
strbreak(char *line, char **vec, int maxvec)
|
|
{
|
|
register char c, *tp, *sp;
|
|
int nvec;
|
|
char quotc;
|
|
|
|
nvec = 0;
|
|
sp = line;
|
|
|
|
for( ;; )
|
|
{
|
|
while( isspace(*sp) )
|
|
sp++;
|
|
if( *sp == '\0' )
|
|
break;
|
|
|
|
if( nvec >= maxvec )
|
|
return -1;
|
|
|
|
*vec++ = sp;
|
|
nvec++;
|
|
|
|
quotc = 0;
|
|
tp = sp;
|
|
while( (c = *sp++) != '\0' && (quotc || !isspace(c)) )
|
|
{
|
|
if( quotc )
|
|
{
|
|
if( c == quotc )
|
|
quotc = 0;
|
|
else
|
|
*tp++ = c;
|
|
continue;
|
|
}
|
|
if( c == '\\' )
|
|
*tp++ = (*sp == '\0' ? '\\' : *sp++);
|
|
else
|
|
if( c == '\'' )
|
|
quotc = c;
|
|
else
|
|
*tp++ = c;
|
|
}
|
|
*tp = '\0';
|
|
if( c == '\0' )
|
|
break;
|
|
}
|
|
|
|
return nvec;
|
|
}
|
|
|
|
|
|
/* ----- menu routines ----- */
|
|
|
|
/* init the stuff for this drive type. Was part of menuloop, but
|
|
* to avoid finding all cases where menu stuff is used, when -c is
|
|
* passed, I want to init the menus, but not enter the loop...
|
|
*/
|
|
void
|
|
menuloop_init(void)
|
|
{
|
|
switch (drivernum) {
|
|
|
|
|
|
|
|
case SCSI_NUMBER:
|
|
#ifdef SMFD_NUMBER
|
|
case SMFD_NUMBER:
|
|
#endif /* SMFD_NUMBER */
|
|
funcvallist = scsi_funcvallist;
|
|
dot_item = &scsi_dot_item;
|
|
dotdot_item = &scsi_dotdot_item;
|
|
help_item = &scsi_help_item;
|
|
init_menus(&scsi_fx_menu);
|
|
break;
|
|
|
|
default:
|
|
printf("Don't know how about driver type %d\n",
|
|
drivernum);
|
|
exit(1);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* repeated selections from a menu. use the current menu to start with.
|
|
* subsequent function calls may change the current menu. this is the
|
|
* Only way to start up nameitem() correctly.
|
|
*/
|
|
void
|
|
menuloop(void)
|
|
{
|
|
for( ;; ) {
|
|
argcheck();
|
|
callmenu(cmenu);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* traverse the menu tree to find the named item. return a pointer.
|
|
* global inputs:
|
|
* rmenu is a pointer to the root menu.
|
|
* cmenu is a pointer to the current menu.
|
|
* dot_item is just a handle on a dummy item and funcval.
|
|
* dotdot_item is a legit item which executes ".." from the menu root.
|
|
* help_item is a legit item which implements the help function.
|
|
* dot_item, dotdot_item, and help_item are created by the menu scripts.
|
|
*
|
|
* side-effects:
|
|
* name_item the returned item.
|
|
* pmenu is a pointer to the parent menu of the item.
|
|
*
|
|
* the caller has already set up the "current" menu and path.
|
|
*
|
|
* there are 2 special cases: "?" and "..". for "?", fake an item
|
|
* with the right menu pointer. for "..", back up one level of menu,
|
|
* or pop back one level of control if already at the root.
|
|
*
|
|
* We used to print the menu #, but it turns out that the
|
|
* numbers are confusing, because they change from release
|
|
* to release, and depending on whether or not expert mode is on
|
|
* in some cases, and worst of all, the same number can mean
|
|
* different things for different drive types. So now it is
|
|
* just the item, along with a visual indication of the least ambiguous
|
|
* prefix. Olson, 3/91.
|
|
*/
|
|
static ITEM *
|
|
nameitem(char *path)
|
|
{
|
|
register ITEM *t;
|
|
register MENU *m;
|
|
STRBUF comp;
|
|
register int i;
|
|
register char c;
|
|
|
|
m = cmenu;
|
|
c = *path++;
|
|
if( c == '/' )
|
|
{
|
|
m = rmenu;
|
|
while( c == '/' )
|
|
c = *path++;
|
|
}
|
|
|
|
for( ;; )
|
|
{
|
|
pmenu = m;
|
|
if( c == '\0' )
|
|
{
|
|
t = dot_item;
|
|
name_item.value = t->value;
|
|
name_item.name = m->name;
|
|
MENUFIELD(&name_item) = m;
|
|
return &name_item;
|
|
}
|
|
|
|
i = 0;
|
|
while( c != '\0' && c != '/' )
|
|
{
|
|
comp.c[i++] = c;
|
|
c = *path++;
|
|
}
|
|
comp.c[i] = '\0';
|
|
|
|
while( c == '/' )
|
|
c = *path++;
|
|
|
|
if( strcmp(comp.c, "..") == 0 )
|
|
{
|
|
m = m->dotdot;
|
|
if( m != 0 )
|
|
continue;
|
|
t = dotdot_item;
|
|
}
|
|
else
|
|
if( strcmp(comp.c, "?") == 0 )
|
|
{
|
|
t = help_item;
|
|
}
|
|
else
|
|
{
|
|
if(!(t = matchitem(m, comp.c, 0)))
|
|
return 0;
|
|
m = MENUFIELD(t);
|
|
}
|
|
|
|
if( c == '\0' )
|
|
{
|
|
name_item = *t;
|
|
return &name_item;
|
|
}
|
|
|
|
if( m == 0 )
|
|
{
|
|
printf("not a menu\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* prompt with a menu, and implement the response.
|
|
*/
|
|
void
|
|
callmenu(MENU *m)
|
|
{
|
|
char line[INBUFSIZE]; int argc; char **argv;
|
|
register ITEM *t;
|
|
|
|
if( m->nitems <= 0 )
|
|
menu_digest(m);
|
|
|
|
if(m->initfunc) /* call initial function, if set */
|
|
(*m->initfunc)();
|
|
newline();
|
|
menu_mprint(m);
|
|
prmenu = m;
|
|
prompt("> ");
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
|
|
if( argc <= 0 )
|
|
return;
|
|
|
|
if( (t = nameitem(*argv)) != 0 )
|
|
{
|
|
*argv = t->name;
|
|
prmenu = pmenu;
|
|
if( MENUFIELD(t) == 0 )
|
|
newline();
|
|
callsub(FUNCFIELD(t), argc, argv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* match a string against a menu, and return the corresponding item.
|
|
*/
|
|
ITEM *
|
|
matchitem(MENU *m, char *s, int numok)
|
|
{
|
|
register int n;
|
|
if( (n = match(m, s, numok)) < 0 )
|
|
return 0;
|
|
return m->items+n;
|
|
}
|
|
|
|
|
|
/*
|
|
* validate a numeric string. return true if it is syntactically ok,
|
|
* pass back the number in *ip
|
|
*/
|
|
iscnum(char *src, uint64_t *ip)
|
|
{
|
|
return *src != '\0' && *skipcnum(src, 0, ip) == '\0';
|
|
}
|
|
|
|
|
|
/*
|
|
* match a string against a menu, and return the index of the
|
|
* corresponding item.
|
|
* BUT disallow match if item is 'expert' & expert mode is not set.
|
|
* Need to do expert check here rather than matchitem, so we don't get
|
|
* bogus ambiguous matches on menu choices that aren't visible!
|
|
* Numeric input allowed for numok. This is currently
|
|
* used ONLY for selecting a (non-scsi) drive type from a known list.
|
|
*/
|
|
int
|
|
match(MENU *m, char *s, int numok)
|
|
{
|
|
register int nmatches, ix;
|
|
uint64_t l;
|
|
register ITEM *t;
|
|
|
|
if( m->nitems <= 0 )
|
|
menu_digest(m);
|
|
|
|
if(numok && iscnum(s, &l))
|
|
{
|
|
if( 0 < (int)l && l <= m->nitems)
|
|
return (int)l-1;
|
|
printf("%d is out of range\n", (int)l);
|
|
return -1;
|
|
}
|
|
|
|
nmatches = 0;
|
|
|
|
/*
|
|
* search for a menu item with a matching item.
|
|
* if there is an exact match, return its index.
|
|
* otherwise, if the number of items with matching
|
|
* prefix is exactly one, return its index. if
|
|
* more or less than one, say so. Use m->nitems
|
|
* so we don't check invisible expert mode stuff when
|
|
* not in in expert mode...
|
|
*/
|
|
for( t = m->items; t < &m->items[m->nitems]; t++ )
|
|
{
|
|
if( strcmp(t->name, s) == 0 )
|
|
{
|
|
ix = t - m->items;
|
|
return ix;
|
|
}
|
|
if( wmatch(t->name, s) )
|
|
{
|
|
if(++nmatches > 1) {
|
|
if(nmatches == 2) /* on 2nd match, print msg and 1st */
|
|
printf("%s is ambiguous: %s", s, m->items[ix].name);
|
|
printf(", %s", t->name);
|
|
}
|
|
else
|
|
ix = t - m->items;
|
|
}
|
|
}
|
|
|
|
if( nmatches == 1 )
|
|
return ix;
|
|
|
|
if( nmatches <= 0 )
|
|
{
|
|
printf("No match found for \"%s\"\n", s);
|
|
return -1;
|
|
}
|
|
|
|
newline(); /* ambiguous, print the newline */
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* match an item name against a string, and return true if there is
|
|
* a matching prefix. if it is a compound item name, try matching
|
|
* against its subparts; _ is NOT considered punctuation.
|
|
*/
|
|
int
|
|
wmatch(char *iname, char *s)
|
|
{
|
|
if( prefmatch(iname, s) )
|
|
return 1;
|
|
while( *iname != '\0' )
|
|
{
|
|
if( isspace(*iname) || (*iname != '_' && ispunct(*iname)))
|
|
if( prefmatch(iname+1, s) )
|
|
return 1;
|
|
iname++;
|
|
}
|
|
return 0;
|
|
}
|
|
/*
|
|
* match a particular subpart of an item name against a string, and
|
|
* return true if they match. disregard punctuation in the item name,
|
|
* and spaces in the string. at least one char must match.
|
|
*/
|
|
int
|
|
prefmatch(char *ipart, char *str)
|
|
{
|
|
register char *s;
|
|
s = str;
|
|
for( ;; )
|
|
{
|
|
if( *ipart != *s )
|
|
{
|
|
while( isspace(*ipart) || ispunct(*ipart) )
|
|
ipart++;
|
|
while( isspace(*s) )
|
|
s++;
|
|
}
|
|
if( *s == '\0' )
|
|
return s > str;
|
|
if( *ipart != *s )
|
|
return 0;
|
|
ipart++ , s++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set off a string with dashes
|
|
*/
|
|
void
|
|
setoff(const char *s, ...)
|
|
{
|
|
va_list args;
|
|
|
|
printf("----- ");
|
|
va_start(args, s);
|
|
vprintf(s, args);
|
|
va_end(args);
|
|
printf("-----\n");
|
|
|
|
}
|
|
|
|
void
|
|
newline(void)
|
|
{
|
|
printf("\n");
|
|
}
|
|
|
|
/*
|
|
* warning banner
|
|
*/
|
|
void
|
|
banner(char *s)
|
|
{
|
|
register int i;
|
|
for( i = 5; --i >= 0; )
|
|
printf(" *");
|
|
while( *s != 0 )
|
|
printf(" %c", *s++);
|
|
for( i = 5; --i >= 0; )
|
|
printf(" *");
|
|
newline();
|
|
}
|
|
|
|
/*
|
|
* initialize menu tree
|
|
*/
|
|
void
|
|
init_menus(MENU *m)
|
|
{
|
|
rmenu = cmenu = m;
|
|
menu_traverse(rmenu);
|
|
}
|
|
|
|
/*
|
|
* search the menu tree for submenus, and create dotdot links
|
|
*/
|
|
void
|
|
menu_traverse(MENU *m)
|
|
{
|
|
register ITEM *t;
|
|
|
|
menu_digest(m);
|
|
for( t = m->items; t->name != 0; t++ )
|
|
if( MENUFIELD(t) != 0 )
|
|
{
|
|
MENUFIELD(t)->dotdot = m;
|
|
menu_traverse(MENUFIELD(t));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set up internal menu parameters, how long and how wide.
|
|
*/
|
|
void
|
|
menu_digest(MENU *m)
|
|
{
|
|
ITEM *t, *t1;
|
|
int i, j, k;
|
|
|
|
m->nitems = 0;
|
|
m->maxwidth = 0;
|
|
for( t = m->items; t->name != 0; t++ )
|
|
{
|
|
if (t->expert && !expert) break; /* Note: all nonexpert items */
|
|
/* MUST come first in a menu. */
|
|
i = strlen(t->name);
|
|
if( i > m->maxwidth )
|
|
m->maxwidth = i;
|
|
m->nitems++;
|
|
}
|
|
/* set the number of chars needed for uniqueness in menu
|
|
* items, to clue the user. */
|
|
for(i=0, t=m->items; i < m->nitems; t++, i++) {
|
|
t->uniqchars = 1;
|
|
for(j=0, t1=m->items; j < m->nitems; t1++, j++) {
|
|
if(t == t1)
|
|
continue;
|
|
k = strlen(t->name);
|
|
while(strncmp(t->name, t1->name, t->uniqchars) == 0
|
|
/*
|
|
* Don't continue checking if name is subset of
|
|
* another name
|
|
*/
|
|
&& t->uniqchars < k)
|
|
t->uniqchars++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
menusub(int i, ITEM *t, char *tgt)
|
|
{
|
|
sprintf(tgt, "[%.*s]%s", t->uniqchars, t->name,
|
|
t->name+t->uniqchars);
|
|
}
|
|
|
|
static void
|
|
menunumsub(int i, ITEM *t, char *tgt)
|
|
{
|
|
sprintf(tgt, "%d) %s", i, t->name);
|
|
}
|
|
|
|
/* print a menu with / denoting submenus */
|
|
/*ARGSUSED*/
|
|
static void
|
|
menumsub(int i, ITEM *t, char *tgt)
|
|
{
|
|
sprintf(tgt, "[%.*s]%s%s", t->uniqchars, t->name,
|
|
t->name+t->uniqchars, MENUFIELD(t) != 0 ? "/" : "");
|
|
}
|
|
|
|
static void
|
|
menu_print(MENU *m, int numok)
|
|
{
|
|
if(nomenus) return;
|
|
setoff(m->title);
|
|
colprint(m->items, m->nitems, sizeof *m->items,
|
|
m->maxwidth+2, numok ? menunumsub : menusub);
|
|
}
|
|
|
|
static void
|
|
menu_mprint(MENU *m)
|
|
{
|
|
if(nomenus) return;
|
|
setoff(m->title);
|
|
colprint(m->items, m->nitems, sizeof *m->items,
|
|
m->maxwidth+2, menumsub);
|
|
}
|
|
|
|
/*
|
|
* return a symbolic interpretation of flag bits
|
|
*/
|
|
char *
|
|
attribs(ITEM *items, int flags)
|
|
{
|
|
static STRBUF s;
|
|
register ITEM *t;
|
|
register char *cp, *f;
|
|
|
|
cp = s.c;
|
|
f = "";
|
|
for( t = items; t->name != 0; t++ )
|
|
if( (flags & t->value) != 0 )
|
|
{
|
|
sprintf(cp, "%s%s", f, t->name);
|
|
cp += strlen(cp);
|
|
flags &= ~t->value;
|
|
f = "|";
|
|
}
|
|
if( flags != 0 || cp == s.c )
|
|
sprintf(cp, "%s0x%x", f, flags);
|
|
return s.c;
|
|
}
|
|
|
|
|
|
/* ----- menu routines with prompting and quit control ----- */
|
|
/*
|
|
* setup to call the named subroutine with no arguments
|
|
*/
|
|
void
|
|
callfunc(void (*sub)(void), char *line)
|
|
{
|
|
int argc; char *argv[MAX_VEC];
|
|
argc = strbreak(line, argv, MAX_VEC);
|
|
callsub(sub, argc, argv);
|
|
}
|
|
|
|
/*
|
|
* set up control record (prompt, arguments, and quit label) for
|
|
* and call the given subroutine.
|
|
*/
|
|
void
|
|
callsub(void (*sub)(void), int argc, char **argv)
|
|
{
|
|
volatile int oldintr; /* ensure on stack for restoring */
|
|
CR c1;
|
|
|
|
argc--; c1.name = *argv++;
|
|
c1.argc = argc; c1.argv = argv;
|
|
c1.chain = chead;
|
|
chead = &c1;
|
|
sig_jmp = (_jmp)chead->lab;
|
|
if(setjmp(sig_jmp))
|
|
exercising = 0; /* see gread() */
|
|
else {
|
|
(void)setintr(oldintr = setintr(0));
|
|
(*sub)();
|
|
}
|
|
|
|
/* do setintr after chead changed, so we go to the prev chead->lab */
|
|
chead = c1.chain;
|
|
sig_jmp = (_jmp)chead->lab;
|
|
(void)setintr(oldintr);
|
|
}
|
|
|
|
/*
|
|
* print prompt by tracing back through control records.
|
|
*/
|
|
void
|
|
prompt(char *s)
|
|
{
|
|
if(!chead) return; /* early error message, with nomenus */
|
|
printc(chead);
|
|
printf("%s", s);
|
|
}
|
|
static void
|
|
printc(CR *cp)
|
|
{
|
|
if( cp->chain == 0 )
|
|
{
|
|
printf("%s", cp->name);
|
|
prmenupath(prmenu);
|
|
return;
|
|
}
|
|
printc(cp->chain);
|
|
printf("/%s", cp->name);
|
|
}
|
|
|
|
/*
|
|
* print a menu path, ala pwd
|
|
*/
|
|
void
|
|
prmenupath(MENU *m)
|
|
{
|
|
if( m == 0 || m->dotdot == 0 )
|
|
return;
|
|
prmenupath(m->dotdot);
|
|
printf("/%s", m->name);
|
|
}
|
|
|
|
/*
|
|
* change the current menu to the just-named menu from nameitem().
|
|
*/
|
|
void
|
|
dot_func(void)
|
|
{
|
|
argcheck();
|
|
cmenu = MENUFIELD(&name_item);
|
|
}
|
|
|
|
/*
|
|
* dotdot out of the root menu. optionally update the disk label.
|
|
* called as a menu function, 2 levels are popped to accomplish the
|
|
* desired purpose.
|
|
*/
|
|
void
|
|
dotdot_func(void)
|
|
{
|
|
optupdate();
|
|
chead = chead->chain;
|
|
sig_jmp = (_jmp)chead->lab;
|
|
mpop();
|
|
}
|
|
|
|
/*
|
|
* pop out of the current level of control.
|
|
*/
|
|
void
|
|
mpop(void)
|
|
{
|
|
/* this is done for the nomenus (-c) code, so that anybody causing
|
|
* an error causes us to exit. */
|
|
if(nomenus)
|
|
exit(2); /* semi-unique error code, so we can tell we pop'ed */
|
|
longjmp(sig_jmp, 1);
|
|
}
|
|
|
|
|
|
/* ----- */
|
|
|
|
|
|
/* ---- printing by cols ----- */
|
|
static uint linewidth = 79; /* max # characters on a line */
|
|
uint minspacing = 3; /* min width of a word column */
|
|
uint maxspacing = 8; /* max width of a word column */
|
|
|
|
/*
|
|
* print a table by columns. compute number and width of columns to
|
|
* make it look nice.
|
|
*/
|
|
void
|
|
colprint(void *p, uint n, uint size, uint w, void (*sub)(int,char *,char *))
|
|
{
|
|
STRBUF s;
|
|
register int wordwidth, rowsize;
|
|
int nrows, ncols, itemlag;
|
|
register int row, col, i;
|
|
register char *t;
|
|
|
|
if( n <= 0 )
|
|
return;
|
|
|
|
/*
|
|
* first see how many word columns will fit on one line.
|
|
* make sure word columns are at least minwidth in width.
|
|
* then check if the number of columns can be reduced
|
|
* without increasing the number of rows
|
|
*/
|
|
wordwidth = w + minspacing;
|
|
ncols = (linewidth + minspacing) / wordwidth;
|
|
nrows = (n + ncols - 1) / ncols;
|
|
ncols = (n + nrows - 1) / nrows;
|
|
|
|
/*
|
|
* now try to distribute the word columns evenly across the line,
|
|
* but don't let them get spaced too far apart
|
|
*/
|
|
wordwidth = linewidth / ncols;
|
|
if( wordwidth > w + maxspacing )
|
|
wordwidth = w + maxspacing;
|
|
|
|
rowsize = nrows * size;
|
|
|
|
itemlag = 0;
|
|
for( row = 0; row < nrows; row++ )
|
|
{
|
|
t = (char *)p + row * size;
|
|
i = row + 1;
|
|
for( col = 0; col < ncols; col++ )
|
|
{
|
|
if( i > n )
|
|
break;
|
|
|
|
(*sub)(i, t, s.c);
|
|
/* The following condition is true only for ARCS. Do not
|
|
print non usable items.
|
|
*/
|
|
if ((strncmp("[n1",s.c,3) == 0) || (strncmp("[n2",s.c,3) == 0))
|
|
while( --itemlag >= 0 );
|
|
else {
|
|
while( --itemlag >= 0 )
|
|
putchar(' ');
|
|
printf("%s", s.c);
|
|
}
|
|
itemlag = wordwidth-strlen(s.c);
|
|
t += rowsize;
|
|
i += nrows;
|
|
}
|
|
newline();
|
|
itemlag = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* ----- interactive hacks ----- */
|
|
/*
|
|
* interactively get a string. prompt using the given name. pass back
|
|
* the string in str. if the default string is not 0, use it if the
|
|
* response is null.
|
|
*/
|
|
void
|
|
getstring(char *str, char *dfl, char *name)
|
|
{
|
|
STRBUF s;
|
|
register int firsttime, ointr;
|
|
for( firsttime = 1;; firsttime = 0 )
|
|
{
|
|
if( !firsttime /* || dfl == 0 */ )
|
|
printf("please enter a string\n");
|
|
prompt(": "); printf("\"%s\" = ", name);
|
|
if( dfl != 0 ) printf("(%s) ", dfl);
|
|
ointr = mkintr();
|
|
ggets(s.c, sizeof s.c);
|
|
(void)setintr(ointr);
|
|
if( qcheck(s.c) )
|
|
continue;
|
|
if( *s.c == '\0' && dfl != 0 )
|
|
strcpy(str, dfl);
|
|
else
|
|
strcpy(str, s.c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/* get a fractional answer; return the value. Only used interactively.
|
|
very special purpose, returns numerator where divisor is given.
|
|
Used for the buffer full/empty stuff for CDC drives. Fraction
|
|
will be >= 0 and < 1.
|
|
*/
|
|
getfrac(uint curval, uint divisor, char *prmpt)
|
|
{
|
|
char line[INBUFSIZE]; int argc; char **argv;
|
|
int ointr;
|
|
uint64_t tmpn;
|
|
|
|
for(;;) {
|
|
prompt(": ");
|
|
printf("%s = (%u/%u) ", prmpt, curval,divisor);
|
|
|
|
ointr = mkintr();
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
(void)setintr(ointr);
|
|
|
|
if(argc == 0)
|
|
return curval; /* accept default */
|
|
if(!qcheck(*argv) && argc == 1 && iscnum(*argv, &tmpn) &&
|
|
tmpn < divisor)
|
|
return tmpn;
|
|
printf("please enter the numerator so the ratio is between 0 and 1\n");
|
|
}
|
|
}
|
|
|
|
|
|
/* get cnt numbers with no prompts, but with error checking. Returns #
|
|
* of numbers retrieved; 0 if none, -1 if something entered, but not a
|
|
* number, or out of range.
|
|
*/
|
|
get_nums(uint64_t hi, uint64_t *n, uint cnt)
|
|
{
|
|
char line[INBUFSIZE];
|
|
int argc;
|
|
char **argv;
|
|
int i, ointr;
|
|
|
|
ointr = mkintr();
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
(void)setintr(ointr);
|
|
|
|
if( argc > 0 ) {
|
|
if(qcheck(*argv))
|
|
return -1; /* help wanted */
|
|
if(argc > cnt)
|
|
return -1;
|
|
}
|
|
else
|
|
return argc;
|
|
|
|
for(i=0 ; i<argc; i++, argv++) {
|
|
if(!iscnum(*argv, n)) {
|
|
printf("%s isn't valid\n", *argv);
|
|
break;
|
|
}
|
|
if(hi && (*n >= hi)) {
|
|
printf("%llu out of range\n", *n);
|
|
break;
|
|
}
|
|
n++;
|
|
}
|
|
if(i != argc)
|
|
return -1;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* interactively get a number quantity. prompt using the given name.
|
|
* pass back the number via _n.
|
|
* If there is an upper limit (hi non-zero), and an out of range
|
|
* number is given, give them the range and re-prompt.
|
|
* Pop back if the response is 'q'.
|
|
* Return 0 if the user accepts the default value, else 1.
|
|
*/
|
|
getnum(uint64_t *_n, uint64_t hi, char *name)
|
|
{
|
|
uint64_t n;
|
|
int ret;
|
|
|
|
for(;;) {
|
|
n = *_n;
|
|
prompt(": ");
|
|
printf("%s = ", name);
|
|
printf("(%llu) ", n);
|
|
ret = get_nums(hi, &n, 1);
|
|
if(ret == 1 || ret == 0) /* gave us 1, or used default */
|
|
break;
|
|
printf("please enter a number");
|
|
if(hi)
|
|
printf(" from 0..%llu", hi-1);
|
|
|
|
newline();
|
|
}
|
|
*_n = n;
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* convert (initial) string to number,
|
|
* This is almost identical to the new stroul() ansi routine.
|
|
* it's so close, let's just use strtoull()...
|
|
*/
|
|
char *
|
|
skipcnum(char *sp, int defradix, uint64_t *ip)
|
|
{
|
|
char *nsp;
|
|
|
|
*ip = strtoull(sp, &nsp, defradix);
|
|
if(nsp == sp)
|
|
*ip = 0;
|
|
return nsp;
|
|
}
|
|
|
|
|
|
/*
|
|
* decode the given string as a block number or cyl/hd/sector. passes back
|
|
* the block number in _bn. returns 1 if the string is syntactically ok.
|
|
*/
|
|
isbn(char *str, daddr_t *_bn)
|
|
{
|
|
daddr_t b;
|
|
uint64_t l;
|
|
|
|
if( iscnum(str, &l) )
|
|
b = l;
|
|
else
|
|
return 0;
|
|
|
|
*_bn = b;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* interactively get a (daddr_t) block number. the number may be entered
|
|
* as cyl/hd/sec. prompt using the given name. pass back the block
|
|
* number via _n. if the initial value of *_n is >=0, use it as the default.
|
|
* pop back if the response is 'q'.
|
|
*/
|
|
void
|
|
getbn(daddr_t *_bn, daddr_t hi, char *name)
|
|
{
|
|
char line[INBUFSIZE]; int argc; char **argv;
|
|
daddr_t b;
|
|
register int firsttime, ointr;
|
|
int result;
|
|
|
|
for( firsttime = 1;; firsttime = 0 )
|
|
{
|
|
b = *_bn;
|
|
if( !firsttime || b < 0 )
|
|
{
|
|
printf("please enter a bn");
|
|
if( hi > 0 )
|
|
printf(" from 0 to %lld", hi-1);
|
|
newline();
|
|
}
|
|
prompt(": "); printf("%s = ", name);
|
|
if( b >= 0 )
|
|
printf("(%llu) ", b);
|
|
|
|
ointr = mkintr();
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
(void)setintr(ointr);
|
|
|
|
if(argc > 0 && qcheck(*argv))
|
|
continue;
|
|
if(argc == 1)
|
|
result = isbn(*argv, &b);
|
|
else
|
|
result = !argc; /* error if argc not 0 */
|
|
if(!result || b < 0) {
|
|
printf("?\n");
|
|
continue;
|
|
}
|
|
|
|
if( hi > 0 )
|
|
if( !(0 <= b && b < hi) )
|
|
{
|
|
printf("%s out of range (%lld not between 0 and %lld)\n", name,
|
|
b, hi);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
*_bn = b;
|
|
}
|
|
|
|
/*
|
|
* interactively get a menu choice (index). prompt using the given name.
|
|
* pass back the choice via _n. if the initial value of *_n is >=0, use
|
|
* it as the default. pop back if the response is '..'.
|
|
*/
|
|
void
|
|
getchoice(int *_n, MENU *m, const char *name, int numok)
|
|
{
|
|
char line[INBUFSIZE]; int argc; char **argv;
|
|
register int n;
|
|
int firsttime;
|
|
|
|
if( m->nitems <= 0 )
|
|
menu_digest(m);
|
|
|
|
for( firsttime = 1;; firsttime = 0 )
|
|
{
|
|
n = *_n;
|
|
if( !firsttime || n < 0 )
|
|
menu_print(m, numok);
|
|
prompt(": "); printf("%s = ", name);
|
|
if (n >= 0 && n < m->nitems)
|
|
printf("(%s) ", m->items[n].name);
|
|
(void)mkintr();
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
(void)setintr(0);
|
|
if( argc > 0 )
|
|
{
|
|
if( qcheck(*argv) )
|
|
continue;
|
|
if(argc > 1 || (n = match(m, *argv, numok)) < 0 ) {
|
|
if(argc > 1) printf("?\n");
|
|
/* else match printed a message */
|
|
continue;
|
|
}
|
|
}
|
|
if( !(0 <= n && n < m->nitems) )
|
|
{
|
|
printf("out of range\n");
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
*_n = n;
|
|
}
|
|
|
|
/*
|
|
* check an argument number string for legality. pass back the number
|
|
* via _n. if *_n >= 0, use it as the default. pop back if illegal.
|
|
*/
|
|
static void
|
|
checknum(char *str, uint64_t *_n, uint64_t hi, char *name)
|
|
{
|
|
uint64_t n;
|
|
|
|
n = *_n;
|
|
if(!*str || !iscnum(str, &n))
|
|
argerr("non-numeric %s", name);
|
|
if(hi && n >= hi)
|
|
argerr("%s out of range 0..%d", name, hi-1);
|
|
*_n = n;
|
|
}
|
|
|
|
/*
|
|
* check an argument block number string for legality. pass back the
|
|
* number via _n. if *_n >= 0, use it as the default. pop back if illegal.
|
|
*/
|
|
void
|
|
checkbn(char *str, daddr_t *_bn, daddr_t hi, char *name)
|
|
{
|
|
daddr_t b;
|
|
|
|
b = *_bn;
|
|
if( !(b >= 0 && *str == '\0') )
|
|
if( !isbn(str, &b) )
|
|
argerr("unrecognizable %s", name);
|
|
if( !(0 <= b && b < hi) )
|
|
argerr("%s out of range 0..%d", name, hi-1);
|
|
*_bn = b;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* get a number parameter from the arglist, or by prompting.
|
|
* this one really does take an int, not an int64.
|
|
*/
|
|
argnum(uint *(_n), uint dfl, uint hi, char *name)
|
|
{
|
|
uint64_t tmp;
|
|
int ret;
|
|
|
|
*_n = dfl;
|
|
tmp = (uint64_t)dfl;
|
|
if( noargs() ) {
|
|
ret = getnum(&tmp, (uint64_t)hi, name);
|
|
*_n = (uint)tmp;
|
|
return ret;
|
|
}
|
|
else {
|
|
checknum(getarg(), &tmp, (uint64_t)hi, name);
|
|
*_n = (uint)tmp;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* get a block number from the arglist, or by prompting.
|
|
*/
|
|
void
|
|
argbn(daddr_t *_bn, daddr_t dfl, daddr_t hi, char *name)
|
|
{
|
|
*_bn = dfl;
|
|
if( noargs() )
|
|
getbn(_bn, hi, name);
|
|
else
|
|
checkbn(getarg(), _bn, hi, name);
|
|
}
|
|
|
|
/*
|
|
* get a slice from the arglist, or by prompting.
|
|
* this also is really just int, not int64
|
|
*/
|
|
void
|
|
argslice(uint *_slice, uint dfl, uint hi, char *name)
|
|
{
|
|
uint64_t tmp;
|
|
*_slice = dfl;
|
|
tmp = (uint64_t)dfl;
|
|
if( noargs() )
|
|
getnum(&tmp, (uint64_t)hi, name);
|
|
else
|
|
checknum(getarg(), &tmp, (uint64_t)hi, name);
|
|
*_slice = (uint)tmp;
|
|
}
|
|
|
|
|
|
/*
|
|
* validate a choice. if invalid, report it and pop out of the current func
|
|
*/
|
|
static
|
|
checkchoice(char *s, int *_n, MENU *m)
|
|
{
|
|
int n;
|
|
|
|
n = *_n;
|
|
if( !(n >= 0 && *s == '\0') )
|
|
if( (n = match(m, s, 0)) < 0 )
|
|
mpop();
|
|
*_n = n;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* get a menu choice from the arglist, or by prompting.
|
|
*/
|
|
void
|
|
argchoice(int *_n, int dfl, MENU *m, const char *name)
|
|
{
|
|
*_n = dfl;
|
|
if( noargs() )
|
|
getchoice(_n, m, name, 0);
|
|
else
|
|
checkchoice(getarg(), _n, m);
|
|
}
|
|
|
|
/*
|
|
* get a yes-no answer, default given by yflag.
|
|
* return true if the answer is yes.
|
|
*/
|
|
yesno(int yflag, char *s)
|
|
{
|
|
char line[INBUFSIZE]; int argc; char **argv;
|
|
int y;
|
|
register int firsttime, ointr;
|
|
|
|
if(nomenus)
|
|
return 0; /* shouldn't be here normally; when we do get here,
|
|
* we don't want to take the default action */
|
|
|
|
for( firsttime = 1;; firsttime = 0 )
|
|
{
|
|
y = yflag;
|
|
if( !firsttime /* || yflag < 0 */ )
|
|
printf("please enter a yes or no\n");
|
|
printf("%s? ", s);
|
|
if( y >= 0 )
|
|
printf("(%s) ", y? "yes" : "no");
|
|
ointr = mkintr();
|
|
mgetargs(line, sizeof line, &argc, &argv);
|
|
(void)setintr(ointr);
|
|
|
|
if( argc > 0 )
|
|
{
|
|
if( qcheck(*argv) )
|
|
continue;
|
|
if( argc == 1 && wmatch("yes", *argv) )
|
|
{
|
|
y = 1;
|
|
break;
|
|
}
|
|
if( argc == 1 && wmatch("no", *argv) )
|
|
{
|
|
y = 0;
|
|
break;
|
|
}
|
|
printf("?\n");
|
|
continue;
|
|
}
|
|
if( y >= 0 )
|
|
break;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
/*
|
|
* get a yes-no answer, default yes.
|
|
* return true if the answer is yes.
|
|
*/
|
|
yes(char *s)
|
|
{
|
|
return yesno(1, s);
|
|
}
|
|
|
|
/*
|
|
* get a yes-no answer, default no.
|
|
* return true if the answer is no.
|
|
*/
|
|
no(char *s)
|
|
{
|
|
return !yesno(0, s);
|
|
}
|
|
|
|
/*
|
|
* get a string parameter from the arglist, or by prompting.
|
|
*/
|
|
void
|
|
argstring(char *str, char *dfl, char *name)
|
|
{
|
|
if( !noargs() )
|
|
checkstring(str, dfl);
|
|
else
|
|
getstring(str, dfl, name);
|
|
}
|
|
|
|
/*
|
|
* validate a string argument.
|
|
*/
|
|
void
|
|
checkstring(char *str, char *dfl)
|
|
{
|
|
strcpy(str, getarg());
|
|
if( *str == '\0' && dfl != 0 )
|
|
strcpy(str, dfl);
|
|
}
|
|
|
|
/*
|
|
* return true if the arglist is exhausted
|
|
*/
|
|
noargs(void)
|
|
{
|
|
return chead->argc <= 0;
|
|
}
|
|
|
|
/*
|
|
* ensure that the arglist is exhausted.
|
|
* if not, pop out of the current func
|
|
*/
|
|
void
|
|
argcheck(void)
|
|
{
|
|
if(!nomenus && !noargs())
|
|
argerr("arg count");
|
|
}
|
|
|
|
|
|
/*
|
|
* get the current arg from the arglist
|
|
*/
|
|
char *
|
|
getarg(void)
|
|
{
|
|
if( --chead->argc >= 0 )
|
|
return *chead->argv++;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* get flags from the arglist. cp is a list of flag chars, fp is a list
|
|
* of flag variables.
|
|
*/
|
|
void
|
|
checkflags(char *cp, char *fp)
|
|
{
|
|
register CR *crp;
|
|
register char *ap;
|
|
register int i;
|
|
char argbotch;
|
|
|
|
argbotch = 0;
|
|
crp = chead;
|
|
|
|
for( i = 0; cp[i] != '\0'; i++ )
|
|
fp[i] = 0;
|
|
|
|
while( crp->argc > 0 && *(ap = *crp->argv) == '-' )
|
|
{
|
|
crp->argc--; crp->argv++;
|
|
while(*++ap)
|
|
{
|
|
for( i = 0; cp[i] != '\0'; i++ )
|
|
if( cp[i] == *ap )
|
|
break;
|
|
if( cp[i] == '\0' )
|
|
{
|
|
errwarn("unknown flag %c", *ap);
|
|
argbotch++;
|
|
continue;
|
|
}
|
|
fp[i]++;
|
|
}
|
|
}
|
|
|
|
if( argbotch )
|
|
argerr("legal flags are: %s", cp);
|
|
}
|
|
|
|
|
|
/* ----- help routines ----- */
|
|
/*
|
|
* check for "quit" response; goto nearest quit label if there is a match.
|
|
* check for "help" response; return 1 to caller if there is a match.
|
|
*/
|
|
int
|
|
qcheck(char *s)
|
|
{
|
|
if( strcmp("..", s) == 0 )
|
|
mpop();
|
|
return strcmp("?", s) == 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* print a pair of help strings in our standard format
|
|
* if first string is empty, this is a continuation line,
|
|
* so don't print the -, it looks strange, particular
|
|
* for the -options help lines.
|
|
*/
|
|
static void
|
|
helpprint(char *a, char *b)
|
|
{
|
|
printf("%-20s %s %s\n", a, *a ? "-" : " ", b);
|
|
}
|
|
/*
|
|
* help for a (sub)menu. print help for each of its items.
|
|
*/
|
|
static void
|
|
subhelp(MENU *m, char *s)
|
|
{
|
|
register ITEM *t;
|
|
|
|
if( s != 0 )
|
|
setoff(s);
|
|
for( t = m->items; t->name != 0; t++ ){
|
|
if (t->expert && !expert) break;
|
|
helpitem(0, t);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* the help function. print long help for each named function,
|
|
* or if there are no named items, short help for each item in
|
|
* the current menu.
|
|
*/
|
|
void
|
|
help_func(void)
|
|
{
|
|
register ITEM *t;
|
|
register char *ap;
|
|
|
|
if( noargs() ) {
|
|
printf("On all menus, only the shortest unique prefix of the choice\n"
|
|
" needs to be typed. \"..\" returns to the level above, or\n"
|
|
" ends the entry of a list of items. Items ending in / have\n"
|
|
" further menus. ? \"somechoice\" gives help on that choice.\n\n");
|
|
subhelp(cmenu, 0);
|
|
return;
|
|
}
|
|
|
|
while( (ap = getarg()) != 0 )
|
|
{
|
|
if( (t = nameitem(ap)) != 0 )
|
|
{
|
|
helpitem(1, t);
|
|
continue;
|
|
}
|
|
printf("no help for %s\n", ap);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* print help for one item, which may or may not be a menu
|
|
*/
|
|
void
|
|
helpitem(int n, register ITEM *t)
|
|
{
|
|
if( MENUFIELD(t) != 0 )
|
|
menuhelp(n, t);
|
|
else
|
|
funchelp(n, t);
|
|
}
|
|
|
|
/*
|
|
* print help for one function item
|
|
*/
|
|
void
|
|
funchelp(int n, ITEM *t)
|
|
{
|
|
register char **i;
|
|
i = HELPFIELD(t);
|
|
helpprint(i[0], i[1]);
|
|
i += 2;
|
|
if( n )
|
|
while( *i != 0 )
|
|
helpprint("", *i++);
|
|
}
|
|
|
|
/*
|
|
* print help for one non-function item
|
|
*/
|
|
void
|
|
menuhelp(int n, ITEM *t)
|
|
{
|
|
funchelp(n, t);
|
|
if( n )
|
|
subhelp(MENUFIELD(t), t->name);
|
|
}
|