v07i035: Ann Arbor XL key uploader
sources-request at mirror.UUCP
sources-request at mirror.UUCP
Sat Oct 25 05:55:16 AEST 1986
Submitted by: Rayan Zachariassen <seismo!utai!rayan>
Mod.sources: Volume 7, Issue 35
Archive-name: aaaxlkeys
[ If you are fortunate enough to have both an XL and USG Unix, note
that this program uses index/rindex, not strchr/strrchr. -r$ ]
#!/bin/sh
# This is a shell archive. Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
# If all goes well, you will see the message "No problems found."
# Exit status; set to 1 on "wc" errors or if would overwrite.
STATUS=0
# Contents: README Makefile aaxl.c aaxl.l
echo x - README
if test -f README ; then
echo README exists, putting output in $$README
OUT=$$README
STATUS=1
else
OUT=README
fi
sed 's/^X//' > $OUT <<'@//E*O*F README//'
X Ann Arbor XL series programmable strings uploader
XI wrote this one evening partly for relaxation, partly as a tangible example
Xof my coding style. I was having a friendly discussion with one of our local
Xpurists (hi Geoff!) about how code should look like, which tradeoffs to make,
Xetc. Some of his comments on the first draft of this were adopted in the code,
Xbecause they seemed quite sensible within my philosophy as well. I am not a
Xgreat fan of remapping keyboards (though it is useful at times) and don't use
Xthis program regularly, though another fellow around here does. I'm sending it
Xout primarily as a sample of good (I think) coding, in case anyone cares for
Xa non-trivial example. Besides, some people might find it useful. No claims
Xare made about the portability of this stuff. It works on 4.n on Vaxen.
XThere is one large compiler dependency, clearly marked. I'll take elegant over
Xproper most any day...
Xrayan
XRayan Zachariassen <rayan at utai.uucp>
XAI group, University of Toronto
@//E*O*F README//
chmod u=rw,g=rw,o=rw $OUT
echo x - Makefile
if test -f Makefile ; then
echo Makefile exists, putting output in $$Makefile
OUT=$$Makefile
STATUS=1
else
OUT=Makefile
fi
sed 's/^X//' > $OUT <<'@//E*O*F Makefile//'
X## Quick Makefile for Ann Arbor XL program
XCFLAGS=-O
Xaaxl: aaxl.c
X $(CC) $(CFLAGS) aaxl.c -o aaxl
Xinstall:
X @echo cp aaxl and aaxl.l to appropriate places
X cp aaxl /usr/bin
X strip /usr/bin/aaxl
X cp aaxl.l /usr/man/man1/aaxl.1
@//E*O*F Makefile//
chmod u=rw,g=rw,o=rw $OUT
echo x - aaxl.c
if test -f aaxl.c ; then
echo aaxl.c exists, putting output in $$aaxl.c
OUT=$$aaxl.c
STATUS=1
else
OUT=aaxl.c
fi
sed 's/^X//' > $OUT <<'@//E*O*F aaxl.c//'
X/*
X * Ann Arbor XL series programmable strings uploader.
X */
X#ifndef lint
Xstatic char *RCSid = "$Header: aaxl.c,v 1.1 86/05/09 19:48:10 rayan Exp $";
X#endif
X/*
X * Copyright (c) 1986 by Rayan S. Zachariassen.
X *
X * Permission is granted to anyone to use this software for any
X * purpose on any computer system, and to redistribute it, subject
X * to the following four restrictions:
X *
X * 1. The author is not responsible for the consequences of use of
X * this software, no matter how awful, even if they arise
X * from imperfections in it.
X *
X * 2. The origin of this software must not be misrepresented, either
X * by explicit claim or by omission.
X *
X * 3. Altered versions must be plainly marked as such, and must not
X * be misrepresented as being the original software, but must be
X * marked as being an alteration of this software.
X *
X * 4. Commercial or any for-profit redistribution of this software is
X * prohibited unless by prior written agreement from the author.
X */
X/*
X * Several files may be given as arguments. With no files specified,
X * $HOME/.aaxlrc is read. A filename of - reads from stdin.
X * The options are:
X *
X * -a append, i.e. don't clear to default programmable strings.
X * -d debug, prints out memory usage stats instead of uploading.
X * -v verbose, print out body of upload string to stdout.
X * -l lock, to lock programmable strings until power-up or SETUP-Z
X *
X * To initialize the programmable strings on your XL, use
X *
X * aaxl /dev/null
X *
X * Format of files:
X *
X * Each input file contains lines specifying a key to upload a programmable
X * string for. Lines starting with #, and empty lines, are ignored.
X * The format of each line is
X *
X * name <whitespace> options [ <whitespace> <tab> text ]
X *
X * where <whitespace> is blanks, tabs, and commas. The text is optional,
X * if not there the key will revert to default programmed text (usually none).
X * This can be used to change other behavior of a key.
X *
X * The options field consists of keywords from the following lists:
X *
X * ctrl, shift, ctrl-shift, meta, 0-31, 64 - shift level
X * host, display, graphics - transmit direction
X * never, always - repeat attribute
X * level - this is a shift key
X * normal - everything normal
X *
X * The defaults for all but the 'normal' keyword are whatever the previous
X * option value was for the previous key. The normal (and initial default)
X * values correspond to all-zero parameters. See the XL manual for detailed
X * explanation of the options.
X *
X * The text field follows one or more blanks or tabs after the option field
X * and consists of everything from there till a newline. The string may contain
X * all the usual C string escapes (\###, \n\t\s\a\b\f\r\e), and in addition
X * may contain one sequence like \`...` where ... is a command to be executed
X * by the shell. The result (stdout) of the command is interpolated in place
X * of the \` escape.
X *
X * This program reports errors, including XL memory overflow that could
X * occur. All errors are fatal. The program doesn't send an upload string
X * to the terminal unless no errors were detected at all. Memory allocation
X * for the upload string is done dynamically in the program, so excessively
X * large string sequences are handled. Space-optimal output is produced.
X *
X * Rayan Zachariassen
X * rayan at utai.uucp
X *
X * Toronto, March 27 1986
X *
X */
X#include <stdio.h>
X#include <ctype.h>
X#define USAGE "aaxl [-a] [-d] [-l] [-v] [file ...]"
X#define RCFILE ".aaxlrc" /* file to read from in $HOME */
X#define MEMSIZ 896 /* memory size in bytes of an XL */
X#define MAXPOWERMEM 255 /* non-volatile power-on memory size */
X#define CLICK 1024 /* memory increased in chunks of this */
X#define PREAMBLE "\033P>"
X#define POSTAMBLE "\033\\"
X#define MAXONEUSE (2*MEMSIZ+3+2+1+1+1+4+2+(sizeof POSTAMBLE)-1)
Xint lineno, memusage;
Xchar *program, *file, *out;
Xextern char *index(), *rindex(), *itoa(), *malloc(), *realloc();
Xextern FILE *popen();
Xextern int errno;
Xextern char *sys_errlist[];
Xextern int getopt();
Xextern char *optarg;
Xextern int optind;
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X int ch, errflg, keysappend, debug, lock, verbose;
X unsigned int xlsize;
X char *xlmem, buf[BUFSIZ];
X FILE *fp;
X program = argv[0];
X if (out = rindex(program, '/'))
X program = out + 1;
X errflg = keysappend = debug = lock = verbose = 0;
X while ((ch = getopt(argc, argv, "adlv")) != EOF) {
X switch ((char) ch) {
X case 'a':
X keysappend = 1;
X break;
X case 'd':
X debug = 1;
X break;
X case 'l':
X lock = 1;
X break;
X case 'v':
X verbose = 1;
X break;
X default:
X errflg++;
X break;
X }
X }
X if (errflg) {
X fprintf(stderr, "Usage: %s\n", USAGE);
X exit(errflg);
X }
X if (optind == argc) {
X char *cp;
X extern char *getenv();
X cp = getenv("HOME");
X if (*cp && chdir(cp) < 0) {
X fprintf(stderr, "%s: chdir(%s): %s\n", program,
X cp, sys_errlist[errno]);
X exit(1);
X } else if (cp == 0) {
X fprintf(stderr,
X "%s: no $HOME environment variable\n", program);
X exit(1);
X }
X argv[--optind] = RCFILE;
X }
X xlsize = CLICK + MAXONEUSE;
X if ((xlmem = malloc(xlsize)) == 0) {
X fprintf(stderr, "%s: malloc failure\n", program);
X exit(1);
X }
X out = xlmem;
X append(PREAMBLE);
X /*
X * keysappend lock global parameter
X *
X * 0 0 0 or 1
X * 0 1 3
X * 1 0 empty
X * 1 1 2
X */
X if (!keysappend)
X append(itoa(3*lock));
X else if (lock)
X append("2");
X for (; optind < argc; optind++) {
X file = argv[optind];
X if (strcmp(file, "-") == 0 && (fp = fdopen(0, "r")) == NULL) {
X perror("stdin");
X exit(1);
X } else if ((fp = fopen(file, "r")) == NULL) {
X perror(file);
X exit(1);
X }
X lineno = 0;
X memusage = 0;
X while (fgets(buf, sizeof buf, fp)) {
X lineno++;
X buf[strlen(buf)-1] = '\0';
X if (buf[0] == '#' || buf[0] == '\0')
X continue;
X doline(buf);
X if (out - xlmem >= xlsize - MAXONEUSE) {
X char *cp;
X if ((cp = realloc(xlmem, xlsize + CLICK)) == 0)
X error("realloc failure\n", (char *)0);
X out = cp + (xlmem - out);
X xlmem = cp;
X xlsize += CLICK;
X }
X }
X (void) fclose(fp);
X *out = 0;
X }
X if (verbose)
X printf("%s\n", xlmem + sizeof PREAMBLE - 1);
X if (memusage > MEMSIZ) {
X fprintf(stderr,
X "%s: Ann Arbor XL memory not large enough!\n", program);
X debug = 1;
X errflg = 1;
X }
X if (!debug) {
X append(POSTAMBLE);
X if (write(1, xlmem, strlen(xlmem)) < 0) {
X fprintf(stderr, "%s: write failure\n", program);
X exit(1);
X }
X } else
X printf("Memory usage: %d bytes, or %d%%\n",
X memusage, (100*memusage)/MEMSIZ);
X exit(errflg);
X}
X#define KEY 0
X#define LEVEL 1
X#define DIR 2
X#define REPEAT 3
X#define LFLAG 4
X#define NUMATTR 5
Xdoline(buf)
Xchar *buf;
X{
X int i, nomore, mustbreak;
X register char *cp;
X char *start;
X short new[NUMATTR];
X static short old[NUMATTR] = { -2, 0, 0, 0, 0 };
X for (i = 0; i < NUMATTR; i++)
X new[i] = old[i];
X cp = buf;
X while (*cp && isascii(*cp) && !isspace(*cp))
X cp++;
X nomore = *cp == 0;
X *cp++ = 0;
X if ((new[KEY] = find(buf)) < 0)
X error("unknown key name '%s'\n", buf);
X append("|");
X if (nomore) {
X if (new[KEY] != old[KEY] + 1)
X append(itoa(new[KEY]));
X old[KEY] = new[KEY];
X append("|");
X return;
X }
X while (*cp && isascii(*cp) && (isspace(*cp) || *cp == ','))
X cp++;
X if (!*cp) {
X if (new[KEY] != old[KEY] + 1)
X append(itoa(new[KEY]));
X old[KEY] = new[KEY];
X append("|");
X return;
X }
X /*
X * Option parsing
X *
X * The options field consists of space-seperated keywords
X * from the following lists:
X *
X * ctrl, shift, ctrl-shift, meta, 0-31, 64 - shift level
X * host, display, graphics - transmit direction
X * never, always - repeat attribute
X * level - this is a shift key
X * normal - everything normal
X */
X mustbreak = 0;
X while (!mustbreak && *cp && isascii(*cp) && !isspace(*cp)) {
X start = cp; /* assert isascii(*start) */
X while (*cp && isascii(*cp) && !isspace(*cp) && *cp != ',')
X cp++;
X if (*cp == '\t')
X mustbreak = 1;
X *cp++ = 0; /* assert cp > start */
X switch (*start) {
X case 'a':
X new[REPEAT] = 2;
X break;
X case 'c':
X if (index(start, 's'))
X new[LEVEL] = 3;
X else
X new[LEVEL] = 2;
X break;
X case 'd':
X new[DIR] = 2;
X break;
X case 'g':
X new[DIR] = 3;
X break;
X case 'h':
X new[DIR] = 1;
X break;
X case 'l':
X new[LFLAG] = 1;
X break;
X case 'm':
X new[LEVEL] = 64;
X break;
X case 'n':
X if (*(start+1) == 'e')
X new[REPEAT] = 1;
X else
X new[LEVEL] = new[DIR] = new[REPEAT] = new[LFLAG] = 0;
X break;
X case 's':
X new[LEVEL] = 1;
X break;
X default:
X if (isdigit(*start)) {
X new[LEVEL] = atoi(start);
X if (new[LEVEL] > 31 && new[LEVEL] != 64)
X error("illegal shift level %s\n",
X itoa(new[LEVEL]));
X } else
X error("unknown option '%s'\n", start);
X }
X /* do NOT skip tabs, but DO skip commas */
X while (*cp && (*cp == ' ' || *cp == '\n' || *cp == ','))
X cp++;
X }
X for (i = 0; i < NUMATTR; i++) {
X if (i == KEY)
X continue;
X if (new[i] != old[i])
X break;
X }
X if (i == NUMATTR) { /* old attributes are identical to new */
X if (new[KEY] != old[KEY] + 1)
X append(itoa(new[KEY]));
X } else {
X if (new[KEY] == 200)
X error("%s string must have normal attributes\n", buf);
X for (i = 0; i < NUMATTR; i++) {
X if (new[i] != old[i])
X append(itoa(new[i]));
X append(";");
X old[i] = new[i];
X }
X trim(';');
X memusage++;
X }
X append("|");
X while (*cp && isascii(*cp) && isspace(*cp))
X cp++;
X if (!*cp)
X return;
X if (new[KEY] == 200) {
X int i = memusage;
X encode(cp);
X if (memusage - i > MAXPOWERMEM)
X error("%s string exceeds available memory\n", buf);
X memusage = i;
X } else if (new[KEY] == 120 && new[LEVEL] == 3)
X error("ctrl-shift-reset is not programmable\n", (char *)0);
X else
X encode(cp);
X return;
X}
Xappend(s)
Xchar *s;
X{
X while (*s)
X *out++ = *s++;
X}
Xtrim(c)
Xchar c;
X{
X while (*--out == c)
X ;
X out++;
X}
Xencode(in)
Xchar *in;
X{
X int i;
X while (*in != 0) {
X if (*in == '\\') {
X switch (*++in) {
X case 'n':
X *in = '\n';
X break;
X case 'r':
X *in = '\r';
X break;
X case 'e':
X *in = '\033';
X break;
X case 'b':
X *in = '\b';
X break;
X case 'f':
X *in = '\f';
X break;
X case 'a':
X *in = '\007';
X break;
X case 't':
X *in = '\t';
X break;
X case 's':
X *in = ' ';
X break;
X case '`':
X /*
X * I know I'm gonna regret this...
X * \`echo hi`
X * is replaced by the result (stdout) of
X * executing that command...
X */
X if (rindex(in, '`') && rindex(in, '`') != in) {
X char *cmd, buf[MEMSIZ+1];
X FILE *pfp;
X cmd = in + 1;
X in = rindex(in, '`');
X *in++ = 0;
X if ((pfp = popen(cmd, "r")) != NULL) {
X int n;
X n = fread(buf, 1, MEMSIZ, pfp);
X if (n > 0) {
X buf[n] = 0;
X encode(buf);
X }
X (void) pclose(pfp);
X }
X continue;
X } else
X *in = '`';
X break;
X default:
X if (!(*in >= '0' && *in <= '7')) {
X in--;
X break;
X } else
X i = *in++ - '0';
X if (*in >= '0' && *in <= '7') {
X i = 8*i + (*in++ - '0');
X if (*in >= '0' && *in <= '7')
X i = 8*i + (*in++ - '0');
X }
X *--in = (char) i;
X }
X }
X if (*in < ' ') {
X *out++ = '~';
X *out++ = (char)((*in & 037)+0100);
X } else {
X switch (*in) {
X case '\177':
X *out++ = '~';
X *out++ = '?';
X break;
X case '~':
X case '|':
X *out++ = '~';
X *out++ = *in;
X break;
X default:
X *out++ = *in;
X }
X }
X memusage++;
X in++;
X }
X}
X/*
X * The following table gives correspondence between key name and number.
X * Names are seperated by space, and each string represents 20 keys in
X * increasing number order.
X */
Xchar *lookup[] =
X{ "s1 s2 space s3 s4 s5 s6",
X "pause shift-l z x c v b n m , . / shift-r scroll zoom",
X "_ ctrl a s d f g h j k l ; ' return back-space",
X "tab q w e r t y u i o p [ ] line-feed del",
X "esc 1 2 3 4 5 6 7 8 9 0 - = ` \\ break",
X "0P _ 00P .P enter ,P 1P 2P 3P _ tabP 4P 5P 6P +P _ 7P 8P 9P -P",
X "reset setup f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18",
X "send erase edit delete insert print _ _ _ _ Header Trailer ENQ DA",
X "_",
X "_",
X "Power-on",
X 0
X};
X/*
X * This little baby relies on contiguous allocation of the strings in
X * the lookup[] array. If your compiler doesn't grok that, it's simplicity
X * itself to wrap a loop around the body of this thing.
X */
Xint
Xfind(keyname)
Xchar *keyname;
X{
X register char *cp, *s;
X int key;
X key = 0;
X for (cp = lookup[0]; *cp; cp++) {
X s = keyname;
X while (*s && *cp == *s)
X cp++, s++;
X if (*s == 0 && (*cp == ' ' || *cp == 0))
X return key;
X while (*cp && *cp != ' ')
X cp++;
X if (!*cp)
X key = 20*((key/20)+1);
X else
X key++;
X }
X return -1;
X}
Xchar *
Xitoa(num)
Xint num;
X{
X static char digs[10];
X char *cp;
X cp = digs + 9;
X *cp-- = '\0';
X if (num == 0) {
X *cp = '0';
X return cp;
X }
X while (num > 0) {
X *cp-- = (num % 10) + '0';
X num /= 10;
X }
X return cp+1;
X}
Xerror(format, string)
Xchar *format, *string;
X{
X fprintf(stderr, "%s: file %s line %d: ", program, file, lineno);
X fprintf(stderr, format, string);
X exit(1);
X}
@//E*O*F aaxl.c//
chmod u=rw,g=rw,o=rw $OUT
echo x - aaxl.l
if test -f aaxl.l ; then
echo aaxl.l exists, putting output in $$aaxl.l
OUT=$$aaxl.l
STATUS=1
else
OUT=aaxl.l
fi
sed 's/^X//' > $OUT <<'@//E*O*F aaxl.l//'
X.TH AAXL l "University of Toronto"
X.SH NAME
Xaaxl - program Ann Arbor XL keyboard
X.SH SYNOPSIS
X.B aaxl
X[
X.B \-a
X] [
X.B \-d
X] [
X.B \-l
X] [
X.B \-v
X] [
X.I file
X\&... ]
X.SH DESCRIPTION
X.PP
XAaxl reads a description of keynames and corresponding attributes and a text
Xto program the key with. Several files may be given as arguments. With no files
Xspecified, $HOME/.aaxlrc is read. A filename of \fB-\fR reads from stdin.
X.PP
XThe \fB-a\fR flag is used to append the downloaded strings to the current
Xstate. If not given, programmable strings are cleared before defining any
Xnew string.
X.PP
XThe \fB-d\fR flag is a debug flag, causing Ann Arbor XL memory usage statistics
Xto be printed instead of loading the programmable strings. An absolute
Xbytecount, and the percentage of programmable strings memory that would be
Xused, is printed.
X.PP
XThe \fB-l\fR flag asks for the key programming lock to be set, after the
Xprogrammable strings have been loaded.
X.PP
XThe \fB-v\fR flag asks the program to be verbose and print the body of the
Xcontrol string it would send to the Ann Arbor, to stdout. The entire control
Xstring is still written out, unless \fB-d\fR is used.
X.PP
XThe program collects definitions of programmable keys from the files it reads.
XThe definitions are wrapped in preamble and postamble control sequences, and
Xsend to stdout. Other simultaneous output to the terminal will probably corrupt
Xthe programming command, though it is written using a single (hopefully atomic)
Xoutput operation.
X.PP
XAs a side benefit, to initialize the programmable strings on the XL, use
X.sp
X.nf
X aaxl /dev/null
X.fi
X.PP
XEach input contains lines specifying a key to upload a programmable
Xstring for. Lines starting with #, and empty lines, are ignored.
XThe format of each line is
X.sp
X.nf
X name <whitespace> options [ <whitespace> <tab> text ]
X.fi
X.sp
Xwhere <whitespace> is blanks, tabs, and commas. The text is optional,
Xif not there the key will revert to default programmed text (usually none).
XThis can be used to change other behavior of a key.
X.PP
XThe options field consists of keywords from the following lists:
X.sp
X.nf
Xctrl, shift, ctrl-shift, meta, 0-31, 64 - shift level
Xhost, display, graphics - transmit direction
Xnever, always - repeat attribute
Xlevel - this is a shift key
Xnormal - everything normal
X.fi
X.PP
XThe defaults for all but the 'normal' keyword are whatever the previous
Xoption value was for the previous key. The normal (and initial default)
Xvalues correspond to all-zero parameters. See the XL manual for detailed
Xexplanation of the options.
X.PP
XThe text field follows one or more blanks or tabs after the option field
Xand consists of everything from there till a newline. The string may contain
Xall the usual C string escapes (\\###, \\n\\t\\s\\a\\b\\f\\r\\e), and in
Xaddition may contain one sequence like \\`...` where ... is a command to be
Xexecuted by the shell. The result (stdout) of the command is interpolated in
Xplace of the \\` escape.
X.PP
XKey names used in the first field correspond to the legend on the caps. Keys
Xin the keypad group have a capital P appended to the cap name. The following
Xlist shows the complete list of recognized names. Note that key names which
Xstart with a capital letter do not correspond to a keyboard key, but rahter
Xto other capabilities of the XL. For example, DA programs the answerback
Xstring (Device Attributes), Power-on the power-on string, etc.
X.sp
Xs1 s2 space s3 s4 s5 s6
Xpause shift-l z x c v b n m , . / shift-r scroll zoom
Xctrl a s d f g h j k l ; ' return back-space
Xtab q w e r t y u i o p [ ] line-feed del
Xesc 1 2 3 4 5 6 7 8 9 0 - = ` \\ break
X0P 00P .P enter ,P 1P 2P 3P tabP 4P 5P 6P +P 7P 8P 9P -P
Xreset setup f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 f13 f14 f15 f16 f17 f18
Xsend erase edit delete insert print Header Trailer ENQ DA
XPower-on
X.PP
XThis program reports errors, including XL memory overflow that could
Xoccur. All errors are fatal. The program doesn't send an upload string
Xto the terminal unless no errors were detected at all. Memory allocation
Xfor the upload string is done dynamically in the program, so excessively
Xlarge string sequences are handled. Space-optimal output is produced.
X.SH ENVIRONMENT
X.PP
XWhen no file arguments are given to the program, the value of the
X.I HOME
Xenvironment variable is assumed to be a directory containing a .aaxlrc file,
Xwhich will be read if it exists.
X.SH SEE ALSO
X"XL Series User Guide" by Ann Arbor Terminals, Inc.
X.SH AUTHOR
XRayan Zachariassen, University of Toronto CSRI/AI.
X.SH BUGS
XIf a command is embedded in the key text, the last backquote in the text
Xmust correspond to the end of the command escape.
X.PP
XProbably doesn't handle NUL's in strings terribly gracefully. Who does.
@//E*O*F aaxl.l//
chmod u=rw,g=rw,o=rw $OUT
echo Inspecting for damage in transit...
temp=/tmp/sharin$$; dtemp=/tmp/sharout$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
20 170 1002 README
11 33 229 Makefile
579 2171 12997 aaxl.c
121 851 4723 aaxl.l
731 3225 18951 total
!!!
wc README Makefile aaxl.c aaxl.l | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if test -s $dtemp ; then
echo "Ouch [diff of wc output]:"
cat $dtemp
STATUS=1
elif test $STATUS = 0 ; then
echo "No problems found."
else
echo "WARNING -- PROBLEMS WERE FOUND..."
fi
exit $STATUS
More information about the Mod.sources
mailing list