From decwrl!elroy.jpl.nasa.gov!usc!cs.utexas.edu!uunet!allbery Sun Feb 18 00:50:51 PST 1990 Article 1342 of comp.sources.misc: Path: decwrl!elroy.jpl.nasa.gov!usc!cs.utexas.edu!uunet!allbery From: peter@ficc.uu.net (Peter da Silva) Newsgroups: comp.sources.misc Subject: v10i071: Improvements to Parseargs Message-ID: <79119@uunet.UU.NET> Date: 17 Feb 90 02:38:39 GMT Sender: allbery@uunet.UU.NET Organization: Xenix Support, FICC Lines: 2928 Approved: allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) Posting-number: Volume 10, Issue 71 Submitted-by: peter@ficc.uu.net (Peter da Silva) Archive-name: parseargs You're missing some stuff I sent in. Some serious improvements to parseargs, for example. : #! /bin/sh # This is a shell archive, created at Ferranti International Controls Corp. # by peter (peter da silva, +1 713 274 5180) on Wed Feb 7 08:10:16 1990 # Remove anything before the "#! /bin/sh" line, then unpack it by saving # it into a file and typing "sh file". If you do not have sh, you need # unshar, a dearchiving program which is widely available. In the absolute # wost case, you can crack the files out by hand. # If the archive is complete, you will see the message "End of archive." # at the end. # This archive contains the following files... # 'README' # 'README.PDS' # 'Makefile' # 'funclist.h' # 'parseargs.h' # 'useful.h' # 'ckalloc.c' # 'argtype.c' # 'fp_argtype.c' # 'funclist.c' # 'openpath.c' # 'amiga_args.c' # 'unix_args.c' # 'syserr.c' # 'traceset.c' # 'stest.c' # 'ckalloc.3' # 'funclist.3' # 'openpath.3' # 'parseargs.3' # 'syserr.3' # 'trace.3' # To extract them, run the following through /bin/sh echo x - README sed 's/^X//' > README << '//END_OF_FILE' X X NIFTY UTILITY LIBRARY X X Eric P. Allman X University of California X Berkeley, California X eric@Berkeley.EDU X X XSUMMARY X This directory contains a subset of a utility library that I have X used (in various forms) for several years now. This particular X version is rather sparse, being a relatively recent reimplementation. X X I am making this available as a result of the rather surprising X response to my C Advisor column in UNIX Review Vol. 7 No. 11 on X argument parsing, in which I described an alternative to getopt. X Several dozen people have asked for the source code -- an amazing X number, considering that in the four years prior to this column, X I have gotten perhaps six letters in toto. X X Rather than limiting this distribution to the single argument X parsing module, I have added several other routines, many of which X my more faithful readers will recognize. X XCOPY/REUSE POLICY X Permission is hereby granted to freely copy and redistribute this X software, provided that the author is clearly credited in all X copies and derivations. Neither the name of the author nor that X of the University may be used to endorse or promote products X derived from this software without specific written permission. X This software is provided ``As Is'' and without any express or X implied warranties. X XCONTENTS X This directory contains: X README -- this file. X Makefile -- a makefile for the library. X useful.h -- a general header file, used by most everything. X ckalloc.c -- an interface to malloc(3) which diagnoses X out of memory conditions. This topic was discussed X in the C Advisor column in UNIX Review Vol. 7 No 7. X ckalloc.3 -- documentation for ckalloc. X funclist.h -- a header file specific to the function list X routines in funclist.c. X funclist.c -- routines that provide lists of ``callout'' X functions, used by several of the other routines X to handle errors. X funclist.3 -- documentation for the funclist routines. X lwp_dump.c -- a routine to dump the state of the LightWeight X Process system in SunOS 4.0 -- this isn't used, but X may be useful to some of you. X openpath.c -- a routine to open a file, searching through X a path of directories to find it. X openpath.3 -- documentation for openpath. X parseargs.h -- headers for the argument parser. X parseargs.c -- a command line argument parser. Popular X response to my C Advisor column about this routine X in UNIX Review Vol. 7 No. 11 prompted me to make X this distribution available. X parseargs.3 -- documentation for parseargs. X fp_args.c -- argument value parsers for floating point values; X used by parseargs. This is only included if you need X FP values. I had to break this out because RISC/os X 4.01 from Mips doesn't seem to support strtod in the X BSD environment. You may find you need to include X -lm for this to work. X syserr.c -- error message printing routines. A version of X this was discussed in the C Advisor column in UNIX X Review, Vol. 7 No. 7. X syserr.3 -- documentation for syserr. X traceset.c -- routines to set trace flags. The flags are X tested by macros contained in useful.h. X trace.3 -- documentation for the trace routines and macros. X stest.c -- a small test program for the argument parser. X X The parseargs routine really ought to have a way of matching a X list (e.g., return the rest of argv). This isn't especially X hard to do, but I haven't gotten around to it yet. X XDISCLAIMERS X I hacked this code up to (hopefully) work on ANSI C compilers, X since several readers seem to be interested in this sort of thing. X I can't claim to have really tested this. X X The original version was tested under SunOS 4.0 on SPARC architectures. X The version you see has been loosely tested on a Mips M/2000 running X RISC/os 4.01; I have only tried it in the BSD environment, and that X only loosely. X XACKNOWLEDGEMENTS X I wrote the first version of this code while working at the X International Computer Science Institute in Berkeley, CA. X X$Header: README,v 2.1 89/12/30 20:59:14 eric Exp $ //END_OF_FILE echo x - README.PDS sed 's/^X//' > README.PDS << '//END_OF_FILE' XUpdate to parseargs by Peter da Silva (peter@ficc.uu.net). X(second update: more improvements to arg parsing, argChar type) X(third update, return to original calling sequence, argList type) X XParseargs is a really nifty set of routines, but it doesn't fit too Xwell with standard UNIX semantics. In particular, you can get into a Xlot of trouble using it in a script if it drops into interactive mode Xon you. Also, it's not as useful as it could be for non-UNIX systems. XTo make it work better, I've made a couple of changes. X XIt compiled straight out of the box on System III once I'd provided Xbcopy, bcmp, and strtol. The strtol I've provided is almost totally Xuntested, but hopefully you won't need to use it. It's only for folks with Xold UNIX systems. X XFirst change was to disable the interactive prompting for arguments. XI think that's inconsistent with usual UNIX semantics. You can undo Xthis change by #defining INTERACTIVE when compiling parseargs.c. X XThe second change was to allow for a trailing list of arguments. I Xoriginally implemented this by changing the calling sequence to parseargs. XOn reflection this would just produce incompatibilities, so I added a Xnew "type", argList. This handles a pointer to a list of names: X Xstruct namelist { X struct namelist *nl_next; X char *nl_name; X}; X XThis is implemented with some "magic" in parseargs, and perhaps it would Xdo better as an additional flag, ARGLIST, and a set of listType routines Xto handle them. Also, it currently keeps the list in LIFO order. It would Xbe more convenient in FIFO order, though this would take a little more Xwork to implement (basically just stepping through the list in argList to Xfind the tail, instead of inserting at the head), and can be fixed in a Xsingle pass after parseargs returns with (say) !argv = reverselist(argv)!: X Xstruct namelist *reverselist(from) Xstruct namelist *from; X{ X struct namelist *to, *tmp; X X to = NULL; X while(from) { X tmp = from->nl_next; /* remove top from old list */ X from = from->nl_next; X tmp->nl_next = to; /* insert top in new list */ X to = tmp; X } X return to; X} X XThe final change is the addition of a 'argChar' type. This parses character Xarguments (such as the '-T' option to 'awk'), accepting single characters, X'\nnn' octal escapes, and '^X' for control characters. X XParseargs itself no longer uses ckalloc, traceset, funclist, and so on. Xthese routines are pretty cool, but when you're grafting parseargs onto Xan existing program they just get in the way. Also, it's possible to make Xparseargs fail in a cleaner fashion by handling out-of-memory cases myself. XCertainly there's not going to be any loose memory lying around to be Xcollected when parseargs starts up! To turn on TRACE and the funclist code Xjust add -DTRACESTUFF and -DFUNCLISTSTUFF to the Makefile. X XAlso, the error messages have been made a bit more descriptive. Instead Xof saying "stest: value required for -c flag", it prints "stest: RepCount Xrequired for -c flag". The ad_prompt element should relly be a descriptive Xword that can be used in a sentence... or for non_UNIX systems a multi- Xcharacter or keyword based flag. I have an Amiga version included, for Xexample, that uses keyword syntax. In that version, the usage message reads: X XUsage: amiga_test [GROUP ]... [REP ] + X [DIR ] [X] [Y] [Z] [TAB ] []... X XInstead of: X XUsage: unix_test [-n ]... [-c ] \ X [-d ] [-x] [-y] [-z] [-t ] []... X XThis would solve the old problem of UNIX programs sticking out like a Xsore thumb in other operating systems. X XThe Amiga version still needs to prompt for options if called with a Xsingle '?' as an argument. This may require some redesign: it's almost Xcertainly not going to be possible to stuff *extra* file names into Xargv. Perhaps something like: X X parseargs(&argc, &argv, "File"); X X???????? //END_OF_FILE echo x - Makefile sed 's/^X//' > Makefile << '//END_OF_FILE' X# $Header: Makefile,v 2.1 89/12/30 20:59:01 eric Exp $ X XTARGET= /usr XLIBDIR= /lib XINCDIR= ${TARGET}/include X XINCLUDES= -I. XMODEL= -Ms XMODELNAME= S XSTYLE= unix XO= -O XOPTIONS= # -DINTERACTIVE -DTRACESTUFF -DFUNCLISTSTUFF X XCFLAGS= ${MODEL} ${INCLUDES} $O ${OPTIONS} X XOFILES= ckalloc.o \ X argtype.o \ X fp_argtype.o \ X funclist.o \ X openpath.o \ X ${STYLE}_args.o \ X syserr.o \ X traceset.o \ X strtol.o X XHFILES= funclist.h \ X parseargs.h \ X useful.h X XCFILES= ckalloc.c \ X argtype.c \ X fp_argtype.c \ X funclist.c \ X openpath.c \ X amiga_args.c \ X unix_args.c \ X syserr.c \ X traceset.c \ X stest.c XMANFILES= ckalloc.3 \ X funclist.3 \ X openpath.3 \ X parseargs.3 \ X syserr.3 \ X trace.3 XXXFILES= README \ X README.PDS \ X Makefile X XOBJS= ${OFILES} XSRCFILES= ${XXFILES} ${HFILES} ${CFILES} ${MANFILES} XLIBARGS= ${MODELNAME}lib${STYLE}_args.a XLIBFILE= ${MODELNAME}libparse.a XLOCLIBS= ${LIBARGS} XSYSLIBS= -lm XLIBS= ${LOCLIBS} ${SYSLIBS} XSHAR= shar X X${STYLE}_test: stest.o ${LOCLIBS} X ${CC} ${CFLAGS} -o ${STYLE}_test stest.o ${LIBS} X Xckalloc.o funclist.o syserr.o: funclist.h X X${STYLE}_args.o: parseargs.h X X${LIBARGS}: ${OBJS} X ar rvu $@ ${OBJS} X ranlib $@ X Xinstall: ${INCDIR}/parseargs.h ${LIBDIR}/${LIBFILE} X X${INCDIR}/parseargs.h: parseargs.h X (cd ${INCDIR}; rm -f ${HFILES}) X cp ${HFILES} ${INCDIR} X X${LIBDIR}/${LIBFILE}: ${LIBARGS} X rm -f ${LIBDIR}/${LIBFILE} X cp ${LIBARGS} ${LIBDIR}/${LIBFILE} X ranlib ${LIBDIR}/${LIBFILE} X Xshar: parseargs.shar X Xparseargs.shar: ${SRCFILES} X rm -f parseargs.shar X ${SHAR} ${SRCFILES} > parseargs.shar X Xclean: X rm -f ${OFILES} ${LIBARGS} ${STYLE}_test tags stest.o X Xtags: ${CFILES} ${HFILES} X ctags ${CFILES} ${HFILES} //END_OF_FILE echo x - funclist.h sed 's/^X//' > funclist.h << '//END_OF_FILE' X/* X** FUNCLIST.H -- headers for function list routines X** X** $Header: funclist.h,v 2.0 89/12/24 00:56:23 eric Exp $ X** X** Each entry in this list contains a pointer to a function to be X** invoked, and an integer argument to be passed to it. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#ifndef FUNCLIST X X#ifndef _USEFUL_H_ X#include X#endif X X#define FUNCLIST struct _funclist X XFUNCLIST X{ X FUNCLIST *_fl_next; /* next in chain */ X VOID (*_fl_func)(); /* function to invoke */ X ARBPTR _fl_arg; /* first argument */ X}; X X#define FUNCLISTNULL ((FUNCLIST *) NULL) X X#endif //END_OF_FILE echo x - parseargs.h sed 's/^X//' > parseargs.h << '//END_OF_FILE' X/* X** PARSEARGS.H -- declarations for argument vector parser X** X** $Header: parseargs.h,v 2.0 89/12/24 00:56:29 eric Exp $ X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#ifndef ARGDESC X X#ifndef _USEFUL_H_ X#include X#endif X X#define ARGDESC struct _argdesc X XARGDESC X{ X char ad_name; /* flag name */ X char ad_flags; /* flags */ X BOOL (*ad_type) ARGS((ARGDESC *, char *, BOOL)); X /* function to parse value */ X ARBPTR ad_valp; /* pointer to value storage area */ X char *ad_prompt; /* prompt string */ X}; X X/* bits for ad_flags */ X#define ARGREQ 0x01 /* required argument */ X#define ARGOPT 0x00 /* optional argument pseudo-flag */ X#define ARGHIDDEN 0x02 /* don't display in usage message */ X#define ARGGIVEN 0x08 /* (internal) argument has been specified */ X X/* types available for ad_type */ Xextern BOOL argBool ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argChar ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argStr ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argInt ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argShort ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argLong ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argFloat ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argDouble ARGS((ARGDESC *, char *, BOOL)); Xextern BOOL argList ARGS((ARGDESC *, char *, BOOL)); X Xstruct namelist { X struct namelist *nl_next; X char *nl_name; X}; X X#define ENDOFARGS { '\0' } X X#endif //END_OF_FILE echo x - useful.h sed 's/^X//' > useful.h << '//END_OF_FILE' X/* X** USEFUL.H -- various definitions of general interest X** X** $Header: useful.h,v 2.0 89/12/24 00:56:33 eric Exp $ X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#ifndef _USEFUL_H_ X#define _USEFUL_H_ X X#include X X/* give a stab at the multiple-language dilemma */ X#ifdef __STDC__ X#define ARGS(x) x X#define NOARGS (void) X#define __ANSI_C__ X#else X#if defined(c_plusplus) || defined(__cplusplus) X#define ARGS(x) x X#define NOARGS () X#define __ANSI_C__ X#else X#define ARGS(x) () X#define NOARGS () X#endif X#endif X X#ifndef VOID X#ifdef __ANSI_C__ X#define VOID void X#else X#define VOID int X#endif X#endif X X#ifndef TRUE X#define TRUE 1 X#define FALSE 0 X#endif X X#ifndef BOOL X#define BOOL char X#endif X X#ifndef STATIC X#ifndef NODEBUG X#define STATIC X#else X#define STATIC static X#endif X#endif X X#ifndef EXTERN X#define EXTERN extern X#endif X X#ifndef CONST X#ifdef __ANSI_C__ X#define CONST const X#else X#define CONST X#endif X#endif X X#ifndef NULL X#define NULL 0 X#endif X X#ifndef CHARNULL X#define CHARNULL ((char *) NULL) X#endif X X#define FILENULL ((FILE *) NULL) X X#ifdef __ANSI_C__ X#define ARBPTR void * X#else X#define ARBPTR char * X#endif X#define __ (ARBPTR) X#define ARBNULL (__ NULL) X X#ifndef TRACESTUFF X#define NODEBUG X#endif X X#ifndef NODEBUG X#define _TRACE_SIZE 200 Xextern unsigned char _TraceVect[_TRACE_SIZE]; X#define TRACEF(f, l) (_TraceVect[f] >= l) X#define TRACE(f, l, m) (TRACEF(f, l) ? printf m : 0) X#else X#define TRACEF(f, l) (FALSE) X#define TRACE(f, l, m) X#endif X X#ifdef lint X#define VERSIONID(v) X#else X#define VERSIONID(v) static char _Version[] = v; X#endif X X#define BITSET(b, w) (((b) & (w)) != 0) X X#ifdef __STDC__ X#include X#else Xextern char *strchr ARGS((char *, char)); Xextern char *strrchr ARGS((char *, char)); X#endif X Xextern ARBPTR ckalloc ARGS((unsigned int)); X X#define MAXINPUTLINE 200 /* maximum string input line */ X#define MAXWORDLEN 100 /* maximum word (token) length */ X X#ifndef BSD X#define bcopy(f,t,l) memcpy(t,f,l) X#define bcmp(s,t,l) memcmp(s,t,l) X#endif X X#endif /* _USEFUL_H_ */ //END_OF_FILE echo x - ckalloc.c sed 's/^X//' > ckalloc.c << '//END_OF_FILE' X#include X#include X XVERSIONID("$Header: ckalloc.c,v 2.0 89/12/24 00:56:21 eric Exp $"); X X/* X** CKALLOC -- allocate memory, checking for error conditions X** X** If we cannot allocate memory, we call the list of functions X** in OutOfMemoryFuncs. We then try again. If the second attempt X** fails, we terminate the process. X** X** Parameters: X** size -- the number of bytes of memory to be allocated. X** X** Returns: X** A pointer to the allocated memory. X** Never returns if memory cannot be allocated. X** X** Side Effects: X** As implied by memory allocation. X** X** Globals: X** OutOfMemoryFuncs -- a FUNCLIST that is called when memory X** cannot be allocated. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X XFUNCLIST *OutOfMemoryFuncs = FUNCLISTNULL; X XARBPTR Xckalloc(size) X unsigned int size; X{ X ARBPTR p; X extern char *malloc ARGS((unsigned int)); X X p = __ malloc(size); X if (p == ARBNULL) X { X funclist_call(OutOfMemoryFuncs, ARBNULL); X p = __ malloc(size); X if (p == ARBNULL) X syserr("ckalloc: out of memory"); X } X return (p); X} //END_OF_FILE echo x - argtype.c sed 's/^X//' > argtype.c << '//END_OF_FILE' X#include X#include X#include X X#ifdef __STDC__ Xtypedef void *pointer; X#else Xtypedef char *pointer; X#endif X Xextern pointer malloc(); X X#define ALL_AD ad = argd; ad->ad_name != '\0'; ad++ X#define ALL_DEFS ad = _DefaultArgs; ad->ad_name != '\0'; ad++ X Xextern char *ProgName; X X/* X** ARGtype -- argument translation routines. X** X** Each of these converts a parameter value to the internal form, X** including validity checking. Their parameters and return values X** all behave identically. X** X** Parameters: X** ad -- the argument descriptor for this parameter. X** vp -- a pointer to the string input value. X** copyf -- if TRUE, the value will be destroyed later, X** and so should be copied if it will be retained X** (as for a string). X** X** Returns: X** TRUE -- if the conversion was successful. The actual X** value should be stored in the location indicated X** by ad->ad_valp. X** FALSE -- if the conversion failed. The reason for failure X** should be diagnosed using usrerr(). X** X** Side Effects: X** The value should be stored through ad->ad_valp. X*/ X XBOOL XargStr(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X char *cp; X X if (copyf) X { X register int i; X X i = strlen(vp) + 1; X cp = (char *) malloc(i); X if(!cp) { X usrerr("out of memory parsing %s", ad->ad_prompt); X return FALSE; X } X bcopy(vp, cp, i); X } X else X { X cp = vp; X } X *(char **) ad->ad_valp = cp; X return (TRUE); X} X XBOOL XargChar(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern long strtol ARGS((char *, char **, int)); X int status = FALSE; X char c; X X if (strlen(vp) == 2 && vp[0]=='^') X { X c = vp[1] ^ '@'; X status = TRUE; X } X else if(strlen(vp) > 1 && vp[0]=='\\') X { X c = (int) strtol(&vp[1], &vpp, 8); X if (*vpp == '\0') X status = TRUE; X } X else if(strlen(vp) == 1) X { X c = *vp; X status = TRUE; X } X X if(status == TRUE) X *(char *) ad->ad_valp = c; X else X { X usrerr("invalid character argument '%s' for %s", X vp, ad->ad_prompt); X } X return status; X} X X/*ARGSUSED*/ XBOOL XargInt(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern long strtol ARGS((char *, char **, int)); X X *(int *) ad->ad_valp = (int) strtol(vp, &vpp, 0); X if (*vpp != '\0') X { X usrerr("invalid integer argument '%s' for %s", X vp, ad->ad_prompt); X return (FALSE); X } X else X { X return (TRUE); X } X} X X/*ARGSUSED*/ XBOOL XargShort(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern long strtol ARGS((char *, char **, int)); X X *(short *) ad->ad_valp = (short) strtol(vp, &vpp, 0); X if (*vpp != '\0') X { X usrerr("invalid integer argument '%s' for %s", X vp, ad->ad_prompt); X return (FALSE); X } X else X { X return (TRUE); X } X} X X/*ARGSUSED*/ XBOOL XargLong(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern long strtol ARGS((char *, char **, int)); X X *(long *) ad->ad_valp = strtol(vp, &vpp, 0); X if (*vpp != '\0') X { X usrerr("invalid integer argument '%s' for %s", X vp, ad->ad_prompt); X return (FALSE); X } X else X { X return (TRUE); X } X} X Xstruct booltab X{ X char *bname; /* string to match against */ X char bneedmatch; /* number of characters that must match */ X BOOL bval; /* value to use */ X}; X XSTATIC struct booltab _BoolTab[] = X{ X "yes", 1, TRUE, X "no", 1, FALSE, X "true", 1, TRUE, X "false", 1, FALSE, X "on", 2, TRUE, X "off", 3, FALSE, X CHARNULL X}; X X/*ARGSUSED*/ XBOOL XargBool(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X register struct booltab *b; X register char *cp; X int l; X X /* convert input to lower case */ X for (l = 0, cp = vp; *cp != '\0'; l++, cp++) X { X if (isupper(*cp)) X *cp = tolower(*cp); X } X X /* search for a match in the table */ X for (b = _BoolTab; b->bname != CHARNULL; b++) X { X /* if too short, don't even bother trying */ X if (l < b->bneedmatch) X continue; X X if (bcmp(vp, b->bname, l) == 0) X { X /* got a match */ X *(BOOL *) ad->ad_valp = b->bval; X return (TRUE); X } X } X X usrerr("invalid Boolean argument '%s' for %s", vp, ad->ad_prompt); X return (FALSE); X} X X#ifdef TRACESTUFF X/*ARGSUSED*/ XBOOL XargTrace(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X traceset(vp); X return (TRUE); X} X#endif X X/*ARGSUSED*/ XBOOL XargEnd(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X return (FALSE); X} X XBOOL XargList(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X char *cp; X struct namelist *nl; X X if (copyf) X { X register int i; X X i = strlen(vp) + 1; X cp = (char *) malloc(i); X if(!cp) { X usrerr("out of memory saving string %s", ad->ad_prompt); X return FALSE; X } X bcopy(vp, cp, i); X } X else X { X cp = vp; X } X X nl = (struct namelist *) malloc(sizeof *nl); X if(!nl) { X usrerr("out of memory saving arg %s", ad->ad_prompt); X if(copyf) free(cp); X return FALSE; X } X X nl->nl_next = *(struct namelist **) ad->ad_valp; X nl->nl_name = cp; X *(struct namelist **) ad->ad_valp = nl; X return (TRUE); X} //END_OF_FILE echo x - fp_argtype.c sed 's/^X//' > fp_argtype.c << '//END_OF_FILE' X#include X#include X XVERSIONID("$Header: fp_args.c,v 2.0 89/12/24 00:56:21 eric Exp $"); X X/* X** PARSEARGV argument type functions for floating point operands. X** X** These are broken out to avoid loading the floating precision X** conversion routines when they aren't actually needed. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X/*ARGSUSED*/ XBOOL XargDouble(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern double strtod ARGS((char *, char **)); X X *(double *) ad->ad_valp = strtod(vp, &vpp); X if (*vpp != '\0') X { X usrerr("invalid floating point argument '%s' for %s", X vp, ad->ad_prompt); X return (FALSE); X } X else X { X return (TRUE); X } X} X X/*ARGSUSED*/ XBOOL XargFloat(ad, vp, copyf) X register ARGDESC *ad; X register char *vp; X BOOL copyf; X{ X auto char *vpp; X extern double strtod ARGS((char *, char **)); X X *(float *) ad->ad_valp = (float) strtod(vp, &vpp); X if (*vpp != '\0') X { X usrerr("invalid floating point argument '%s' for %s", X vp, ad->ad_prompt); X return (FALSE); X } X else X { X return (TRUE); X } X} //END_OF_FILE echo x - funclist.c sed 's/^X//' > funclist.c << '//END_OF_FILE' X#include X#include X XVERSIONID("$Header: funclist.c,v 2.0 89/12/24 00:56:22 eric Exp $"); X X/* X** FUNCLIST_ADD -- add a function specification to a function list X** X** Parameters: X** flh -- the address of a pointer to a function list. Note X** that this must be a FUNCLIST **. X** func -- the function to add to the list. X** arg -- the first argument to the function. X** X** Returns: X** none. X** X** Side Effects: X** The indicated function is added to the function list X** for later invocation by funclist_call. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X XVOID Xfunclist_add(flh, func, arg) X FUNCLIST **flh; X VOID (*func)(); X ARBPTR arg; X{ X register FUNCLIST *fl; X X fl = (FUNCLIST *) ckalloc(sizeof *fl); X fl->_fl_func = func; X fl->_fl_arg = arg; X fl->_fl_next = *flh; X *flh = fl; X} X /* X** FUNCLIST_CALL -- call all the functions on a function list. X** X** Parameters: X** fl -- the function list to call. Unlike funclist_add, X** this should not be an indirect pointer. X** arg -- the second argument to the functions. X** X** Returns: X** none. X** X** Side Effects: X** As implied by the function list. X*/ X XVOID Xfunclist_call(fl, arg) X register FUNCLIST *fl; X ARBPTR arg; X{ X while (fl != FUNCLISTNULL) X { X (*fl->_fl_func)(fl->_fl_arg, arg); X fl = fl->_fl_next; X } X} //END_OF_FILE echo x - openpath.c sed 's/^X//' > openpath.c << '//END_OF_FILE' X#include X XVERSIONID("$Header: openpath.c,v 2.0 89/12/24 00:56:26 eric Exp $"); X X/* X** OPENPATH -- open a file, searching through a path X** X** Parameters: X** fname -- the file name, relative to the desired path. X** mode -- the open mode, just like fopen. X** path -- the search path. X** X** Returns: X** FILENULL if the file could not be found at all. X** A FILE pointer open for reading for the first instance X** of the file that could be found in the path X** otherwise. X** X** Side Effects: X** none. X** X** Notes: X** Should really just find the file, not open it. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X Xchar *DefaultPath = CHARNULL; X X#ifndef DEFAULTROOTPATH X#define DEFAULTROOTPATH "/usr/local:/usr:~:" X#endif X XFILE * Xopenpath(fname, mode, path) X char *fname; X char *mode; X char *path; X{ X register char *dp; X register char *ep; X register char *bp; X FILE *fp; X char fbuf[200]; X extern char *getenv ARGS((char *)); X X dp = path; X if (dp == CHARNULL) X { X dp = DefaultPath; X if (dp == CHARNULL) X { X /* locate the root of our utility tree */ X dp = getenv("ROOTPATH"); X if (dp == CHARNULL) X dp = DEFAULTROOTPATH; X DefaultPath = dp; X } X } X for (;; dp = ++ep) X { X register int l; X int i; X int fspace; X X /* extract a component */ X ep = strchr(dp, ':'); X if (ep == CHARNULL) X ep = &dp[strlen(dp)]; X X /* find the length of that component */ X l = ep - dp; X bp = fbuf; X fspace = sizeof fbuf - 2; X if (l > 0) X { X /* X ** If the length of the component is zero length, X ** start from the current directory. If the X ** component begins with "~", start from the X ** user's $HOME environment variable. Otherwise X ** take the path literally. X */ X X if (*dp == '~' && (l == 1 || dp[1] == '/')) X { X char *home; X X home = getenv("HOME"); X if (home != CHARNULL) X { X i = strlen(home); X if ((fspace -= i) < 0) X goto toolong; X bcopy(home, bp, i); X bp += i; X } X dp++; X l--; X } X if (l > 0) X { X if ((fspace -= l) < 0) X goto toolong; X bcopy(dp, bp, l); X bp += l; X } X X /* add a "/" between directory and filename */ X if (ep[-1] != '/') X *bp++ = '/'; X } X X /* now append the file name */ X i = strlen(fname); X if ((fspace -= i) < 0) X { X toolong: X fprintf(stderr, "openpath: pathname too long (ignored)\n"); X *bp = '\0'; X fprintf(stderr, "\tDirectory \"%s\"\n", fbuf); X fprintf(stderr, "\tFile \"%s\"\n", fname); X continue; X } X bcopy(fname, bp, i + 1); X X /* try to open that file */ X fp = fopen(fbuf, mode); X X /* if it exists, all is well */ X if (fp != FILENULL) X return (fp); X X /* if not, and no other alternatives, life is bleak */ X if (*ep == '\0') X return (FILENULL); X X /* otherwise try the next component in the search path */ X } X} //END_OF_FILE echo x - amiga_args.c sed 's/^X//' > amiga_args.c << '//END_OF_FILE' X#include X#include X#include X XVERSIONID("$Header: parseargs.c,v 2.1 89/12/30 20:59:48 eric Exp $"); X X/* X** PARSEARGS -- parse an argument vector, given a description X** X** Parameters: X** argv -- the argument vector as passed to main(). X** argd -- the argument descriptor array. X** X** Returns: X** Nothing X** Exits with return code 20 if error in args. X** Exits with return code 10 if system error. X** X** Side Effects: X** Converts and stores arguments into variables as X** described by argd. X** X** Globals: X** DefaultPath -- the pathname of a set of places to X** look for system files, set from the ROOTPATH X** environment variable, or a default. X** ProgName -- the name of this program, saved for error X** messages and the like. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#define ALL_AD ad = argd; ad->ad_name != '\0'; ad++ X#define ALL_DEFS ad = _DefaultArgs; ad->ad_name != '\0'; ad++ X Xchar *ProgName; X X#ifdef TRACESTUFF Xextern BOOL argTrace ARGS((ARGDESC *, char *, BOOL)); X#endif Xextern BOOL argEnd ARGS((ARGDESC *, char *, BOOL)); X X/* default arguments -- apply to all programs */ XSTATIC ARGDESC _DefaultArgs[] = X{ X /* name flags type valp prompt */ X#ifdef TRACESTUFF X 'T', ARGOPT, argTrace, ARBNULL, "TRACE", X#endif X '-', ARGOPT, argEnd, ARBNULL, "ARGS", X ENDOFARGS X}; X X/* override argument descriptor, if none given by user */ XSTATIC ARGDESC _NullArgDesc[] = X{ X ENDOFARGS X}; X XVOID Xparseargs(argv, argd) X char **argv; X ARGDESC argd[]; X{ X register ARGDESC *ad, *list; X register char **av; X register char *p; X BOOL noflags; X BOOL error; X extern char *getenv ARGS((char *)); X X av = argv++; X /* save the name of this program (for error messages) */ X ProgName = *av; X X /* allow null argument descriptor */ X if (argd == (ARGDESC *) NULL) X argd = _NullArgDesc; X X /* clear out any cruft in the argument descriptor */ X for (ALL_AD) X { X ad->ad_flags &= ~ARGGIVEN; X } X for (ALL_DEFS) X { X ad->ad_flags &= ~ARGGIVEN; X } X X /* run through the argument vector */ X noflags = FALSE; X error = FALSE; X ad = NULL; /* No argument requested */ X list = NULL; X while (*++av != CHARNULL) X { X /* Previous keyword required a value */ X if(ad) X { X /* try to convert the type */ X if (!(*ad->ad_type)(ad, *av, FALSE)) X error = TRUE; X else X ad->ad_flags |= ARGGIVEN; X ad = NULL; X continue; X } X X /* If looking for keywords, see if this is one */ X if(!noflags) { X for(ALL_AD) X if(match(*av, ad->ad_prompt) == 0) X break; X if(ad->ad_name == '\0') X for(ALL_DEFS) X if(match(*av, ad->ad_prompt) == 0) X break; X } X if(ad->ad_name == '\0') X ad = NULL; X X /* If we have a keyword here */ X if(!noflags && ad) X { X list = NULL; X p = strchr(*av, '='); X if(p) /* matched NAME=VALUE */ X { X p++; X /* try to convert the type */ X if (!(*ad->ad_type)(ad, p, FALSE)) X error = TRUE; X else X ad->ad_flags |= ARGGIVEN; X ad = NULL; X } X else X { X if (ad->ad_type == argBool) X { X *(BOOL *) ad->ad_valp = TRUE; X ad->ad_flags |= ARGGIVEN; X ad = NULL; X } X else if (ad->ad_type == argEnd) X { X noflags = TRUE; X ad->ad_flags |= ARGGIVEN; X ad = NULL; X } X else if (ad->ad_type == argList) X { X list = ad; X ad = NULL; X } X } X } X else /* it's a positional argument */ X { X if(list) { X if (!argList(list, *av, FALSE)) X error = TRUE; X list->ad_flags |= ARGGIVEN; X continue; X } X else X { X for (ALL_AD) X { X if (ad->ad_name == ' ' && X (ad->ad_type == argList || X !BITSET(ARGGIVEN, ad->ad_flags)) X ) X break; X } X if (ad->ad_name == '\0') X { X usrerr("too any arguments"); X error = TRUE; X ad = NULL; X } X else X { X /* try to convert */ X if (!(*ad->ad_type)(ad, *av, FALSE)) X error = TRUE; X else X ad->ad_flags |= ARGGIVEN; X ad = NULL; X } X } X } X } X X /* If last argument was a keyword and required an option X * then complain about it X */ X if(ad) { X usrerr("missing argument for %s", ad->ad_prompt); X error = TRUE; X } X *argv = NULL; X X /* now rescan for missing required arguments */ X for (ALL_AD) X { X if (BITSET(ARGREQ, ad->ad_flags) && !BITSET(ARGGIVEN, ad->ad_flags)) X { X if (!BITSET(ARGGIVEN, ad->ad_flags)) X { X /* still didn't get a value... sigh */ X if (ad->ad_name == ' ') X { X usrerr("%s required", X ad->ad_prompt); X } X else X { X usrerr("%s required for -%c flag", X ad->ad_prompt, ad->ad_name); X } X error = TRUE; X } X } X } X X if (error) X { X usage(argd); X exit(20); X } X} X /* X** USAGE -- print a usage message X** X** Parameters: X** argd -- the description of expected arguments. X** X** Returns: X** none X** X** Side Effects: X** prints on stderr X** X** Globals: X** MaxOutputLine -- the length of the maximum output line X** allowed before wrapping. This should be fetched X** from the terminal driver on systems that support X** this sort of thing. X*/ X Xint MaxOutputLine = 72; X XVOID Xusage(argd) X ARGDESC *argd; X{ X register ARGDESC *ad; X int ll; X int pl; X X fprintf(stderr, "Usage: %s", ProgName); X ll = strlen(ProgName) + 7; X X for (ALL_AD) X { X char keyword[BUFSIZ]; X char name[BUFSIZ]; X int i, j; X X j = 0; X for(i = 0; ad->ad_prompt[i]; i++) { X if(isupper(ad->ad_prompt[i])) { X keyword[j++] = ad->ad_prompt[i]; X name[i] = tolower(ad->ad_prompt[i]); X } X else X name[i] = ad->ad_prompt[i]; X } X name[i] = 0; X if(j > 0) X keyword[j] = 0; X else X strcpy(keyword, ad->ad_prompt); X X /* don't display hidden arguments */ X if (BITSET(ARGHIDDEN, ad->ad_flags)) X continue; X X /* figure out how wide this parameter is (for printing) */ X if (ad->ad_name != ' ') X { X pl = strlen(keyword); X if (ad->ad_type != argBool) X pl += strlen(name) + 3;/* _< > */ X } X else X { X pl = strlen(name) + 2; /* < > */ X } X if (!BITSET(ARGREQ, ad->ad_flags)) X pl += 2; /* [ ] */ X if (ad->ad_type == argList) X pl += 3; /* ... */ X pl += 1; /* leading sp */ X X /* see if this will fit */ X if (ll + pl > MaxOutputLine) X { X /* no... start a new line */ X fprintf(stderr, " +\n\t"); X ll = 7; X } X else X { X /* yes... just throw in a space */ X fprintf(stderr, " "); X } X ll += pl; X X /* show the argument */ X if (!BITSET(ARGREQ, ad->ad_flags)) X fprintf(stderr, "["); X if (ad->ad_name != ' ') X { X fprintf(stderr, "%s", keyword); X if (ad->ad_type != argBool) X fprintf(stderr, " "); X } X if (ad->ad_name == ' ' || ad->ad_type != argBool) X fprintf(stderr, "<%s>", name); X if (!BITSET(ARGREQ, ad->ad_flags)) X fprintf(stderr, "]"); X if (ad->ad_type == argList) X fprintf(stderr, "..."); X } X fprintf(stderr, "\n"); X} X X /* match(s1, s2) X** X** Compares two strings, returning >0, <0, or =0 if they match. First a X** check is done on letters capitalised in the second word, and if this X** fails then a complete match is done. Case is ignored in both matches. X** This lets you use case to indicate what part of a keyword is significant. X*/ Xint match(candidate, target) Xchar *target, *candidate; X{ X int i, j; X char c; X X i = j = 0; X X while(target[i] || candidate[i]) { X while(islower(target[i])) i++; X if(!target[i]) { X if(!candidate[j]) return 0; X return dictcmp(target, candidate); X } X c = islower(candidate[j]) X ? toupper(candidate[j]) X : candidate[j]; X if(target[i] != c) return dictcmp(target, candidate); X i++; X j++; X } X return 0; X} X Xint dictcmp(s1, s2) /* "Dictionary" comparison of two strings */ Xchar *s1, *s2; X{ X char c1, c2; X X while(*s1 || *s2) { X c1 = *s1++; X c2 = *s2++; X if(!c1 || !c2) return c1 - c2; X if(isupper(c1)) c1 = tolower(c1); X if(isupper(c2)) c2 = tolower(c2); X if(c1 != c2) return c1 - c2; X } X return 0; X} //END_OF_FILE echo x - unix_args.c sed 's/^X//' > unix_args.c << '//END_OF_FILE' X#include X#include X#include X XVERSIONID("$Header: parseargs.c,v 2.1 89/12/30 20:59:48 eric Exp $"); X X/* X** PARSEARGS -- parse an argument vector, given a description X** X** Parameters: X** argv -- pointer to the argument vector as passed to main(). X** argd -- the argument descriptor array. X** X** Returns: X** Nothing. X** Exits with return code 2 if error in args. X** Exits with return code 1 if system error. X** X** Side Effects: X** Converts and stores arguments into variables as X** described by argd. X** X** Globals: X** DefaultPath -- the pathname of a set of places to X** look for system files, set from the ROOTPATH X** environment variable, or a default. X** ProgName -- the name of this program, saved for error X** messages and the like. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#define ALL_AD ad = argd; ad->ad_name != '\0'; ad++ X#define ALL_DEFS ad = _DefaultArgs; ad->ad_name != '\0'; ad++ X Xchar *ProgName; X X#ifdef TRACESTUFF Xextern BOOL argTrace ARGS((ARGDESC *, char *, BOOL)); X#endif Xextern BOOL argEnd ARGS((ARGDESC *, char *, BOOL)); X X/* default arguments -- apply to all programs */ XSTATIC ARGDESC _DefaultArgs[] = X{ X /* name flags type valp prompt */ X#ifdef TRACESTUFF X 'T', ARGOPT, argTrace, ARBNULL, "TRACE", X#endif X '-', ARGOPT, argEnd, ARBNULL, "ARGS", X ENDOFARGS X}; X X/* override argument descriptor, if none given by user */ XSTATIC ARGDESC _NullArgDesc[] = X{ X ENDOFARGS X}; X XVOID Xparseargs(argv, argd) X char **argv; X ARGDESC argd[]; X{ X register ARGDESC *ad, *list; X register char **av; X register char *p; X int argc; X BOOL noflags; X BOOL error; X#ifdef INTERACTIVE X BOOL shouldprompt; X#endif X extern char *getenv ARGS((char *)); X#ifdef INTERACTIVE X extern int isatty ARGS((int)); X#endif X X av = argv++; X argc = 1; X X /* save the name of this program (for error messages) */ X ProgName = *av; X X#ifdef INTERACTIVE X /* test to see if we are interactive */ X shouldprompt = (BOOL) isatty(0) && (BOOL) isatty(2); X#endif X X /* allow null argument descriptor */ X if (argd == (ARGDESC *) NULL) X argd = _NullArgDesc; X X /* clear out any cruft in the argument descriptor */ X for (ALL_AD) X { X ad->ad_flags &= ~ARGGIVEN; X } X for (ALL_DEFS) X { X ad->ad_flags &= ~ARGGIVEN; X } X X /* run through the argument vector */ X noflags = FALSE; X error = FALSE; X list = NULL; X while ((p = *++av) != CHARNULL) X { X if (*p == '-' && !noflags) X { X /* flag argument */ X if (*++p == '-' && p[1] == '\0') X { X /* -- indicates end of flags */ X noflags = TRUE; X list = NULL; X continue; X } X while (*p != '\0') X { X /* find the flag in the list */ X for (ALL_AD) X { X if (ad->ad_name == *p) X break; X } X if (ad->ad_name == '\0') X { X for (ALL_DEFS) X { X if (ad->ad_name == *p) X break; X } X if (ad->ad_name == '\0') X { X usrerr("flag -%c unknown", *p++); X error = TRUE; X continue; X } X } X X /* move p up to point to the (possible) value */ X p++; X X /* booleans are special, having no value */ X if (ad->ad_type == argBool) X { X *(BOOL *) ad->ad_valp = TRUE; X ad->ad_flags |= ARGGIVEN; X continue; X } X#ifdef TRACESTUFF X else if (ad->ad_type == argTrace) X { X traceset(p); X ad->ad_flags |= ARGGIVEN; X break; X } X#endif X X /* now get the real value */ X if (*p == '\0') X { X p = *++av; X if (p == CHARNULL || *p == '-') X { X av--; X usrerr("%s required for -%c flag", X ad->ad_prompt, ad->ad_name); X error = TRUE; X break; X } X } X X /* try to convert the type */ X if (!(*ad->ad_type)(ad, p, FALSE)) X error = TRUE; X else X ad->ad_flags |= ARGGIVEN; X if(ad->ad_type == argList) X list = ad; X else X list = NULL; X break; X } X } X else X { X /* parsing a list of arguments */ X if(list) { X if (!argList(list, p, FALSE)) X error = TRUE; X continue; X } X /* positional argument */ X for (ALL_AD) X { X if (ad->ad_name == ' ' && X (ad->ad_type == argList || X !BITSET(ARGGIVEN, ad->ad_flags)) X ) X break; X } X if (ad->ad_name == '\0') X { X usrerr("too any arguments"); X error = 1; X continue; X } X X /* try to convert */ X if (!(*ad->ad_type)(ad, p, FALSE)) X error = TRUE; X else X ad->ad_flags |= ARGGIVEN; X } X } X X /* now rescan for missing required arguments */ X for (ALL_AD) X { X if (BITSET(ARGREQ, ad->ad_flags) && !BITSET(ARGGIVEN, ad->ad_flags)) X { X#ifdef INTERACTIVE X /* can we prompt? */ X while (shouldprompt && !error) X { X char buf[MAXINPUTLINE]; X X fprintf(stderr, "%s? ", ad->ad_prompt); X fflush(stderr); X if (fgets(buf, sizeof buf, stdin) == CHARNULL) X break; X p = strchr(buf, '\n'); X if (p != CHARNULL) X *p = '\0'; X if (buf[0] == '\0') X { X usrerr("value required"); X continue; X } X if ((*ad->ad_type)(ad, buf, TRUE)) X { X ad->ad_flags |= ARGGIVEN; X break; X } X } X#endif X X if (!BITSET(ARGGIVEN, ad->ad_flags)) X { X /* still didn't get a value... sigh */ X if (ad->ad_name == ' ') X { X usrerr("%s required", X ad->ad_prompt); X } X else X { X usrerr("%s required for -%c flag", X ad->ad_prompt, ad->ad_name); X } X error = TRUE; X } X } X } X X if (error) X { X usage(argd); X exit(2); X } X X return argc; X} X /* X** USAGE -- print a usage message X** X** Parameters: X** argd -- the description of expected arguments. X** X** Returns: X** none X** X** Side Effects: X** prints on stderr X** X** Globals: X** MaxOutputLine -- the length of the maximum output line X** allowed before wrapping. This should be fetched X** from the terminal driver on systems that support X** this sort of thing. X*/ X Xint MaxOutputLine = 72; X XVOID Xusage(argd) X ARGDESC *argd; X{ X register ARGDESC *ad; X int ll; X int pl; X X fprintf(stderr, "Usage: %s", ProgName); X ll = strlen(ProgName) + 7; X X for (ALL_AD) X { X /* don't display hidden arguments */ X if (BITSET(ARGHIDDEN, ad->ad_flags)) X continue; X X /* figure out how wide this parameter is (for printing) */ X if (ad->ad_name != ' ') X { X pl = 2; /* -x */ X if (ad->ad_type != argBool) X pl += strlen(ad->ad_prompt) + 3;/* _< > */ X } X else X { X pl = strlen(ad->ad_prompt) + 2; /* < > */ X } X if (!BITSET(ARGREQ, ad->ad_flags)) X pl += 2; /* [ ] */ X if (ad->ad_type == argList) /* ... */ X pl += 3; X pl += 1; /* leading sp */ X X /* see if this will fit */ X if (ll + pl > MaxOutputLine) X { X /* no... start a new line */ X fprintf(stderr, " \\\n\t"); X ll = 7; X } X else X { X /* yes... just throw in a space */ X fprintf(stderr, " "); X } X ll += pl; X X /* show the argument */ X if (!BITSET(ARGREQ, ad->ad_flags)) X fprintf(stderr, "["); X if (ad->ad_name != ' ') X { X fprintf(stderr, "-%c", ad->ad_name); X if (ad->ad_type != argBool) X fprintf(stderr, " "); X } X if (ad->ad_name == ' ' || ad->ad_type != argBool) X fprintf(stderr, "<%s>", ad->ad_prompt); X if (!BITSET(ARGREQ, ad->ad_flags)) X fprintf(stderr, "]"); X if (ad->ad_type == argList) X fprintf(stderr, "..."); X } X fprintf(stderr, "\n"); X} //END_OF_FILE echo x - syserr.c sed 's/^X//' > syserr.c << '//END_OF_FILE' X#include X#include X XVERSIONID("$Header: syserr.c,v 2.0 89/12/24 00:56:31 eric Exp $"); X X/* X** SYSERR -- indicate a system error X** X** Parameters: X** m -- the printf() message to print. X** a, b, c, d, e -- parameters to the message X** X** Returns: X** never X** X** Side Effects: X** Terminates the process if no acceptable recover takes place. X** X** Globals: X** SyserrFuncs -- a list of functions to call. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X X#ifdef FUNCLISTSTUFF XFUNCLIST *SyserrFuncs = FUNCLISTNULL; X#endif X XVOID Xsyserr(m, a, b, c, d, e) X char *m; X{ X static BOOL exiting = FALSE; X X /* print the error message */ X _error_message(m, a, b, c, d, e); X X /* if we recursively syserr during exit, drop out now! */ X if (exiting) X _exit(1); X X#ifdef FUNCLISTSTUFF X /* call any possible cleanup functions */ X funclist_call(SyserrFuncs, ARBNULL); X#endif X X /* try a clean exit */ X exiting = TRUE; X exit(1); X /*NOTREACHED*/ X} X /* X** USRERR -- print a user error X** X** Parameters: X** m -- the printf() message to print. X** a, b, c, d, e -- parameters to the message X** X** Returns: X** none. X** X** Side Effects: X** clears the global error number. X*/ X X#ifdef FUNCLISTSTUFF XFUNCLIST *UsrerrFuncs = FUNCLISTNULL; X#endif X XVOID Xusrerr(m, a, b, c, d, e) X char *m; X{ X extern int errno; X X /* print the error message */ X _error_message(m, a, b, c, d, e); X X#ifdef FUNCLISTSTUFF X /* allow cleanup actions */ X funclist_call(UsrerrFuncs, ARBNULL); X#endif X X /* give us a clean slate */ X errno = 0; X} X /* X** _ERROR_MESSAGE -- generic message printing routine. X** X** Includes the program name with the message, as well as the X** various possible error codes. X** X** Parameters: X** m -- the printf() message to print. X** a, b, c, d, e -- parameters to the message X** X** Returns: X** none. X** X** Side Effects: X** none. X** X** Globals: X** ProgName -- the name of the program. X*/ X XSTATIC VOID X_error_message(m, a, b, c, d, e) X char *m; X{ X extern char *ProgName; X int saveerr; X extern int errno; X X saveerr = errno; X if (ProgName != CHARNULL) X fprintf(stderr, "%s: ", ProgName); X fprintf(stderr, m, a, b, c, d, e); X fprintf(stderr, "\n"); X if (saveerr != 0) X { X errno = saveerr; X perror("System error"); X } X} //END_OF_FILE echo x - traceset.c sed 's/^X//' > traceset.c << '//END_OF_FILE' X#ifdef NODEBUG X#undef NODEBUG X#endif X#define TRACESTUFF X#include X#include X XVERSIONID("$Header: traceset.c,v 2.0 89/12/24 00:56:33 eric Exp $"); X X/* X** TRACESET -- set trace flags X** X** Parameters: X** s -- the string describing the trace flags desired. X** X** Returns: X** none. X** X** Side Effects: X** Sets values in the trace vector. X** X** Globals: X** _TraceVect -- used by TRACE and TRACEF to determine the X** current trace flag settings. X** X** Syntax: X** The argument points to a set of comma separated trace X** flag settings. Each setting is a trace flag name, or X** a range of trace flags separated by a hyphen. The X** setting may be followed by a dot and a level. For X** example, "5,23-26.4" sets trace flag 5 to level 1 (the X** default), and flags 23 through 26 to level 4. X** X** Trace flags names may be numeric or symbolic. Symbolic X** names are looked up in the file "lib/traceflags", which X** is found somewhere in the search path. This file has a X** simple "name value" syntax. X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X XSTATIC FILE *_TraceSymFile = FILENULL; XSTATIC BOOL _TraceSymCant = FALSE; Xunsigned char _TraceVect[_TRACE_SIZE]; X XVOID Xtraceset(s) X char *s; X{ X int lo; X int hi; X int lev; X X if (s == CHARNULL || *s == '\0') X s = "0-999"; X X do X { X /* get the low limit of the range */ X lo = _tracetok(&s, TRUE); X if (lo < 0) X lo = 0; X X /* get the high limit of the range */ X if (*s == '-') X hi = _tracetok(&s, TRUE); X else X hi = lo; X if (hi >= _TRACE_SIZE) X hi = _TRACE_SIZE - 1; X X /* get the trace level */ X if (*s == '.') X lev = _tracetok(&s, FALSE); X else X lev = 1; X if (lev < 0) X lev = 1; X X /* now do the actual setting */ X while (lo <= hi) X _TraceVect[lo++] = lev; X } while (*s != '\0'); X X if (_TraceSymFile != FILENULL) X { X fclose(_TraceSymFile); X _TraceSymFile = FILENULL; X } X} X /* X** _TRACETOK -- extract a token X** X** Parameters: X** sp -- an indirect pointer to the string form. It is X** updated to point to the token terminator. X** symok -- if TRUE, a symbolic representation can be X** used. X** X** Returns: X** The value of the token. X** -1 on error. X** X** Side Effects: X** none. X*/ X XSTATIC int X_tracetok(sp, symok) X char **sp; X BOOL symok; X{ X register char *s = *sp; X register char *tp; X char tbuf[MAXWORDLEN + 1]; X char dbuf[MAXINPUTLINE]; X X /* skip leading cruft */ X while (*s != '\0' && !isalnum(*s)) X s++; X X /* extract token */ X for (tp = tbuf; isalnum(*s); *tp++ = *s++) X continue; X *tp = '\0'; X X /* drop trailing cruft */ X while (isspace(*s)) X s++; X *sp = s; X X /* now decode token */ X if (isdigit(tbuf[0])) X return (atoi(tbuf)); X if (!symok) X return (-1); X X /* look up symbol */ X if (!_TraceSymCant && _TraceSymFile == FILENULL) X { X extern FILE *openpath ARGS((char *, char *, char *)); X X _TraceSymFile = openpath("lib/traceflags", "r", CHARNULL); X } X if (_TraceSymFile == FILENULL) X { X _TraceSymCant = TRUE; X return (-1); X } X rewind(_TraceSymFile); X X /* search through the trace file for a matching line */ X while (fgets(dbuf, sizeof dbuf, _TraceSymFile) != CHARNULL) X { X register char *ep; X extern char *_tracematch ARGS((char *, char *)); X X /* comment? */ X if (dbuf[0] == '#' || dbuf[0] == '\n') X continue; X X /* does it match? */ X ep = _tracematch(tbuf, dbuf); X if (ep != CHARNULL) X return (atoi(ep)); X } X return (-1); X} X XSTATIC char * X_tracematch(tp, dp) X register char *tp; X register char *dp; X{ X for ( ; *tp != '\0'; tp++, dp++) X { X /* if we have an exact match, continue trying */ X if (*tp == *dp) X continue; X X /* well, let's try a caseless match */ X if (isupper(*tp) && tolower(*tp) == *dp) X continue; X if (isupper(*dp) && tolower(*dp) == *tp) X continue; X X /* well foo... I guess it doesn't match */ X return (CHARNULL); X } X X /* check the boundary conditions */ X if (!isspace(*dp)) X return (CHARNULL); X X /* drop the trailing white space and return the pointer */ X while (isspace(*dp)) X dp++; X return (dp); X} //END_OF_FILE echo x - stest.c sed 's/^X//' > stest.c << '//END_OF_FILE' X#include X#include X XVERSIONID("$Header: stest.c,v 2.0 89/12/24 00:56:29 eric Exp $"); X X/* X** STEST -- a simple test program for the argument parser X** X** Author: X** Eric Allman X** University of California, Berkeley X*/ X Xint RepCount; Xchar *Name; Xchar *DirName = "."; XBOOL XFlag = FALSE; XBOOL YFlag = FALSE; XBOOL ZFlag = FALSE; Xchar TabChar = ':'; Xstruct namelist *Argv = NULL; Xstruct namelist *Groups = NULL; X XARGDESC Args[] = X{ X ' ', ARGREQ, argStr, __ &Name, "Name", X 'n', ARGOPT, argList, __ &Groups, "newsGROUP", X 'c', ARGOPT, argInt, __ &RepCount, "REPcount", X 'd', ARGOPT, argStr, __ &DirName, "DIRname", X 'x', ARGOPT, argBool, __ &XFlag, "Xflag", X 'y', ARGOPT, argBool, __ &YFlag, "Yflag", X 'z', ARGOPT, argBool, __ &ZFlag, "Zflag", X 't', ARGOPT, argChar, __ &TabChar, "TABchar", X ' ', ARGOPT, argList, __ &Argv, "File", X ENDOFARGS X}; X Xmain(argc, argv) X int argc; X char **argv; X{ X parseargs(argv, Args); X X printf("Name = \"%s\", DirName = \"%s\", RepCount = %d,\n", X Name, DirName, RepCount); X printf("XFlag = %d, YFlag = %d, ZFlag = %d, TabChar=%03o;\n", X XFlag, YFlag, ZFlag, TabChar); X X if(Groups) { X printf("Newsgroups: "); X while(Groups) { X printf("%s", Groups->nl_name); X Groups = Groups->nl_next; X if(Groups) X putchar(' '); X else X putchar('\n'); X } X } X X if(Argv) { X printf("Remaining args: "); X while(Argv) { X printf("%s", Argv->nl_name); X Argv = Argv->nl_next; X if(Argv) X putchar(' '); X else X putchar('\n'); X } X } X exit(0); X} //END_OF_FILE echo x - ckalloc.3 sed 's/^X//' > ckalloc.3 << '//END_OF_FILE' X.\" $Header: ckalloc.3,v 2.0 89/12/24 00:56:12 eric Exp $ X.TH CKALLOC 3 X.SH NAME Xckalloc \- allocate memory safely X.SH SYNOPSIS Xvoid *ckalloc(size) X.br Xunsigned int size; X.PP X#include X.br Xextern FUNCLIST *OutOfMemoryFuncs; X.SH DESCRIPTION X.I ckalloc Xallocates memory from the heap in the same manner as X.IR malloc (3). XHowever, if sufficient memory is unavailable, Xrecovery is attempted by calling the list of functions Xindicated by the X.I OutOfMemoryFuncs Xglobal function list X(see X.IR funclist (3) Xfor details). XAfter these functions are called, Xthe allocation is retried. X.PP XIf the second attempt fails, X.I ckalloc Ximmediately terminates the process with a message Xusing X.IR syserr (3). X.SH SEE ALSO Xmalloc(3), Xfunclist(3), Xsyserr(3) X.SH AUTHOR XEric Allman, University of California, Berkeley //END_OF_FILE echo x - funclist.3 sed 's/^X//' > funclist.3 << '//END_OF_FILE' X.\" $Header: funclist.3,v 2.0 89/12/24 00:56:22 eric Exp $ X.TH FUNCLIST 3 X.SH NAME Xfunclist_add, funclist_call \- manipulate lists of function pointers X.SH SYNOPSIS X#include X.PP Xvoid funclist_add(flp, func, arg1) X.br XFUNCLIST **flp; X.br Xvoid (*func)(void *, void *); X.br Xvoid *arg1; X.PP Xvoid funclist_call(fl, arg2) X.br XFUNCLIST *fl; X.br Xvoid *arg2; X.SH DESCRIPTION XA X.I "function list" Xis a linked list of function pointers Xthat can be invoked at some future time. XThese are often used to handle exceptional conditions Xin generic library routines. XThe implementation is not particularly efficient; Xthese are intended primarily to allow flexible recovery Xfrom relatively rare error conditions. X.PP XFunction lists are declared using: X.PP X FUNCLIST *FuncListHead = FUNCLISTNULL; X.PP XFunctions are added to the list using: X.PP X funclist_add(&FuncListHead, &func, arg1) X.PP Xwhere ``func'' is the name of the function to be invoked. XThe X.I arg1 Xargument is passed as the first parameter to the function when it is called, Xand is otherwise uninterpreted. X.PP XFunction lists are invoked using: X.PP X funclist_call(FuncListHead, arg2) X.PP XFunctions are invoked in the opposite order of their addition Xto the function list. XEach function is called as: X.PP X func(arg1, arg2); X.PP XThat is, the first argument is from the addition to the list, Xand the second is from the invocation. X.SH BUGS XThere should be some way to remove functions from the list. X.SH AUTHOR XEric Allman, University of California, Berkeley. //END_OF_FILE echo x - openpath.3 sed 's/^X//' > openpath.3 << '//END_OF_FILE' X.\" $Header: openpath.3,v 2.0 89/12/24 00:56:24 eric Exp $ X.TH OPENPATH 3 X.SH NAME Xopenpath \- open a file, searching through a search path X.SH SYNOPSIS XFILE *openpath(fname, mode, path) X.br Xchar *fname; X.br Xchar *mode; X.br Xchar *path; X.PP Xextern char *DefaultPath; X.SH DESCRIPTION X.I openpath Xopens a file like X.IR fopen (3), Xexcept it uses a search path. XA FILE pointer is returned for the first file matching the name Xthat could be opened Xin the search path. XIf no file name matches, XFILENULL is returned. X.PP XThe X.I mode Xis passed directly to X.IR fopen (3). X.PP XThe X.I path Xis a colon-separated list of possible directories. XEach element of the path can begin with a tilde (``~'') Xto interpolate the X$HOME Xenvironment variable. XZero length entries indicate the current directory. XIf the path is not specified (CHARNULL), Xthe external variable X.I DefaultPath Xis used. XThis is set from the X$ROOTPATH Xenvironment variable. XIf this is not set, a system default is used, Xusually ``/usr/local:/usr:~:''. X.SH SEE ALSO Xfopen(3) X.SH AUTHOR XEric Allman, University of California, Berkeley //END_OF_FILE echo x - parseargs.3 sed 's/^X//' > parseargs.3 << '//END_OF_FILE' X.\" $Header: parseargs.3,v 2.0 89/12/24 00:56:26 eric Exp $ X.TH PARSEARGS 3 X.SH NAME Xparseargs, usage \- parse command line argument vectors X.SH SYNOPSIS X#include X.PP Xvoid parseargs(argv, argd) X.br Xchar *argvp[]; X.br XARGDESC argd[]; X.PP Xvoid usage(argd) X.SH DESCRIPTION XGiven a vector of string-valued arguments Xsuch as that passed to X.I main Xand a vector describing the possible arguments, X.I parseargs Xmatches actual arguments to possible arguments, Xconverts values to the desired type, Xand diagnoses problems such as Xmissing arguments, Xextra arguments, Xand argument values that are syntactically incorrect. X.PP XWhen invoked interactively, Xunder the UNIX operating system and Xif parseargs has been compiled for interactive operation, X.I parseargs Xprompts the user for argument values that are Xrequired but are not given on the command line, Xand values that are supplied but syntactically incorrect. X.PP XBy default this option is not compiled in to avoid problems in Xshell scripts, where a program unexpectedly going into interactive Xmode might tun out to be a little disconcerting. X.PP XGiven a description of possible arguments, X.I usage Xprints a reasonably friendly version of this description. X.PP XThe argument description vector Xcontains one entry for each possible flag. XEach entry has five fields: X.IP name \w'prompt'u+2n XThe single character name of the associated flag. XFor example, to indicate that the program is expecting a ``\-x'' flag, Xthis field would contain \'x\'. XPositional arguments (those without a ``\-\fIx\fP'' prefix) Xare indicated by passing a ``space'' character. X.IP flags XFlags modifying the semantics of this entry, XThese should have one of XARGREQ Xto indicate a required argument or XARGOPT Xto indicate an optional argument. XARGHIDDEN Xcan be ``ored'' in to indicate a flag that should not be printed Xin usage messages \(em Xfor example, flags intended for internal debugging purposes. X.IP type XThe type of the argument. XThis is actually a pointer to a function, as described below. XStandard types include XargBool (Boolean flags), XargStr (string-valued arguments), XargList (a LIFO list of string-valued arguments), XargChar (char-valued arguments), XargInt (native integer arguments), XargShort (short integer arguments), XargLong (long integer arguments), XargFloat (short floating point arguments), Xand XargDouble (long floating point arguments). X.IP valp XA pointer to the variable that should receive the converted value. XSince this is typed as ``pointer to anything'', Xthis field should be preceded with a double underscore (``_\|_'') X(a macro defined in parseargs.h) Xto perform the appropriate type cast. X.IP prompt XThe string used when prompting interactively for argument values, Xand printed in usage messages. X.PP XThe list of arguments is terminated using ENDOFARGS. X.PP XFor example, consider the description: X.RS X.PP X.nf Xint RepCount = 2; XBOOL Verbose = FALSE; Xchar *InFile; Xchar *OutFile = CHARNULL; XBOOL XRated = FALSE; Xstruct namelist *Files = NULL; X XARGDESC Args[] = X{ X 'c', ARGOPT, argInt, __ &RepCount, "REPcount", X 'v', ARGOPT, argBool, __ &Verbose, "Verbose", X ' ', ARGREQ, argStr, __ &InFile, "INPUTfile", X ' ', ARGOPT, argStr, __ &OutFile, "OUTPUTfile", X 'X', ARGHIDDEN, argBool, __ &XRated, "XratedMODE", X ' ', ARGOPT, argList, __ &Files, "File", X ENDOFARGS X}; X.fi X.RE X.PP XThis describes a program accepting up to three flag arguments and Xone or two positional arguments, plus a list of additional file arguments. XOnly the first positional argument is required. XThe possible flags (in UNIX) are: X.TP X\fB\-c\fP \fIcount\fP XAn integer repetition count. XThis defaults to two. X.TP X\fB\-v\fP XA Boolean ``verbose'' flag. XIt defaults to FALSE. X.TP X\fB\-X\fP XA Boolean ``X Rated'' flag. XThis is not printed in the usage message. X.PP XThe two positional arguments are both strings, as is the final list. XIn AmigaDOS, the options would be X\fBREP\fP \fBcount\fP, X\fBV\fP, and X\fXMODE\fP. X.SH ARGUMENT TYPE FUNCTIONS XThe argument types recognized by X.I parseargs Xcan be extended by adding new type functions. XArgument type functions are declared as: X.RS X.PP X.nf XBOOL argXxx(argd, argp, copyf) X ARGDESC *argd; X char *argp; X BOOL copyf; X.fi X.RE X.PP XThe X.I argd Xargument points to the descriptor for the argument being converted. XIts main use is to find the location in which to store the converted value, Xlocated in argd\(->ad_valp. XThe string value to be converted is passed in X.IR argp . XThe X.I copyf Xflag is TRUE if the X.I argp Xstring value must be copied when saved. XMost non-string types are copied implicitly X(for example, integer arguments are stored in binary form, Xso the original string value need not be saved), Xso this argument can usually be ignored. XPut simply, this flag is XTRUE Xwhen X.I argp Xpoints to a temporary buffer area. X.PP XThe type function successfully converts the value, Xit should return XTRUE. XOtherwise, Xit should print a message using X.IR usrerr (3) Xand return FALSE. This message should be of the form X\fB"invalid xxxx option 'yyyy' for Zzzz"\fP, where xxxx is the type of the Xoption, yyyy is the string passed in vp, and zzzz is the name (taken from X\fBad->ad_prompt\fP). X.PP XFor example, a type function that took a filename Xand stored an open file pointer might be coded as: X.RS X.PP X.nf X/*ARGSUSED*/ XBOOL XargReadFile(argd, argp, copyf) X register ARGDESC *argd; X register char *argp; X BOOL copyf; X{ X register FILE *fp; X X fp = fopen(argp, "r"); X if (fp == NULL) X { X usrerr("cannot open '%s' for reading as %s", X argp, argd->ad_prompt); X return (FALSE); X } X *(FILE *) argd->ad_valp = fp; X return (TRUE); X} X.fi X.RE X.SH SEE ALSO Xsyserr(3) X.br XThe ``C Advisor'' column in X.ul XUNIX Review XVol. 7 No. 11. X.SH AUTHOR XEric Allman, University of California, Berkeley X.SH MODIFICATIONS XModified to accept a vector of arguments, better error messages, Xand AmigaDOS version by Peter da Silva. //END_OF_FILE echo x - syserr.3 sed 's/^X//' > syserr.3 << '//END_OF_FILE' X.\" $Header: syserr.3,v 2.0 89/12/24 00:56:30 eric Exp $ X.TH SYSERR 3 X.SH NAME Xsyserr, usrerr \- display error messages X.SH SYNOPSIS Xvoid syserr(msg, arg1, ...) X.br Xchar *msg; X.br Xvoid *arg1; X.PP Xvoid usrerr(msg, arg1, ...) X.PP X#include X.br Xextern FUNCLIST *SyserrFuncs; X.br Xextern FUNCLIST *UsrerrFuncs; X.PP Xextern char *ProgName; X.SH DESCRIPTION XThese routines print error messages. XUnrecoverable errors should be sent using X.IR syserr , Xwhich is guaranteed to never return. XRecoverable errors can be printed using X.IR usrerr . X.PP XBoth routines label the message with the contents of the X.I ProgName Xvariable, which is set by X.IR parseargs (3) Xto the name of the invoking program. XThe X.I msg Xand up to five arguments are passed to X.IR fprintf (3) Xto be printed. XThe message should X.I not Xcontain a trailing newline; Xthis is added internally. XThe contents of the X.I errno Xglobal variable is then appended to the message. X.PP XAfter printing the message, X.I syserr Xinvokes the X.I SyserrFuncs Xfunction list. XA member function can attempt to clean up and return to a top loop Xusing X.IR longjmp (3) Xor some similar mechanism. XIf the function list returns normally, Xthe process is terminated. X.PP XAfter X.I usrerr Xprints the message, Xit invokes the X.I UsrerrFuncs Xfunction list. XMember functions can use non-local jumps if a ``quick abort'' policy Xis desired. XIf this function list returns, X.I usrerr Xreturns to the caller. XThe global X.I errno Xvariable is reset to zero. X.SH BUGS XShould allow an arbitrary number of arguments. X.PP XMessages printed by X.I syserr Xshould probably also be logged using X.IR syslog (3) Xor the equivalent. X.PP XIt is possible to get recursive calls to X.I syserr Xif one of the X.I SyserrFuncs Xcalls X.IR syserr . XSimilar comments apply to X.IR usrerr . X.SH SEE ALSO Xsyslog(3), Xfunclist(3), Xlongjmp(3), Xexit(3) //END_OF_FILE echo x - trace.3 sed 's/^X//' > trace.3 << '//END_OF_FILE' X.\" $Header: trace.3,v 2.0 89/12/24 00:56:31 eric Exp $ X.TH TRACE 3 X.SH NAME XTRACE, TRACEF, traceset \- trace package X.SH SYNOPSIS X#include X.PP XBOOL TRACEF(flag, level) X.br Xint flag; X.br Xint level; X.PP XTRACE(flag, level, (\fIprintf arguments\fP)); X.PP Xvoid traceset(tracestring) X.br Xchar *tracestring; X.SH DESCRIPTION XThe X.I traceset Xroutine and the X.I TRACE Xand X.I TRACEF Xmacros implement a simple trace flag package. XA large number of trace flags X(200 default) Xcan each be set to a trace level Xvarying from zero X(no tracing for this flag) Xto 255 X(full tracing). X.PP XThe X.I TRACEF Xmacro tests whether the specified flag is set to at least the indicated level. XFor example: X.PP X if (TRACEF(24, 3)) X.PP Xtests flag 24; if it is set to 3 or higher, Xthe X.B if Xstatement succeeds. X.PP XThe X.I TRACE Xmacro incorporates a X.IR printf (3) Xstatement. XFor example, the code: X.PP X TRACE(24, 3, ("xyzzy = %d\n", xyzzy)); X.PP XPrints the ``xyzzy'' variable Xwhen trace flag 24 is set to level 3 or higher. XThe extra set of parentheses is important. X.PP XFlags can be adjusted using the X.I traceset Xroutine. XThis parses the trace string, setting indicated flags as it goes. XThe string is a comma-separated list of trace settings. XEach setting has the syntax: X.PP X flag[\-flag][.level] X.PP XIf the level is omitted, one is assumed. XIf only one flag is indicated, Xit is set individually. XIf a range of flags is given, Xall flags inclusively between the two values are set. XFor example: X.PP X 20\-29.2 X.PP Xsets the ten flags numbered between 20 and 29 to level 2. X.PP XFlag names can be symbolic. XSymbolic names are looked up in the file X``/lib/traceflags'', Xwhere X Xis the default search path used by X.IR openpath (3). XThis file is a series of lines, each containing a flag name, Xwhite space, Xand the flag value. XBlank lines Xand lines beginning with `#' are ignored. XCase is ignored in flag names. X.SH SEE ALSO Xopenpath(3), Xparseargs(3) X.SH AUTHOR XEric Allman, University of California, Berkeley //END_OF_FILE echo "End of archive." # end of archive. exit 0 --- _--_|\ Peter da Silva. +1 713 274 5180. . / \ \_.--._/ Xenix Support -- it's not just a job, it's an adventure! v "Have you hugged your wolf today?" `-_-'