Streams message allocation
Larry McVoy
lm at snafu.Sun.COM
Fri Aug 10 03:21:06 AEST 1990
In article <1990Aug2.043059.578 at cbnewsl.att.com> sar0 at cbnewsl.att.com (stephen.a.rago) writes:
>The number of messages needed per buffer class (size) is something that
>can only be determined statistically or empirically. In the absence of
>information like inter-arrival rate of allocation requests and message
>hold times, the best way to proceed is to start with an educated guess
>of how many messages you may need.
Here's a package I wrote a few years ago when tuning STREAMS for SCO XENIX
(I was porting the LAI TCP/IP to XENIX and tuning was really critical).
Use this while your system is under a "normal" load and it will give you a
pretty good idea of where to set things. No promises that it works -
it worked on SCO XENIX a while ago, beyond that you're on your own. If
you have SCO XENIX & TCP/IP you should have this program already.
# This is a shell archive. Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file". (Files
# unpacked will be owned by you and have default permissions.)
#
# This archive contains:
# Makefile mode.c sw.1 sw.c term.h termcap.c
echo x - Makefile
cat > "Makefile" << '//E*O*F Makefile//'
O = sw.o termcap.o mode.o
S = Makefile sw.1 sw.c termcap.c mode.c term.h
#CFLAGS=-DUNIX=\"/xenix\" -DKMEM=\"/dev/mem\" -DSAVECNT=0
BIN=/usr/local/bin
all sw: $O
cc $O -ltermlib -o sw
install: sw
cp sw $(BIN)
chown root $(BIN)/sw
chmod 4755 $(BIN)/sw
clean:
rm -f $O a.out core shar
clobber: clean
rm -f sw
shar:
shar $S > shar
//E*O*F Makefile//
echo x - mode.c
cat > "mode.c" << '//E*O*F mode.c//'
/*
* copyright (C) 1986 by Larry McVoy
* MUST be distributed in source form only.
*/
# include <stdio.h>
# include <sgtty.h>
# include <fcntl.h>
static struct sgttyb buf;
static done = 0;
delay(on)
{
if (on) {
int flags;
fcntl(0, F_GETFL, &flags);
flags &= ~O_NDELAY;
return fcntl(0, F_SETFL, flags);
}
else {
return fcntl(0, F_SETFL, O_NDELAY);
}
}
cbreak(on)
{
if (!done) {
ioctl(fileno(stdin), TIOCGETP, &buf);
done++;
}
if (on) {
buf.sg_flags |= CBREAK;
ioctl(fileno(stdin), TIOCSETP, &buf);
}
else {
buf.sg_flags &= ~CBREAK;
ioctl(fileno(stdin), TIOCSETP, &buf);
}
}
echo(on)
{
if (!done) {
ioctl(fileno(stdin), TIOCGETP, &buf);
done++;
}
if (on) {
buf.sg_flags |= ECHO;
ioctl(fileno(stdin), TIOCSETP, &buf);
}
else {
buf.sg_flags &= ~ECHO;
ioctl(fileno(stdin), TIOCSETP, &buf);
}
}
//E*O*F mode.c//
echo x - sw.1
cat > "sw.1" << '//E*O*F sw.1//'
.TH SW 1
.UC 4
.SH NAME
sw - (stream watch) watch streams resources on System V
.SH SYNOPSIS
.B sw
.SH DESCRIPTION
.I Sw
digs into kmem to find out how many streams, queues, message blocks, and data
blocks are in use. It find this in the \fIstruct strstat strst\fR variable.
For each category mentioned above the following fields are printed:
.IP Use
(strst.use)
How many of the resource in question are in use.
.IP "Ave10, Ave30, Ave60, Ave120"
As above, only the value is averaged over the last N iterations
(an iteration is
about one second).
.IP Total
(strst.total)
The total number of the resource used since boot time or the last time it
was cleared.
.IP Max
(strst.max)
The maximum number of the resource allocated at the same time.
.IP Fail
(strst.fail)
The number of times a request was made, for the resource, that could not
be granted.
.PP
The data block section is further broken down into sub classes. This section
has a slightly different format; in the title section the field is
\fISiz<#> Count <#>\fR, where the first number is the data block size and
the second number is the number of data blocks statically allocated.
.PP
The screen is managed by curses. It will respond to:
.IP c
clear the total max & fail fields (you have to have write permission on
/dev/mem).
.IP ^L
redraw the screen.
.IP N
Where N is 0-9. Set the number of seconds between interations.
.IP q
(quit) Quit the program.
.IP ^L
(Control-L) Refresh the screen.
.SH FILES
.DT
/dev/kmem kernel memory
.br
/unix for getting variable addresses
.SH BUGS
The definitions of the various fields is my best guess, they do not reflect
any AT&T documentation that I've read.
.SH COPYRIGHT
\fBSw\fR is copyright 1988 by Larry McVoy. Permission is hereby granted to
publish strings in source or object form as long as all copyright notices
are retained. Object-only distributions are permitted only if the source
is also freely available from the distributer. Any fee charged for such
publication may consist only of a reasonable charge for any media used.
.SH AUTHOR
Larry McVoy (lm at eng.sun.com)
//E*O*F sw.1//
echo x - sw.c
cat > "sw.c" << '//E*O*F sw.c//'
/*
* copyright 1988 by Larry McVoy. All rights reserved.
* If you redistribute this you must distribute in source form.
*/
#include "term.h"
#include <signal.h>
#ifdef M_XENIX
#include <a.out.h>
#else
#include <nlist.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strstat.h>
#include <sys/var.h>
#if defined(M_XENIX) || defined(sys5)
#include <sys/utsname.h>
#endif
#ifndef UNIX
#define UNIX "/vmunix"
#endif
#ifndef KMEM
#define KMEM "/dev/kmem"
#endif
#ifdef M_XENIX
#define v_nblk4096 v_nblk8192
#endif
#ifdef sun
#define NCLASS 9
#endif
#ifndef SAVECNT
#define SAVECNT 121
#endif
#define kbytes(x) ( ((x)+1023) >> 10)
#ifdef M_XENIX
#define nlist xlist
#define n_value xl_value
#define n_name xl_name
struct nlist nl[] = {
#define NL_STRST 0
{0,0,0,"_strst"},
#define NL_RBSIZE 1
{0,0,0,"_rbsize"},
#define NL_V 2
{0,0,0,"_v"},
#define NL_DBALLOC 3
{0,0,0,"_dballoc"},
#define NL_NMBLOCK 4
{0,0,0,"_nmblock"},
{0,0,0,(char *) 0},
};
#else
#ifdef sys5
struct nlist nl[] = {
#define NL_STRST 0
{"_strst"},
#define NL_RBSIZE 1
{"_rbsize"},
#define NL_V 2
{"_v"},
#define NL_DBALLOC 3
{"_dballoc"},
#define NL_NMBLOCK 4
{"_nmblock"},
{ 0 },
};
#else
#ifdef sun
char* nl_names[] = {
#define NL_STRST 0
"_strst",
#define NL_RBSIZE 1
"_rbsize",
#define NL_NDBLKS 2
"_ndblks",
#define NL_DBALLOC 3
"_dballoc",
#define NL_NMBLOCK 4
"_nmblock",
"",
};
struct nlist nl[sizeof(nl_names)/sizeof(char*)];
#endif /* sun */
#endif /* sys5 */
#endif /* M_XENIX */
ushort rbsize[NCLASS];
short cnt[NCLASS];
struct dbalcst dballoc[NCLASS];
int total, ndblock, nmblock, fd, sleeptime = 1;
#ifndef sun
struct var v;
#endif
typedef struct {
alcdat stream;
alcdat queue;
alcdat mblock;
alcdat dblock;
alcdat dblk[NCLASS];
} Strstat;
Strstat strst;
/*
* It's the main thing...
*/
main(ac, av)
char** av;
{
int* p;
int i;
int done();
for (i=1; i<ac; ++i) {
if (av[i][0] == '-')
if (isdigit(av[i][1]))
sleeptime = atoi(&av[i][1]);
}
/*
* get stuff from kmem
*/
# ifdef sun
for (i=0; i<sizeof(nl_names)/sizeof(char*); ++i)
nl[i].n_name = nl_names[i];
# endif
if (nlist(UNIX, nl))
error("nlist");
if (((fd = open(KMEM, 2)) == -1) && ((fd = open(KMEM, 0)) == -1))
error(KMEM);
readstuff();
# ifdef sun
for (i=0; i<NCLASS; ndblock += cnt[i++])
;
# else
for (i=0, p= &v.v_nblk4096; i<NCLASS; ndblock += (cnt[NCLASS - ++i] = *p++))
;
# endif
/*
* screen/mode stuff
*/
termcap();;
echo(0);
cbreak(1);
delay(0);
signal(SIGHUP, done);
signal(SIGINT, done);
signal(SIGQUIT, done);
signal(SIGTERM, done);
screen();
/*
* display until quit
*/
for ( ;; ) {
if (lseek(fd, nl[NL_STRST].n_value, 0) == -1)
error("lseek");
if (read(fd, &strst, sizeof(strst)) != sizeof(strst))
error("read");
dump(&strst);
for (i=0; i<sleeptime; ++i) {
if (input())
break;
sleep(1);
}
input();
}
}
/*
* look for commands; expect to be in cbreak mode
*/
input()
{
char c;
if (read(0, &c, 1) != 1)
return 0;
switch (c) {
case 'c':
clearstuff();
return 1;
case '':
screen();
return 1;
case 'q':
done();
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
sleeptime = c - '0';
Pause();
return 1;
case '?':
case 'h':
clear();
tprint("", 0, 0);
printf("\nOptions are\n");
printf("\tc\tclear total, max, and fail fields\n");
printf("\t^L\trefresh the screen\n");
printf("\tq\tquit\n");
printf("\tN\twhere N is a number 0..9 for the delay between refresh\n");
printf("Hit any key to continue....\n");
while (read(0, &c, 1) != 1)
sleep(1);
screen();
return 1;
default:
return 0;
}
}
/*
* clear the total, max, and fail fields
*
* N.B. assumes acldat is use, total, max, fail
* and there are NCLASS+4 acldat's in a strstat
*/
clearstuff()
{
static int clr[3];
register i;
clear();
lseek(fd, nl[NL_STRST].n_value+sizeof(int), 0);
for (i=0; i<NCLASS+4; ++i) {
if (write(fd, clr, sizeof(clr)) != sizeof(clr))
perror("write");
lseek(fd, sizeof(int), 1);
}
screen();
}
/*
* display the "once only" stuff
*/
screen()
{
char buf[200];
register i;
#if defined(M_XENIX) || defined(sys5)
struct utsname u;
#else
char host[100];
#endif
clear();
#if defined(M_XENIX) || defined(sys5)
uname(&u);
sprintf(buf, "Host=%s", u.nodename);
#else
gethostname(host, sizeof(host));
sprintf(buf, "Host=%s", host);
#endif
tprint(buf, 50, 0);
# if SAVECNT > 0
sprintf(buf, "%11s%5s%6s%6s%6s%6s%7s%8s%8s%8s",
"Resource", "Cnt", "Use", "Ave10", "Ave30", "Ave60", "Ave120", "Total",
"Max", "Fail");
# else
sprintf(buf, "%11s%5s%10s%8s%8s%8s",
"Resource", "Cnt", "Use", "Total", "Max", "Fail");
# endif
tprint(buf, 0, 2);
# ifdef sun
sprintf(buf, "%11s:%4s", "stream", "?");
tprint(buf, 0, 3);
sprintf(buf, "%11s:%4s", "queue", "?");
tprint(buf, 0, 4);
# else
sprintf(buf, "%11s:%4d", "stream", v.v_nstream);
tprint(buf, 0, 3);
sprintf(buf, "%11s:%4d", "queue", v.v_nqueue);
tprint(buf, 0, 4);
# endif
sprintf(buf, "%11s:%4d", "mblock", nmblock);
tprint(buf, 0, 5);
sprintf(buf, "%11s:%4d", "dblk totals", ndblock);
tprint(buf, 0, 6);
# if SAVECNT > 0
sprintf(buf, "%4s%4s%4s%4s%6s%6s%6s%6s%7s%8s%8s%8s",
"Size", "Cnt", "Med", "Low", "Use", "Ave10", "Ave30", "Ave60", "Ave120", "Total", "Max", "Fail");
# else
sprintf(buf, "%-4s%4s%4s%4s%4s%6s%8s%8s%8s",
"Mem", "Size", "Cnt", "Med", "Low", "Use", "Total", "Max", "Fail");
# endif
tprint(buf, 0, 8);
for (total=i=0; i<NCLASS; ++i) {
register lo = dballoc[i].dba_lo;
register med = dballoc[i].dba_med;
total += rbsize[i] * cnt[i];
# if SAVECNT > 0
sprintf(buf, "%4d %3d %3d %3d", rbsize[i], cnt[i], med, lo);
# else
sprintf(buf, "%3d %4d %3d %3d %3d", kbytes(rbsize[i] * cnt[i]),
rbsize[i], cnt[i], med, lo);
# endif
tprint(buf, 0, 9 + i);
}
sprintf(buf, "Buffers (used/total) = ");
tprint(buf, 0, 23);
Pause();
}
Pause()
{
char buf[40];
sprintf(buf, "Pause=%d", sleeptime);
tprint(buf, 0, 0);
}
# if SAVECNT == 0
/*
* display the information, called once per second (about)
*
* No averaging version
*/
dump(s)
register struct strstat* s;
{
char buf[80];
register i, mem = 0;
static calls = 0;
sprintf(buf, "%6d%8d%8d%8d",
s->stream.use, s->stream.total, s->stream.max, s->stream.fail);
tprint(buf, 20, 3);
sprintf(buf, "%6d%8d%8d%8d",
s->queue.use, s->queue.total, s->queue.max, s->queue.fail);
tprint(buf, 20, 4);
sprintf(buf, "%6d%8d%8d%8d",
s->mblock.use,
s->mblock.total, s->mblock.max, s->mblock.fail);
tprint(buf, 20, 5);
sprintf(buf, "%6d%8d%8d%8d",
s->dblock.use, s->dblock.total, s->dblock.max, s->dblock.fail);
tprint(buf, 20, 6);
for (i=0; i<NCLASS; ++i) {
mem += s->dblk[i].use * rbsize[i];
sprintf(buf, "%6d%8d%8d%8d",
s->dblk[i].use, s->dblk[i].total, s->dblk[i].max, s->dblk[i].fail);
tprint(buf, 20, 9 + i);
}
sprintf(buf, "%d/%d Kbytes", kbytes(mem), kbytes(total));
tprint(buf, 23, 23);
calls++;
sprintf(buf, "Calls=%d", calls);
tprint(buf, 10, 0);
}
# else
/*
* display the information, called once per second (about)
*
* Averaging version
*/
dump(s)
register struct strstat* s;
{
char buf[80];
register i, j, b10, b30, b60, b120, mem = 0;
static struct strstat pst[SAVECNT];
static struct strstat sum10, sum30, sum60, sum120;
static calls = 0;
j = calls % SAVECNT;
b10 = (j + SAVECNT - 10) % SAVECNT;
b30 = (j + SAVECNT - 30) % SAVECNT;
b60 = (j + SAVECNT - 60) % SAVECNT;
b120 = (j + SAVECNT - 120) % SAVECNT;
memcpy(&pst[j], s, sizeof(struct strstat));
addstrst(s, &sum10);
addstrst(s, &sum30);
addstrst(s, &sum60);
addstrst(s, &sum120);
if (!calls) {
mulstrst(&sum10, 10);
mulstrst(&sum30, 30);
mulstrst(&sum60, 60);
mulstrst(&sum120, 120);
for (i=1; i<SAVECNT; ++i)
memcpy(&pst[i], s, sizeof(struct strstat));
}
else {
substrst(&pst[b10], &sum10);
substrst(&pst[b30], &sum30);
substrst(&pst[b60], &sum60);
substrst(&pst[b120], &sum120);
}
sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d",
s->stream.use,
sum10.stream.use / 10, sum30.stream.use / 30, sum60.stream.use / 60,
sum120.stream.use / 120,
s->stream.total, s->stream.max, s->stream.fail);
tprint(buf, 16, 3);
sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d",
s->queue.use,
sum10.queue.use / 10, sum30.queue.use / 30, sum60.queue.use / 60,
sum120.queue.use / 120,
s->queue.total, s->queue.max, s->queue.fail);
tprint(buf, 16, 4);
sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d",
s->mblock.use,
sum10.mblock.use / 10, sum30.mblock.use / 30, sum60.mblock.use / 60,
sum120.mblock.use / 120,
s->mblock.total, s->mblock.max, s->mblock.fail);
tprint(buf, 16, 5);
sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d",
s->dblock.use,
sum10.dblock.use / 10, sum30.dblock.use / 30, sum60.dblock.use / 60,
sum120.dblock.use / 120,
s->dblock.total, s->dblock.max, s->dblock.fail);
tprint(buf, 16, 6);
for (i=0; i<NCLASS; ++i) {
mem += s->dblk[i].use * rbsize[i];
sprintf(buf, "%6d%6d%6d%6d%7d%8d%8d%8d",
s->dblk[i].use,
sum10.dblk[i].use / 10, sum30.dblk[i].use / 30,
sum60.dblk[i].use / 60, sum120.dblk[i].use / 120,
s->dblk[i].total, s->dblk[i].max, s->dblk[i].fail);
tprint(buf, 16, 9 + i);
}
sprintf(buf, "%d/%d Kbytes", kbytes(mem), kbytes(total));
tprint(buf, 23, 23);
calls++;
sprintf(buf, "Calls=%d", calls);
tprint(buf, 10, 0);
}
/*
* add a to be, leave result in b
*
* N.B. Assumes that elements are ints
*/
addstrst(a, b)
register alcdat* a;
register alcdat* b;
{
register i;
for (i=sizeof(struct strstat)/sizeof(*a); i--; (b++)->use += (a++)->use)
;
}
/*
* subtract a from be, result in b
*/
substrst(a, b)
register alcdat* a;
register alcdat* b;
{
register i;
for (i=sizeof(struct strstat)/sizeof(*a); i--; (b++)->use -= (a++)->use)
;
}
/*
* multiply a by b, result in a
*/
mulstrst(a, b)
register alcdat* a;
register int b;
{
register i;
for (i=sizeof(struct strstat)/sizeof(*a); i--; (a++)->use *= b)
;
}
# endif /* SAVECNT */
/*
* nasty cleanup
*/
error(s)
char* s;
{
perror(s);
done();
}
/*
* nice cleanup
*/
done()
{
echo(1);
cbreak(0);
delay(1);
tprint("", cols -1, lines - 1);
printf("\n");
exit(0);
}
/*
* read all once only stuff from kmem
*/
readstuff()
{
register i;
if (!nl[NL_RBSIZE].n_value) {
printf("%s: no value\n", nl[NL_RBSIZE].n_name);
exit(1);
}
if (!nl[NL_STRST].n_value) {
printf("%s: no value\n", nl[NL_STRST].n_name);
exit(1);
}
# ifdef sun
if (!nl[NL_NDBLKS].n_value) {
printf("%s: no value\n", nl[NL_NDBLKS].n_name);
# else
if (!nl[NL_V].n_value) {
printf("%s: no value\n", nl[NL_V].n_name);
# endif
exit(1);
}
if (!nl[NL_DBALLOC].n_value) {
printf("%s: no value\n", nl[NL_DBALLOC].n_name);
exit(1);
}
if (!nl[NL_NMBLOCK].n_value) {
printf("%s: no value\n", nl[NL_NMBLOCK].n_name);
exit(1);
}
if (lseek(fd, nl[NL_RBSIZE].n_value, 0) == -1)
error("lseek");
if (read(fd, rbsize, sizeof(rbsize)) != sizeof(rbsize))
error("read");
# ifdef sun
if (lseek(fd, nl[NL_NDBLKS].n_value, 0) == -1)
error("lseek");
if (read(fd, cnt, sizeof(cnt)) != sizeof(cnt))
error("read");
# else
if (lseek(fd, nl[NL_V].n_value, 0) == -1)
error("lseek");
if (read(fd, &v, sizeof(v)) != sizeof(v))
error("read");
# endif
# ifdef sun
if (lseek(fd, nl[NL_DBALLOC].n_value, 0) == -1)
error("lseek");
if (read(fd, &nl[NL_DBALLOC].n_value, sizeof(int)) != sizeof(int))
error("read");
# endif
if (lseek(fd, nl[NL_DBALLOC].n_value, 0) == -1)
error("lseek");
if (read(fd, dballoc, sizeof(dballoc)) != sizeof(dballoc))
error("read");
if (lseek(fd, nl[NL_NMBLOCK].n_value, 0) == -1)
error("lseek");
if (read(fd, &nmblock, sizeof(nmblock)) != sizeof(nmblock))
error("read");
# ifdef sun
if (lseek(fd, nl[NL_STRST].n_value, 0) == -1)
error("lseek");
if (read(fd, &nl[NL_STRST].n_value, sizeof(int)) != sizeof(int))
error("read");
# endif
}
//E*O*F sw.c//
echo x - term.h
cat > "term.h" << '//E*O*F term.h//'
# ifndef _TERM_H_
#include <stdio.h>
#include <sgtty.h>
char *getenv();
char *tgetstr();
char PC;
short ospeed;
short lines;
short cols;
char ceolbuf[20];
char clbuf[20];
char pcbuf[20];
char cmbuf[20];
#undef putchar
int putchar();
char term_buf[1024];
# endif _TERM_H_
//E*O*F term.h//
echo x - termcap.c
cat > "termcap.c" << '//E*O*F termcap.c//'
/*
* copyright (C) 1986 by Larry McVoy
* MUST be distributed in source form only.
*/
# include "term.h"
char* tgoto();
/*------------------------------------------------------------------15/Dec/86-*
* init_term - read in the termcap stuff
*----------------------------------------------------------------larry mcvoy-*/
termcap()
{
char *cp = getenv("TERM");
char *foo;
char garbage[10];
struct sgttyb tty;
gtty(1, &tty);
ospeed = tty.sg_ospeed;
if (cp == (char *) 0)
return -1;
if (tgetent(term_buf, cp) != 1)
exit(1);
foo = garbage;
foo = tgetstr("pc", &foo);
if (foo)
PC = *foo;
foo = clbuf;
tgetstr("cl", &foo);
foo = ceolbuf;
tgetstr("ce", &foo);
foo = cmbuf;
tgetstr("cm", &foo);
lines = tgetnum("li");
cols = tgetnum("co");
return 0;
}
/* clear to end of line */
ceol(col, row)
{
char *foo = tgoto(cmbuf, col, row);
write(1, foo, strlen(foo));
tputs(ceolbuf, 1, putchar);
}
/* clear screen */
clear()
{
tputs(clbuf, lines, putchar);
}
/*------------------------------------------------------------------15/Dec/86-*
* tputchar(c, col, row) - put a single char on the screen
*
* Inputs ----> (char), (int), (int)
*
* Bugs ------> Assumes that the coords make sense
*
* Revisions:
*----------------------------------------------------------------larry mcvoy-*/
tputchar(c, col, row)
char c;
{
register char* foo;
foo = tgoto(cmbuf, col, row);
tputs(foo, lines, putchar);
write(1, &c, 1);
}
/*------------------------------------------------------------------15/Dec/86-*
* tprint(s, col, row) - put a string on the screen
*
* Inputs ----> (char), (int), (int)
*
* Results ---> Puts the string out iff it will fit.
*
* Revisions:
*----------------------------------------------------------------larry mcvoy-*/
tprint(s, col, row)
register char* s;
{
register char* foo;
if (row > lines || col > cols)
return -1;
if (strlen(s) > cols - col)
return -2;
foo = tgoto(cmbuf, col, row);
tputs(foo, lines, putchar);
return write(1, s, strlen(s));
}
/* fake putchar for tputs */
putchar(c)
char c;
{
write(1, &c, 1);
}
//E*O*F termcap.c//
echo Possible errors detected by \'wc\' [hopefully none]:
temp=/tmp/shar$$
trap "rm -f $temp; exit" 0 1 2 3 15
cat > $temp <<\!!!
17 53 336 Makefile
57 120 878 mode.c
63 359 2087 sw.1
563 1521 12403 sw.c
19 36 278 term.h
104 281 2203 termcap.c
823 2370 18185 total
!!!
wc Makefile mode.c sw.1 sw.c term.h termcap.c | sed 's=[^ ]*/==' | diff -b $temp -
exit 0
---
Larry McVoy, Sun Microsystems (415) 336-7627 ...!sun!lm or lm at sun.com
More information about the Comp.unix.wizards
mailing list