[comp.sources.misc] v07i116: nlist -- display and monitor kernel variables and data structures

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (08/06/89)

Posting-number: Volume 7, Issue 116
Submitted-by: chris@cetia.UUCP (Chris Bertin)
Archive-name: nlist

[Amazing.  It looks like this should be easy to adapt to almost any flavor
of Unix; it comes with SunOS 3.5, 4.3BSD, and System V R2 and R3 templates.
Horray for portable programming!  ++bsa]

This is 'nlist'. There is a README file and manual that explain what it does,
as well as a few examples of the things you can do with it. I tried posting
it to comp.sources.unix, but nothing seems to be going on with that group any
more (the last posting I received from comp.sources.unix is from the first
of July...). I am not even getting replies to my mail to Rich Salz.

Anyway, if you have any questions, please email them to me.

Chris

---------------------------------- cut here -------------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README
#	MANIFEST
#	nlist.8
#	nlist.c
#	Makefile
#	chars.sun
#	config.sh
#	dnlc.sun
#	file.sun
#	file.sys53
#	minfo.sys5
#	mount.sun
#	mount.sys53
#	syserr.sys5
#	sysinfo1.sys5
#	sysinfo2.sys5
#	sysinfo3.sys5
#	sysinfo4.sys5
#	sysinfo5.sys5
#	sar-s
# This archive created: Wed Aug  2 17:52:40 1989
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README'
then
	echo shar: "will not over-write existing file 'README'"
else
cat << \SHAR_EOF > 'README'

			     -- N L I S T --

To make 'nlist', type 'sh config.sh'. I have built it on SYSV.2, SYSV.3,
SunOs3.5, BSD4.3 and Mach, so I hope it will compile and run without
problems on your system.

There are examples for Sun machines in *.sun and examples for System V
machines in *.sys5*. BSD4.3 systems should be able to run some of the Sun
scripts. If these scripts don't work on your machine, it means
the structures are not quite identical to the ones I have here. It should
be pretty easy to fix them, once you've read the manual. If symbols are not
found, try adding or removing the leading symbol name underscore.

If you have any comments, you can Email them to me.

Chris Bertin	| -- CETIA -- 150, Av Marcelin Berthelot, Z.I. Toulon-Est
+33(94)212005	| 83088 Toulon Cedex, France
		| inria!cetia!chris
SHAR_EOF
fi
if test -f 'MANIFEST'
then
	echo shar: "will not over-write existing file 'MANIFEST'"
else
cat << \SHAR_EOF > 'MANIFEST'
README
MANIFEST
nlist.8
nlist.c
Makefile
chars.sun
config.sh
dnlc.sun
file.sun
file.sys53
minfo.sys5
mount.sun
mount.sys53
syserr.sys5
sysinfo1.sys5
sysinfo2.sys5
sysinfo3.sys5
sysinfo4.sys5
sysinfo5.sys5
sar-s
SHAR_EOF
fi
if test -f 'nlist.8'
then
	echo shar: "will not over-write existing file 'nlist.8'"
else
cat << \SHAR_EOF > 'nlist.8'
.TH NLIST 8 LOCAL
.SH NAME
.ad b
nlist \- display and monitor kernel variables and data structures
.SH SYNOPSIS
.B nlist
[-aptux] [-f format] [-h header] [-l loops] [-m mem]
.if n \{\
.br
.ti 2c\}
[-n namelist] [-s sleep] [-D debug]  symbol|formula ...
.SH DESCRIPTION
.I Nlist
reads kernel variables and displays them. It can be used to monitor variables
or to display arrays of data structures or linked lists. It is also a full
report generator, with a built-in calculator. It is very useful for kernel
debugging (monitoring of debug variables, for example) and can even provide
a user-configurable replacement for utilities like
.I sar(1),
on SYSV systems.
See the EXAMPLE section below.
.PP
The toggle options are:
.TP 12
.I -a
Auto increment the address by the size of the previous read. This is used
to step through a structure with a minimum amount of address calculations.
The auto-increment option is disabled if the cell contains any address
calculation. See the SYMBOLS, FORMULAS and EXAMPLES sections.
.TP
.I -p
Prepend the '0x' prefix, if the display type is in hexadecimal, or the '0'
prefix if it is in octal. Essentially the '#' option of printf.
.TP
.I -t
Print the time in the first column (ala 'sar').
.TP
.I -u
Print unique lines only (excluding the time, if the -t flag is present).
.TP
.I -x
Normally,
.B nlist
exits if a value doesn't fit in the print format, or if a symbol is not
found in the name list. If this option is present,
.B nlist
will carry on silently. See the
.B format
option below. Also, if this flag is set and if a read failure occurs,
the output will be a string of question marks (?).
.PP
The other options are:
.TP 12
.I -f format
The argument is a simplified printf format: a number followed by a valid
format type. See the FORMATS section below.
.TP
.I -h header
If the argument is zero (0), no title will be printed. If it is one (1),
a title is printed on the first line only. If it is any other value, the
title will be reprinted every ${LINES} (24 by default).
.TP
.I -l loops
Normally,
.B nlist
loops forever. The argument is the number of lines that will
be displayed, excluding the headers.
.TP
.I -m memdev
Read kernel memory from
.B memdev
instead of the default
.B /dev/kmem.
.TP
.I -n namelist
Read the namelist from
.B namelist
instead of
.B /unix
or
.B /vmunix.
.TP
.I -s sleep
The argument overrides the default one (1) second pause between line output.
.TP
.I -D debug
This field is a level of debug. One (1) prints information about the read's,
two (2) prints the control structure before it is analyzed, and
three (3) prints it after it has been analyzed. See source code for details.
.PP
The arguments following these options are kernel symbols or formulas. They
will be refered to as
.I cells
in this manual.
.SH SYMBOLS
A symbol is a valid name found in the kernel namelist. A read size may be
supplied after the symbol name, following the special character
.B '|'.
If the print format is not a string (see FORMATS below), only sizes
of 1, 2 and 4 may be given, meaning the symbol to be read is 1, 2 or 4 bytes
big. The default is 4.
.PP
Symbols may be also followed by a positive or negative offset indicated by
.I +value
or
.I -value.
.PP
They may also be followed by a positive or negative loop increment, following
the special character
.B '>':
.I >value,
or
.I >+value,
or
.I >-value.
.PP
Loop incrementing is done for each line, after all the arguments have been
evaluated. It differs from the 'auto-increment' option which will increment
the address of each cell with the read size of the preceding one, before the
read is done.
.PP
These values may be given in any order.
.PP
If a symbol name is followed by an exclamation mark ('!'), the read will
be done once only and the cell won't be printed.
.SH FORMULAS
.B Nlist
has a built-in stacking (reverse Polish) calculator. Arithmetic
operations may be performed on any current value, preceding value or address
of any field (cell). The first non-option argument is cell one (1).
.PP
The special characters are:
.TP 6
.I '#'
means the current value of a cell. That value is the value read from memory
or whatever may have been placed there by a
calculation.
.TP
.I '^'
means the value of a cell on the previous pass.
.TP
.I '&'
means the address of a cell. The address is the memory location from which
data is read.
.PP
The cell identification may be absolute or relative and the special
character '.' means the current cell. For example, '&.' is the read-address
for the current cell, '#1' is the current value of cell 1, '^+3' is
the value of the third cell to the right, on the previous pass, and '#-2' is
the current value of the second cell on the left.
.PP
The valid operations are: +, -, * or / for add, subtract, multiply or divide,
respectively. The operands may be integers, current cell values, previous
cell values or read-addresses.
.PP
Additionally, values or addresses may be initialized or recalculated on every
pass, using the
.B '!'
and the
.B '=',
special symbols respectively. For example:
.PP
.ti 10
.B "#. = &-1 #-2 +"
.PP
will set the current value to the sum of the read-address of the preceding
cell and the current value of the second cell on the left.
.PP
.ti 10
.B "^+1 ! 1"
.PP
will initialize the previous value of the cell on the right to one (1).
Note that initialization cells are not printed.
.PP
If the
.B -x
flag is set, divisions by zero (0) return an error instead of causing
.B nlist
to exit.
.PP
Floating point exceptions, except divisions by zero caught by the
calculator, are always fatal.
.SH CONDITIONAL EVALUATION
If a question mark ('?') is found it the argument, it indicates that
the read address for the current cell has been calculated by another cell
and that the read should take place only if the read-address is non-zero.
Following cells will be skipped as well and the value displayed will be a
string of dashes ('-'). Reading will resume on the next cell that contains
a real symbol, or on the next line, which ever comes first.
.SH FORMATS
The global print format is set to hexadecimal, 8 characters wide, by default.
It may be modified globally using the
.I -h
flag, or for each cell. The syntax of a format cell is: the special character
.B '%',
optionally followed by a positive value and followed by a identifier.
.PP
Formats supported are: %d, %u, %o, %x, %f (2 decimals), %s and %t. %t is
time format, meaning the value is printed using ctime(3).
.PP
The optional value is the width of the cell. If an overflow occurs,
.B nlist
will exit, unless the
.B -x
flag is set, in which case a string of asterisks (*) will be displayed.
.PP
If the format is decimal and if the result of a calculation is not an integer,
floating point format will be used automatically.
.SH HEADERS
Each cell may supply its own header, after the special character 
.B ':'.
If the header contains characters special to
.B nlist,
it must be quoted. If no header is supplied, the argument is printed.
.SH EXAMPLES
Here are now a few examples that will show most of the things
that can be done with
.B nlist:
.PP
Example 1
.PP
This command works on BSD and V7 systems that keep track of the
total number of characters input and output, in '_tk_nin' and '_tk_nout',
respectively.
.PP
.nf
        nlist -t -f 9d -s 2                     \\
                _tk_nin                         \\
                _tk_nout                        \\
                '#. = #1 #2 +  : Total'         \\
                '#1 100 * #3 / : "% Input"'     \\
                '#2 100 * #3 / : "% Output"'
.fi
.PP
Description, argument by argument:
.PP
The time will be printed at the start of each line (-t option), the print
mode will be decimal, 9 characters wide, for all the cells (-f 9d),
and the program will run continuously with a line of output
every 2 seconds (-s 2).
.PP
The value of '_tk_nin' will be read into cell 1, the value of '_tk_nout' into
cell 2, the sum of these 2 values will be placed in the current value
of cell 3.
.PP
Cells 4 and 5 will print out the percentages of inputs and outputs
respectively.
.PP
The result will be something like:
.PP
.TS
rrrrrr.
16:18:39	_tk_nin	_tk_nout	Total	% Input	% Output
16:18:39	19160	994283	1013443	1.89	98.11
16:18:41	19160	994343	1013503	1.89	98.11
16:18:43	19160	994403	1013563	1.89	98.11
16:18:45	19160	994463	1013623	1.89	98.11
.../...
.TE
.PP
Example 2
.PP
This command works on a Sun 3, with a release 3.5EXPORT. If the
mount structure hasn't changed on newer releases, it will work as well. It
should also work on generic 4.3 BSD systems.
.PP
This example uses different print formats, different read sizes,
loop increments and cell auto-increments.
.PP
.nf
        nlist -pax -s 0 -f 8x -l 4            \\
                '^. 1 +       : " #"    %2d'  \\
                '_mounttab>28 : Vfsp'         \\
                '  |1         : Maj     %3o'  \\
                '  |1         : Min     %3o'  \\
                '             : Devvp'        \\
                '             : Bufp'         \\
                '             : Qinod'        \\
                '             : Qflags  |2'   \\
                '             : Btime   %5d'  \\
                '             : Ftime   %5d'
.fi
.PP
Description, argument by argument:
.PP
Turn the 'prefix' option (-p), turn on the 'auto-increment' option (-a),
continue if an overflow occurs (-x), don't pause between line displays (-s 0),
use hexadecimal, 8 characters wide as a default print mode (-f 8x), and
print out 4 lines (-l 4).
.PP
The next argument, on the next line, will just print a header by taking the
value of the current cell on the previous pass (uninitialized, zero) and
adding one (1) to it. Its header will be ' #' and its print format 2 decimal
characters.
.PP
The next argument will cause the variable '_mounttab' to be read in and to
be displayed with the 'Vfsp' title. When all the arguments will be processed,
the address will be incremented by 28 (the size of the mount table
structure). The read-address will be incremented by 4 for the next read,
because auto-increment is turned on and because the read size is 4
bytes (default).
.PP
The next 2 arguments will read 1 byte each (half a 'dev_t' type, on BSD),
and display them as 'Maj' and 'Min', in octal, 3 characters wide.
.PP
The next 3 arguments are pointers, 4 bytes big. These cells are basically
empty cells with a title (':').
.PP
The following argument is a short and the last 2 are longs, displayed in
decimal, 6 characters wide.
.PP
The output will look something like this:
.PP
.if t \{\
.TS
rrrrrrrrrr.
#	m_vfsp	maj	min	devvp	bufp	qinod	qflags	btime	ftime
1	0x110b50	03	0	0x110afc	0x10213c	0	0	0	0
2	0x104400	07	0	0x1044f4	0x10125c	0	0	0	0
3	0x104424	03	03	0x110fc8	0x102208	0	0	0	0
4	0x104594	03	07	0x1044cc	0x1013f4	0	0	0	0
.TE\}
.if n \{\
.nf

#   m_vfsp maj min    devvp     bufp qinod qflags btime ftime
1 0x110b50  03   0 0x110afc 0x10213c     0      0     0     0
2 0x104400  07   0 0x1044f4 0x10125c     0      0     0     0
3 0x104424  03  03 0x110fc8 0x102208     0      0     0     0
4 0x104594  03  07 0x1044cc 0x1013f4     0      0     0     0
.fi\}
.PP
Example 3
.PP
This command will dump the 'dnlc' cache on a Sun. It uses the string output
format and the addressing feature needed to follow linked lists. It should
work without too many modifications on generic 4.3 systems.
.PP
.nf
        nlist -ax -h1 -s 0 -f6x -l 10         \\
                '_ncache        : Hshnxt'     \\
                '               : Hshprv'     \\
                '               : Lrunxt'     \\
                '               : Lruprv'     \\
                '               : Vp'         \\
                '               : Dp'         \\
                '|1             : Len  %3d'   \\
                '|15            : Name %15s'  \\        <== 8
                '               : Ucred'      \\
                '@&1 = #1'                             <== 10
.fi
.PP
Description of arguments not yet explained.
.PP
The argument on the 8th line displays a string of
characters, 15 bytes long.
.PP
The last (10th) argument shows how one can follow a linked list. The '@'
means that the cell won't be printed, '&1 = #1' sets
the address of the first cell to the value of the first cell.
.PP
Here is the result:
.PP
.if t \{\
.TS
rrrrrrrrr
rrrrrrrlr.
Hshnxt	Hshprv	Lrunxt	Lruprv	Vp	Dp	Len	Name	Ucred
a02e0	0	a051c	a0e38	a1db0	a1ac4	0	......\\.......\\	a1518
a1360	a051c	a1728	a0fc4	92b6c	9242e	3	usrxcvsub.c0or0	0
a1ddc	a02e0	a1964	a190c	92834	90668	11	usr.MC68010nd.o	0
a16d0	a1360	a138c	a0de0	95070	92b6c	5	spool.heds40.os	0
a17d8	a1ddc	a117c	a1200	96962	92766	3	ucbf.hub.cld.av	0
a0a9c	a16d0	a0860	a0390	93c52	97be4	7	nlist.oa1.c.old	0
a10cc	a17d8	a030c	a1d00	95a18	92dd6	2	ldrace.h.c30rc1	0
a0cd8	a0a9c	a1bcc	a093c	92c3a	95476	6	time.hh0p@svc.2	0
.../...
.TE\}
.if n \{\
.nf
Hshnxt Hshprv Lrunxt Lruprv    Vp    Dp Len            Name Ucred
 a02e0      0  a051c  a0e38 a1db0 a1ac4   0 ......\\.......\\ a1518
 a1360  a051c  a1728  a0fc4 92b6c 9242e   3 usrxcvsub.c0or0     0
 a1ddc  a02e0  a1964  a190c 92834 90668  11 usr.MC68010nd.o     0
 a16d0  a1360  a138c  a0de0 95070 92b6c   5 spool.heds40.os     0
 a17d8  a1ddc  a117c  a1200 96962 92766   3 ucbf.hub.cld.av     0
 a0a9c  a16d0  a0860  a0390 93c52 97be4   7 nlist.oa1.c.old     0
 a10cc  a17d8  a030c  a1d00 95a18 92dd6   2 ldrace.h.c30rc1     0
 a0cd8  a0a9c  a1bcc  a093c 92c3a 95476   6 time.hh0p@svc.2     0
 .../...
.fi\}
.PP
Example 4
.PP
The next command will show you how to use indirection. On BSD systems, the
variable '_file' is not the beginning of the file table, but a pointer to
the table. It will also show you how to follow a pointer inside a structure,
in this case, the 'ucred' pointer.
.PP
.nf
        nlist -a -s 0 -f7x -h 1 -l 10           \\
                '^. 1 +         : "  #"    %3d' \\
                '_file!'                        \\     <== 3
                '&+1 ! #-1'                     \\     <== 4
                '>26            : flag     %4o' \\     <== 5
                '|2             : type     %4x' \\
                '|2             : count    %6d' \\
                '|2             : msgcount %6d' \\
                '               : fops'         \\
                '               : fdata'        \\
                '               : offset   %6x' \\
                '               : cred'         \\
                '&. = #-1 |2    : Crref    %5d' \\     <== 13
                '|2             : Uid      %4d' \\
                '|2             : Gid      %4d'
.fi
.PP
Description of arguments not yet explained:
.PP
Line 3: The variable '_file' is read but the cell is not displayed because
of the trailing '!'.
.PP
Line 4: Set the read-address of the cell on the right to the value read in the
preceding cell.
.PP
Line 5: This cell's read-address has been initialized by the preceding cell.
On the next pass, it will be incremented by 26 bytes (the size of the file
structure).
.PP
Line 13: Here, we follow the the 'ucred' pointer and display the first
element from that structure. The next 2 cell will auto-increment from
that new address.
.PP
Here is the file table:
.PP
.if t \{\
.TS
rrrrrrrrrrrr.
#	flag	type	count	msgcou	fops	fdata	offset	cred	Crref	Uid	Gid
1	1	1	0	0	7a0f8	970a0	9e2	1047d0	5	110	100
2	1	1	0	0	7a0f8	93ebc	29b	1047d0	5	110	100
3	1	1	0	0	7a0f8	2183ac	1047d6	1047d0	5	110	100
4	1	1	0	0	7a0f8	94c6a	80a7c	1047d0	5	110	100
5	1	1	0	0	7a0f8	94c6a	8a6a4	1047d0	5	110	100
6	3	2	0	0	799fc	0	0	110b24	97	0	0
7	2	1	0	0	7a0f8	93ebc	29b	1047d0	5	110	100
.../...
.TE\}
.if n \{\
.nf
# flag type count msgcou    fops   fdata offset    cred Crref Uid Gid
1    1    1     0      0   7a0f8   970a0    9e2  1047d0     5 110 100
2    1    1     0      0   7a0f8   93ebc    29b  1047d0     5 110 100
3    1    1     0      0   7a0f8  2183ac 1047d6  1047d0     5 110 100
4    1    1     0      0   7a0f8   94c6a  80a7c  1047d0     5 110 100
5    1    1     0      0   7a0f8   94c6a  8a6a4  1047d0     5 110 100
6    3    2     0      0   799fc       0      0  110b24    97   0   0
7    2    1     0      0   7a0f8   93ebc    29b  1047d0     5 110 100
.../...
.fi\}
.PP
Example 5:
.PP
Dump the mount table on a SYSV.3 88K Motorola system ('dev_t' is a long on
that system):
.PP
.nf
        nlist -ap -s 0 -f8x -l 4                \\
                '^. 1 +: #%2d'                  \\
                '_mount |2 >36  : Flags  %6o'   \\
                '|2             : Type   %4d'   \\
                '               : Bsize  %5o'   \\
                '               : Dev    %6o'   \\
                '               : Bufp'         \\
                '               : Inop'         \\
                '               : Mountp'       \\
                '|2             : Rflags'       \\
                '               : Namep'        \\
                '               : Bcount %6d'
.fi
.PP
Example 6:
.PP
This will do what 'sar -s' does, on SYSV:
.PP
.nf
        echo "`uname -a`    `date +%D`\\n"

        nlist -xat -f7d -s 1 -h1                \\
                '@sysinfo'                      \\
                '@'                             \\
                '@'                             \\
                '@'                             \\
                '@#. = #1 ^1 -'                 \\
                '@#. = #2 ^2 -'                 \\
                '@#. = #3 ^3 -'                 \\
                '@#. = #4 ^4 -'                 \\
                '@#-1 #-2 #-3 #-4 +++'          \\
                '#6 100 * #-1 /: "%usr"'        \\
                '#7 100 * #-2 /: "%sys"'        \\
                '#8 100 * #-3 /: "%wio"'        \\
                '#5 100 * #-4 /: "%idle"'
.fi
.PP
Finally, an example of the neat things you can do with
.B nlist:
.PP
.nf
        nlist -h 1 -f 12u -l 31 -s 0 '^+1 ! 1' '^2 2 *:"Powers of 2"'
.fi
.SH FILES
/unix or /vmunix       name list
.br
/dev/kmem              kernel memory
.SH BUGS
Conditional evaluation is very basic.
.PP
The syntax is not exactly user-friendly.
.SH AUTHOR
Chris Bertin.
SHAR_EOF
fi
if test -f 'nlist.c'
then
	echo shar: "will not over-write existing file 'nlist.c'"
else
cat << \SHAR_EOF > 'nlist.c'
#ifndef lint
static char ID[] = "@(#)nlist.c	2.1 Copyright (C) 1989 Chris Bertin 89/07/31";
#endif
/*
 * nlist -- display and monitor kernel variables. Dump kernel structures.
 *
 *	This program is free for redistribution as long as this header
 *	remains unchanged.
 *
 *				Copyleft Chris Bertin, July 1989
 *
 */
#include	<stdio.h>
#ifdef	V7
#include	<a.out.h>
#else
#include	<nlist.h>
#endif	/* V7 */
#ifdef	SYSV
#include	<string.h>
#else
#include	<strings.h>
#endif	/* SYSV */
#include	<ctype.h>
#include	<memory.h>
#include	<time.h>
#include	<sys/types.h>
#include	<sys/signal.h>
#ifdef	SYSV
#include	<sys/var.h>
#endif

#define NLISTBUG	/* see comment in fixnlist() */

#ifndef	SYSV
#define DEF_NLIST	"/vmunix"	/* default namelist */
#else
#define DEF_NLIST	"/unix"
#endif
#define DEF_KMEM	"/dev/kmem"	/* default memory device */
#define DEF_SLEEP	1		/* default pause between displays */
#define DEF_WIDTH	8		/* default print width */
#define DEF_LOOPS	-1		/* default number of loops (infinite) */
#define DEF_RDSIZE	4		/* default read size */

/* misc return values */
#define R_OK		0		/* OK */
#define R_NOPRINT	1		/* value was computed but don't print */
#define R_FLOAT		2		/* print value in float format */
#define R_SKIP		3		/* skip this entry */
#define R_ERR		-1		/* print error value */

/* misc */
#define CDEPTH		4		/* max number of push for formulas */
#define NDEC		2		/* number of decimals for float */
#define NSPACES		1		/* number of spaces between fields */

/* header special characters */
#define H_NOPRINT	'@'		/* don't print if first character */
#define H_POST		':'		/* real header start character */
#define H_SET		'='		/* set field to value */
#define H_INIT		'!'		/* initialize field to value */
#define H_SIZE		'|'		/* read size (real symbols only) */
#define H_FMT		'%'		/* field print width */
#define H_INCR		'>'		/* increment address on next loop */
#define H_NONZERO	'?'		/* quit if this address is 0 */

/* field identifiers in formulas. (For example: #1: value of field1, */
/* ^+1: previous value of next field, &.: nlist address in current field) */
#define H_CURVAL	'#'		/* current value in field number */
#define H_PREVAL	'^'		/* previous value in field number */
#define H_ADDR		'&'		/* the nlist address of the field */
#define H_CURFIELD	'.'		/* current field */

/* values for -h flag */
#define HD_NONE		0		/* don't print header */
#define HD_ONCE		1		/* print header once only */
#define HD_PAGE		2		/* print header every $LINES lines */

typedef float	val_t;
typedef	struct nlist	nl_t;

typedef	struct field {
	nl_t	*f_nl;			/* nlist entry pointer */
	char	*f_arg;			/* original argument */
	char	*f_form;		/* formula or symbol */
	char	*f_post;		/* title */
	char	*f_fmt;			/* print format (dec, oct, hex...) */
	char	*f_buf;			/* read buffer for string printf's */
	short	 f_flags;		/* features */
	short	 f_width;		/* print width */
	short	 f_rdsize;		/* read size */
	off_t	 f_offset;		/* address offset */
	off_t	 f_incr;		/* address increment */
	val_t	 f_curval;		/* current value */
	val_t	 f_preval;		/* previous value */
} fld_t;

/* f_flags bits */
#define F_TITLE		0x1		/* Title is present */
#define F_SYM		0x2		/* valid symbol */
#define F_SET		0x4		/* set value */
#define F_INIT		0x8		/* set value once only */
#define F_DONE		0x10		/* init done */
#define F_QUOTED	0x20		/* header is quoted. Don't clean it */
#define F_NONZERO	0x40		/* quit if address is 0 */
#define F_TIME		0x80		/* print the value as date/time */
#define F_READOK	0x100		/* OK to read size other that 1,2,4 */

extern	char *optarg, *getenv(), *parsearg(), *alloc(), *calloc();
extern	char *prfmt(), *spaces(), *cleanbuf();
extern	char *sys_errlist[];
extern	int errno, optind;
extern	double ceil();

/* formats */
char	decfmt[] =	"%*d";		/* decimal print mode */
char	unsfmt[] =	"%*u";		/* unsigned decimal print mode */
char	octfmt[] =	"%*o";		/* octal print mode */
char	hexfmt[] =	"%*x";		/* hex print mode */
char	flofmt[] =	"%*.*f";	/* floating point format */
char	strfmt[] =	"%*.*s";	/* string print format */
#ifndef	V7
char	extoctfmt[] =	"%#*o";		/* hex with '0' prefix */
char	exthexfmt[] =	"%#*x";		/* octal with '0x' prefix */
#endif
#define	DEF_FMT		hexfmt		/* default print format */

val_t	calc[CDEPTH];			/* calculator stack */
int	cdepth;				/* calculator stack depth */

int	tim, uniq, fmtprefix, debug, autoincr;	/* flags with default 0 */
int	strict = 1;			/* flags with default 1 */
char	*buf, *hdr;			/* line and header buffers */
char	*pname;				/* argv[0], for error messages */

char	*kmemf	= DEF_KMEM;
char	*nlistf	= DEF_NLIST;
char 	*fmt	= DEF_FMT;
int	slp	= DEF_SLEEP;
int	width	= DEF_WIDTH;
int	nloops	= DEF_LOOPS;
int	header	= HD_PAGE;

main (argc, argv)
	char		**argv;
{
	register int	mem, i;
	int		nsymbols, maxw, prtime;
	register fld_t	*baseflp, *flp;
	nl_t		*nl;
	char		*p;
	int		onfpe();

	pname = *argv;
	while ((i = getopt(argc, argv, "aD:f:h:l:m:n:ps:tux")) != -1) {
		switch (i) {
		case 'a':
			autoincr++;
			break;
		case 'D':
			debug = stoi(&optarg, "debug level", 0, 0);;
			break;
		case 'f':
			fmt = prfmt(&optarg, &width, &prtime, 0, optarg);
			break;
		case 'h':
			header = stoi(&optarg, "header value", 1, 0);
			break;
		case 'l':
			nloops = stoi(&optarg, "number of loops", 0, 0);
			break;
		case 'm':
			kmemf = optarg;
			break;
		case 'n':
			nlistf = optarg;
			break;
		case 'p':
			fmtprefix++;
			break;
		case 's':
			slp = stoi(&optarg, "sleep time", 0, 0);
			break;
		case 't':
			tim++;
			break;
		case 'u':
			uniq++;
			break;
		case 'x':
			strict = 0;
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;
	if (argc <= 0)
		usage();
	if ((mem = open (kmemf, 0)) < 0)
		perrexit (kmemf);
	baseflp = (fld_t *) alloc((int) (argc * sizeof (fld_t)));
	nl = (nl_t *) alloc((int) ((argc + 2) * sizeof (nl_t)));
	for (flp = baseflp, maxw = nsymbols = 0, i = 0; i < argc; i++, flp++) {
		p = parsearg(flp, *(argv + i));
		if (flp->f_fmt != NULL)
			maxw += flp->f_width + NSPACES;
		if (flp->f_flags & F_SYM) {
			flp->f_nl = nl + nsymbols;
			nsymbols++;
#ifdef	V7
			(void) strncpy(flp->f_nl->n_name, p, SYMLENGTH);
#else
			flp->f_nl->n_name = p;
#endif
			continue;
		}
		if (autoincr && i > 0 && flp->f_incr == 0 && *p == '\0') {
			sprintf(p = alloc(32), "%c%c%c%c-1 %d +", H_ADDR,
				H_CURFIELD, H_SET, H_ADDR, (flp - 1)->f_rdsize);
			flp->f_flags |= F_SET;
		}
		flp->f_nl = nl + (argc - i + nsymbols + 1);
		flp->f_form = p;
	}
	if (maxw == 0) {
		fprintf(stderr, "%s: nothing to print\n", pname);
		exit (1);
	}
	if (nsymbols) {
		donlist(nl, mem, nsymbols);
		for (i = 0, flp = baseflp; i < argc; i++, flp++)
			if (flp->f_offset && flp->f_nl->n_value)
				flp->f_nl->n_value += flp->f_offset;
	}
	if (header != HD_NONE) {
		hdr = alloc(maxw);
		for (p = hdr, i = 0, flp = baseflp; i < argc; i++, flp++) {
			if (flp->f_fmt == NULL)
				continue;
			sprintf(p, "%s%*.*s", spaces(NSPACES), flp->f_width,
			    flp->f_width, flp->f_post? flp->f_post: flp->f_arg);
			p += flp->f_width + NSPACES;
		}
	}
	(void) signal(SIGFPE, onfpe);
	for (;;) {
		if (process(baseflp, argc, mem, maxw, prtime)) {
			outputline();
			if (--nloops == 0)
				break;
		}
		(void) sleep((unsigned) slp);
	}
	return (0);
}

/*
 * Process the arguments once. Return 1 if 'buf' is to be printed.
 */
process(baseflp, max, mem, maxw, prtime)
	register fld_t	*baseflp;
	register int	max, mem, maxw;
{
	register int	i;
	register char	*p;
	register fld_t	*flp;
	static char	*lastbuf, *tmp;

	if (buf == NULL) {
		buf = alloc(maxw);
		lastbuf = alloc(maxw);
		tmp = alloc(BUFSIZ);
	}
	for (flp = baseflp, buf[0] = '\0', i = 0; i < max; i++, flp++) {
		tmp[0] = '\0';
		switch (analyze(baseflp, flp, max, mem)) {
		case R_NOPRINT:
			continue;
		case R_SKIP:
			sprintf(tmp, strfmt, flp->f_width, flp->f_width,
				"--------------------");
			break;
		case R_FLOAT:
			sprintf(tmp, flofmt, flp->f_width, NDEC, flp->f_curval);
			break;
		case R_OK:
			p = flp->f_fmt;
			if (p == strfmt) {
				if (prtime || (flp->f_flags & F_TIME)) {
					time_t t = (time_t) flp->f_curval;
					(void) strncpy(flp->f_buf, ctime(&t),
						flp->f_width);
				}
				sprintf(tmp, strfmt, flp->f_width, flp->f_width,
					cleanbuf(flp->f_buf, flp->f_width));
				break;
			}
#ifndef	V7
			if (fmtprefix && (long) flp->f_curval) {
				if (flp->f_fmt == hexfmt)
					p = exthexfmt;
				else if (flp->f_fmt == octfmt)
					p = extoctfmt;
			}
#endif
			sprintf(tmp, p, flp->f_width, (long) flp->f_curval);
			break;
		case R_ERR:
			if (strict) {
				outputline();
				fprintf(stderr, "%s: symbol '%s' not found\n",
					pname, flp->f_nl->n_name);
				exit (1);
			}
			sprintf(tmp, strfmt, flp->f_width, flp->f_width,
				"????????????????????");
			break;
		}
		if (strlen(tmp) > flp->f_width) {
			if (strict) {
				outputline();
				fprintf(stderr, "%s: print overflow\n", pname);
				exit (1);
			}
			sprintf(tmp, strfmt, flp->f_width, flp->f_width,
				"********************");
		}
		(void) strcat(buf, spaces(NSPACES));
		(void) strcat(buf, tmp);
	}
	for (i = 0, flp = baseflp; i < max; i++, flp++) {
		flp->f_preval = flp->f_curval;
		if (flp->f_incr && flp->f_nl->n_value)
			flp->f_nl->n_value += flp->f_incr;
	}
	if (uniq) {
		if (strcmp(buf, lastbuf) == NULL)
			return (0);
		(void) strcpy(lastbuf, buf);
	}
	return(1);
}

/*
 * Print output buffer, adding title if needed. Exit if err is set.
 */
outputline()
{
	static int	lines, nlines;
	char		*p;

	if (buf == NULL || *buf == '\0')
		return;
	if (header != HD_NONE) {
		if (nlines == 0)
			lines = nlines = (p = getenv("LINES"))? atoi(p): 24;
		if (lines == nlines) {
			printline(hdr);
			lines = 2;
		}
		lines++;
		if (header == HD_ONCE)
			header = HD_NONE;
	}
	printline(buf);
}

/*
 * Print a line, with the current time if -t flag present.
 */
printline(line)
	char			*line;
{
	time_t			t;
	register struct tm	*tm;

	if (tim) {
		(void) time(&t);
		tm = localtime(&t);
		printf("%2d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
	}
	printf("%s\n", line);
	fflush(stdout);
}

/*
 * Parse an argument and initialize the control structure. Return a pointer
 * to a symbol without any trailers, if the argument is a symbol name.
 */
char *
parsearg(flp, str)
	register fld_t	*flp;
	char		*str;
{
	char		*p;
	register char	*pp, *xp;
	register char	c;
	int		endsym, w, prtime;

	flp->f_rdsize = DEF_RDSIZE;
	flp->f_width = width;
	flp->f_fmt = fmt;
	(void) skipspaces(&str);
	if (*str == H_NOPRINT) {
		flp->f_fmt = NULL;
		str++;
		(void) skipspaces(&str);
	}
	if (isalpha(*str) || *str == '_')
		flp->f_flags |= F_SYM;
	flp->f_arg = str;
	(void) strcpy(pp = alloc(strlen(str) + 1), str);
	for (endsym = 0, p = pp; *p; ) {
		(void) skipspaces(&p);
		xp = p;
		switch (*p++) {
		case H_POST:
			endsym++;
			(void) skipspaces(&p);
			if ((c = *p) != '\'' && c != '"') {
				flp->f_post = p;
				break;
			}
			flp->f_post = ++p;
			flp->f_flags |= F_QUOTED;
			while (*p != c)
				if (*p++ == '\0') {
					fprintf(stderr,
					"%s: missing quote in header -- %s\n",
						pname, pp);
					exit (1);
				}
			*p++ = '\0';
			break;
		case H_FMT:
			endsym++;
			flp->f_fmt = prfmt(&p, &w, &prtime, 1, flp->f_arg);
			if (w)
				flp->f_width = w;
			if (flp->f_fmt == strfmt) {
				flp->f_buf = alloc(flp->f_width + 1);
				if (prtime)
					flp->f_flags |= F_TIME;
				else
					flp->f_flags |= F_READOK;
			}
			break;
		case H_SIZE:
			endsym++;
			flp->f_rdsize = stoi(&p, "read size", 0, 1);
			break;
		case H_INCR:
			endsym++;
			flp->f_incr = stoi(&p, "increment value", 1, 1);
			break;
		case H_SET:
			flp->f_flags |= F_SET;
			break;
		case H_INIT:
			*(p - 1) = (flp->f_flags & F_SYM)? '\0': H_SET;
			flp->f_flags |= (F_SET|F_INIT);
			flp->f_fmt = NULL;
			break;
		case '+':
		case '-':
			if (flp->f_flags & F_SYM)
				endsym++;
			--p;
			flp->f_offset = (val_t) stoi(&p, "offset", 1, 1);
			break;
		case H_NONZERO:
			endsym++;
			flp->f_flags |= F_NONZERO;
			break;
		default:
			continue;
		}
		if (! endsym)
			continue;
		*xp = '\0';
	}
	if (flp->f_buf == NULL)
		flp->f_buf = alloc(flp->f_rdsize);
	if ((flp->f_flags & F_QUOTED) == 0)
		cleanspaces(flp->f_post);
	cleanspaces(pp);
	cleanspaces(flp->f_arg);
	return (pp);
}

/*
 * Handle symbols, formulas, reads, calculator, etc...
 */
analyze(baseflp, flp, max, mem)
	register fld_t	*baseflp, *flp;
	register int	max, mem;
{
	fld_t		*nfp;
	char		*p, *form, *fmtp;
	register int	i;
	register val_t	*vp;
	int		prev, addr, seta;
	static int	skiptonext;

	if (flp == baseflp)
		skiptonext = 0;
	if (debug > 1)
		showfl(flp, flp - baseflp, "start analyze");
	if (flp->f_flags & F_DONE)
		return (R_NOPRINT);
	if (flp->f_flags & F_INIT)
		flp->f_flags |= F_DONE;
	prev = addr = seta = 0;
	fmtp = flp->f_fmt;
	p = form = flp->f_form;
	if (flp->f_flags & F_SYM) {
		skiptonext = 0;
		if (flp->f_nl->n_value == 0)
			return (R_ERR);
		flp->f_curval = (val_t) doread(flp->f_nl->n_name, kmemf, mem,
			flp->f_nl->n_value, flp->f_buf, flp->f_rdsize,
			(flp->f_flags & F_READOK));
		return ((fmtp == flofmt)? R_FLOAT: (fmtp? R_OK: R_NOPRINT));
	}
	for (i = 0; i < CDEPTH; i++)
		calc[i] = 0;
	cdepth = 0;
	vp = &flp->f_curval;
	if (flp->f_flags & F_SET) {
		nfp = baseflp + fn(baseflp, flp, &p, &prev, &seta, max, 0) - 1;
		if (seta == 0) {
			flp = nfp;
			vp = prev? &flp->f_preval: &flp->f_curval;
		}
		(void) skipspaces(&p);
		if (*p++ != H_SET)
			badf(form, --p, "invalid 'set' format");
	}
	*vp = 0;
	while (*p) {
		if (skipspaces(&p))
			continue;
		if (i = isdigit(*p))
			*vp = stoi(&p, "formula", 1, 1);
		else if (i = fn(baseflp, flp, &p, &prev, &addr, max, 1)) {
			if (addr)
				*vp =  (val_t) (baseflp + i - 1)->f_nl->n_value;
			else
				*vp = (prev? (baseflp + i - 1)->f_preval:
					     (baseflp + i - 1)->f_curval);
		}
		if (i) {
			if (push(*vp) == R_ERR)
				badf(form, p, "stack overflow");
			*vp = 0;
			continue;
		}
		switch (*p++) {
		case '+':
			calc[1] = calc[1] + calc[0];
			break;
		case '-':
			calc[1] = calc[1] - calc[0];
			break;
		case '*':
			calc[1] = calc[1] * calc[0];
			break;
		case '/':
			if (calc[0] != 0)
				calc[1] = calc[1] / calc[0];
			else if (strict)
				badf(form, --p, "zero divide");
			else
				return (R_ERR);
			break;
		default:
			badf(form, --p, "invalid operand");
		}
		*vp = 0;
		if (pop() == R_ERR)
			badf(form, --p, "stack empty");
	}
	if (*vp == 0)
		*vp = calc[0];
	if (seta)
		nfp->f_nl->n_value = (long) *vp;
	if (debug > 2)
		showfl(flp, flp - baseflp, "end analyze");
	if ((flp->f_flags & F_NONZERO) && flp->f_nl->n_value == 0)
		skiptonext++;
	if (skiptonext)
		return (fmtp? R_SKIP: R_NOPRINT);
	if (flp->f_nl->n_value)
		flp->f_curval = (val_t) doread(flp->f_nl->n_name, kmemf, mem,
			flp->f_nl->n_value, flp->f_buf, flp->f_rdsize,
			(flp->f_flags & F_READOK));
	if (fmtp == flofmt || (fmtp == decfmt &&
	    (val_t) ceil((double) flp->f_curval) != flp->f_curval))
		return (R_FLOAT);
	return (fmtp? R_OK: R_NOPRINT);
}

/*
 * Return field number. Set 'prev' and 'addr' flags, advance formula pointer.
 */
fn(baseflp, flp, p, prev, addr, max, errok)
	register fld_t	*baseflp, *flp;
	register char	**p;
	int		*prev, *addr;
{
	register int	id, plus;

	if (((*addr = (**p == H_ADDR)) == 0) &&
	    ((*prev = (**p == H_PREVAL)) == 0) && **p != H_CURVAL) {
		if (errok)
			return (0);
		badf(flp->f_form, *p, "not a field identifier");
	}
	(*p)++;
	if (**p == H_CURFIELD) {
		(*p)++;
		return ((flp - baseflp) + 1);
	}
	plus = (**p == '+');
	if ((id = stoi(p, "field number", 1, 1)) < 0 || (plus && id > 0))
		id = (int) (flp - baseflp) + 1 + id;
	if (id <= 0 || id > max) {
		char *pp;
		sprintf(pp = alloc((id / 10) + 2), "%d", id);
		badf(flp->f_form, pp, "invalid field number");
	}
	return (id);
}

/*
 * Convert a string into a positive or negative int. Handles hex and octal.
 * Advance pointer.
 */
stoi(p, what, negok, errok)
	char		*what;
	register char	**p;
{
	int		n, newval, neg, base;

	neg = 0;
	base = 10;
	(void) skipspaces(p);
	if (**p == '+' || (negok && (neg = (**p == '-'))))
		(*p)++;
	if (! strncmp(*p, "0x", 2)) {
		base = 16;
		(*p) += 2;
	}
	else if (**p == '0') {
		base = 8;
		(*p)++;
	}
	for (newval = 0; **p; (*p)++) {
		if ((n = conv(*p, base, what, errok)) < 0)
			break;
		newval = (newval * base) + n;
	}
	(void) skipspaces(p);
	return (neg? -newval: newval);
}

/*
 * Convert an ascii character into a digit.
 */
conv(p, base, what, errok)
	char		*p, *what;
{
	register char	c;

	if (isdigit(c = *p))
		return (c - '0');
	if (isupper(c))
		c = tolower(c);
	if (base == 16 && c >= 'a' && c <= 'f')
		return (10 + c - 'a');
	if (errok)
		return (-1);
	fprintf(stderr, "%s: illegal %s (%s)\n", pname, what, p);
	usage();
	/* NOTREACHED */
}

/*
 * Advance pointer to next non-space character. Return 1 pointer changed.
 */
skipspaces(p)
	register char	**p;
{
	if (! isspace(**p))
		return (0);
	while (isspace(**p))
		(*p)++;
	return (1);
}

/*
 * Remove trailing spaces from string.
 */
cleanspaces(str)
	register char	*str;
{
	if (str == NULL || *str == '\0')
		return;
	while (*str)
		str++;
	--str;
	while (isspace(*str))
		*str-- = '\0';
}

/*
 * Replace unprintable characters in a buffer by dots ('.').
 */
char *
cleanbuf(p, n)
	register char	*p;
{
	register char	*pp;

	for (pp = p; --n >= 0; pp++)
		if (! isprint(*pp))
			*pp = '.';
	return (p);
}

/*
 * Handle a format string. Return a format pointer and the width in 'width'.
 * Advance pointer.
 */
char *
prfmt(p, w, prtime, errok, str)
	char	**p;
	char	*str;
	int	*w, *prtime;
{
	*prtime = 0;
	if ((*w = stoi(p, "print width", 0, 1)) == 0)
		*w = DEF_WIDTH;
	switch (**p) {
	case 'd':
		return (decfmt);
	case 'f':
		return (flofmt);
	case 'o':
		return (octfmt);
	case 't':
		*prtime = 1;
		/* fall thru */
	case 's':
		return (strfmt);
	case 'u':
		return (unsfmt);
	case 'x':
		return (hexfmt);
	}
	if (! isalpha(**p) && errok) {
		(*p)++;
		return (DEF_FMT);
	}
	fprintf(stderr, "%s: invalid print format in '%s' (%c)\n", pname,
		str, **p);
	outputline();
	exit (1);
	/* NOTREACHED */
}

/*
 * Push argument onto calculator stack.
 */
push(n)
	register val_t	n;
{
	register int	i;

	if (cdepth++ == CDEPTH)
		return (R_ERR);
	for (i = cdepth; --i >= 0; )
		calc[i] = i? calc[i - 1]: n;
	return (R_OK);
}

/*
 * Pop one argument from calculator stack.
 */
pop()
{
	register int	i;

	if (--cdepth <= 0)
		return (R_ERR);
	for (i = 1; i <= cdepth; i++)
		if ((calc[i - 1] = calc[i]) == 0)
			break;
	return (R_OK);
}

/*
 * Do 'nlist' and validate name list. Validation is very basic. Note that
 * one extra 'nlist' entry was allocated to handle the additional symbol.
 */
donlist(nl, mem, max)
	register nl_t	*nl;
{
	char		*p;
	int		nproc, size;
#ifdef	SYSV
	struct		var v;

#ifdef	UNDERSCORESYM
	(nl + max)->n_name = "_v";
#else
	(nl + max)->n_name = "v";
#endif
	p = (char *) &v;
	size = sizeof (v);
#else	/* SYSV */
#ifdef	V7
	strncpy((nl + max)->n_name, "_nproc", SYMLENGTH);
#else
#ifdef	UNDERSCORESYM
	(nl + max)->n_name = "_nproc";
#else
	(nl + max)->n_name = "nproc";
#endif
#endif	/* V7 */
	p = (char *) &nproc;
	size = sizeof nproc;
#endif	/* SYSV */
	if (nlist (nlistf, nl) < 0) {
		fprintf(stderr, "%s: nlist(%s) failed -- ", pname, nlistf);
		perrexit("");
	}
	if ((nl + max)->n_value == 0) {
		fprintf(stderr, "%s: symbol '%s' not found in %s\n", pname,
			(nl + max)->n_name, nlistf);
		exit (1);
	}
	(void) doread((nl + max)->n_name, kmemf, mem,
		(off_t) (nl + max)->n_value, p, size, 1);
#ifdef	SYSV
	nproc = v.v_proc;
#endif
	if (nproc > 4096 || nproc < 3) {
		fprintf(stderr, "Namelist out of date\n");
		exit (1);
	}
#ifdef	NLISTBUG
	fixnlist(nl, max);
#endif
}

#ifdef	NLISTBUG
/*
 * If a symbol is more than once in the list, all but the first one will have
 * an n_value of zero. This normally doesn't matter, but 'nlist' can ask for
 * symbol+4 and symbol+8, for example, in which case the second one fails.
 * This is a bug present in all the nlist(3) I have tried.
 */
fixnlist(nl, max)
	register nl_t	*nl;
	register int	max;
{
	register nl_t	*np, *npp;

	for (np = nl; np < nl + (max - 1) ; np++)
		for (npp = np + 1; npp < nl + max; npp++)
			if (! strcmp(npp->n_name, np->n_name)) {
				npp->n_value = np->n_value;
				break;
			}
}
#endif	/* NLISTBUG */

/*
 * Generic read routine. If read size is  1, 2, 4 bytes, return that value,
 * else return 0. If okdiff is 0, don't allow read sizes != 1, 2, 4.
 */
doread(what, from, mem, offset, into, size, okdiff)
	char	*what, *from, *into;
	off_t	offset;
{
	if (debug)
		showread(what, from, offset, size, -1);
	(void) lseek(mem, offset, 0);
	if (read(mem, into, (unsigned) size) != size) {
		showread(what, from, offset, size, errno);
		if (strict) {
			outputline();
			exit (1);
		}
	}
	switch (size) {
	case sizeof (char):
		return ((int) *into);
	case sizeof (short):
		return ((int) *(short *) into);
	case sizeof (int):
		return ((int) *(int *) into);
	default:
		if (okdiff)
			return (0);
		outputline();
		fprintf(stderr, "%s: Invalid read size: %d\n", pname, size);
		exit (1);
	}
	/* NOTREACHED */
}

/*
 * Debug and error routine. Show read arguments.
 */
showread(what, from, offset, size, err)
	char	*what, *from;
	off_t	offset;
{
	fprintf(stderr, "%s: read ", pname);
	if (what)
		fprintf(stderr, "'%s', ", what);
	fprintf(stderr, "%d bytes @ offset 0x%x in %s", size, offset, from);
	if (err >= 0)
		fprintf(stderr, " -- %s", sys_errlist[err]);
	fprintf(stderr, "\n");
}

/*
 * Debug routine. Show the whole control structure.
 */
showfl(flp, n, str)
	register fld_t	*flp;
	char		*str;
{
	fprintf(stderr, "  %s: fld %d, nl %x, buf '%s', name '%s', addr %x\n",
		str, n, flp->f_nl, flp->f_buf, flp->f_nl->n_name,
		flp->f_nl->n_value);
	fprintf(stderr, "  arg '%s', form '%s', post '%s', fmt '%s'\n",
		flp->f_arg, flp->f_form, flp->f_post, flp->f_fmt);
	fprintf(stderr, "  f 0x%x, w %d, rd %d, o %d, i %d, cur %d, pre %d\n",
		flp->f_flags, flp->f_width, flp->f_rdsize, flp->f_offset,
		flp->f_incr, (long) flp->f_curval, (long) flp->f_preval);
}

/*
 * Allocate 'n' bytes on the heap.
 */
char *
alloc(n)
{
	char	*a;

	if ((a = calloc((unsigned) n, (unsigned) 1)) == 0)
		perrexit("calloc");
	return (a);
}

/*
 * Build an array of spaces, return the address.
 */
char *
spaces(n)
{
	static char	*sp;
	register char	*p;

	if (sp == NULL) {
		sp = alloc(n + 1);
		for (p = sp; --n >= 0; p++)
			*p = ' ';
	}
	return (sp);
}

badf(form, msg1, msg2)
	char	*form, *msg1, *msg2;
{
	outputline();
	fprintf(stderr, "%s: invalid formula '%s'", pname, form);
	if (msg1 && *msg1)
		fprintf(stderr, " (%s)", msg1);
	if (msg2 && *msg2)
		fprintf(stderr, " -- %s", msg2);
	fprintf(stderr, "\n");
	exit (1);
}

perrexit(str)
	char	*str;
{
	perror(str);
	exit (1);
}

/*ARGSUSED*/
onfpe(n)
	int	n;
{
	outputline();
	fprintf(stderr, "%s: Floating poing exception\n", pname);
	exit (1);
}

usage()
{
	fprintf(stderr, "Usage: %s [-aptux] [-f format] [-h header] [-l loops] [-m mem]\n\t[-n namelist] [-s sleep] [-D debug] symbol|formula...\n", pname);
	exit (1);
}
SHAR_EOF
fi
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
# Makefile for nlist

all: nlist man

nlist: nlist.o
	$(CC) $(LDFLAGS) -o $@ $@.o -lm

clean:
	rm -f core *.o

clobber: clean
	rm -f nlist

man:
	tbl < nlist.8 | nroff -man > nlist.man
SHAR_EOF
fi
if test -f 'chars.sun'
then
	echo shar: "will not over-write existing file 'chars.sun'"
else
cat << \SHAR_EOF > 'chars.sun'
#! /bin/sh

echo "Showing character I/O statistics"

nlist -t -s 2 -f9d \
	'_tk_nin	:	"Chars in"' \
	'_tk_nout	:	"Chars out"' \
	'#. = #1 #2 +	:	 Total' \
	'#1 100 * #3 /	:	"% Input"'  \
	'#2 100 * #3 /	:	"% Output"' 
SHAR_EOF
fi
if test -f 'config.sh'
then
	echo shar: "will not over-write existing file 'config.sh'"
else
cat << \SHAR_EOF > 'config.sh'
#! /bin/sh

echo "Checking configuration and building 'nlist'"

CFLAGS="-O"
if [ -f /unix ]; then
	echo "This looks like a System 5 system"
	CFLAGS="${CFLAGS} -DSYSV"
	NAMELIST=/unix
	getmain() grep -c '_main[ 	]'
elif [ -f /vmunix ]; then
	echo "This looks like a BSD system"
	NAMELIST=/vmunix
	getmain() grep -c '_main$'
else
	echo "No /unix and no /vmunix. What system are you?" >&2
	exit 1
fi

if [ `nm ${NAMELIST} | getmain` -gt 0 ]; then
	CFLAGS="${CFLAGS} -DUNDERSCORESYM"
	MSG=prepends
else
	MSG="doesn't prepend"
fi

echo "It looks like the loader ${MSG} an underscore to symbol names"
echo "If this is not right, 'nlist' may fail. Build it manually"

echo ""
echo "Making 'nlist'"

make nlist CFLAGS="${CFLAGS}"
SHAR_EOF
fi
if test -f 'dnlc.sun'
then
	echo shar: "will not over-write existing file 'dnlc.sun'"
else
cat << \SHAR_EOF > 'dnlc.sun'
#! /bin/sh

echo "Dumping 100 entries of the dnlc (ncache) structures,
following the 'hash_next' pointer"

nlist -ax -h1 -s 0 -f6x -l 10 \
	'_ncache	: Hshnxt' \
	'		: Hshprv' \
	'		: Lrunxt' \
	'		: Lruprv' \
	'		: Vp' \
	'		: Dp' \
	'|1		: Len%3d' \
	'|15		: Name%15s' \
	'		: Ucred' \
	'@&1 = #1'
SHAR_EOF
fi
if test -f 'file.sun'
then
	echo shar: "will not over-write existing file 'file.sun'"
else
cat << \SHAR_EOF > 'file.sun'
#! /bin/sh

echo "Dumping 10 entries of the file table. Note that _file is the ADDRESS of
the file table and not the file table itself"

# description (lines following the 'nlist' line)
# line 1:	 header
# line 2:	 read '_file' once
# line 3:	 set address of next field to content of previous field
# line 4:	 read that and increment by 26 next time
# line 5:	 read rest of structure with auto increment

nlist -a -s 0 -f7x -h 1 -l 10           \
	'^. 1 +		:"  #"     %3d' \
	'_file!'                        \
	'&+1!#-1'                       \
	'>26		: flag     %4o' \
	'|2		: type     %6x' \
	'|2		: count    %6d' \
	'|2		: msgcount %6d' \
	'		: fops'         \
	'		: fdata'        \
	'		: offset   %6x' \
	'		: cred'         \
	'&. = #-1 |2	: Crref    %5d' \
        '|2		: Uid      %4u' \
        '|2		: Gid      %4u'

SHAR_EOF
fi
if test -f 'file.sys53'
then
	echo shar: "will not over-write existing file 'file.sys53'"
else
cat << \SHAR_EOF > 'file.sys53'
#! /bin/sh

echo "Dumping 50 entries of the file table"

nlist -ap -s 0 -fd -h 2 -l 50 \
	'^. 1 +:  #%3d' \
	'_file>12|1:Flag%4o' \
	'&.=&-1 2+|2:Count%5u' \
	':"Inode"%10x' \
	':"Offset"%10d'
SHAR_EOF
fi
if test -f 'minfo.sys5'
then
	echo shar: "will not over-write existing file 'minfo.sys5'"
else
cat << \SHAR_EOF > 'minfo.sys5'
#! /bin/sh

echo "Dumping the 'minfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f8d -s 2			\
	'minfo		: FreeM %5d'	\
	'		: FreeS %5d'	\
	'		: Vflt'		\
	'		: Demd'		\
	'		: Swap  %5d'	\
	'		: Cache'	\
	'		: File'		\
	'		: Pfault'	\
	'		: CopyWr'	\
	'		: Steal'	\
	'		: Freed'	\
	'		: UmodS'	\
	'		: UmodF'
SHAR_EOF
fi
if test -f 'mount.sun'
then
	echo shar: "will not over-write existing file 'mount.sun'"
else
cat << \SHAR_EOF > 'mount.sun'
#! /bin/sh

	echo "Dumping 10 entries of the mount table"


	nlist -p -aa -s 0 -f 10x -l 10		\
		'^. 1 +	: " #"%2d'		\
		'_mounttab>28	: Vfsp'		\
		'  |1		: Maj	  %3o'	\
		'  |1		: Min	  %3o'	\
		'		: Devvp'	\
		'		: Bufp'		\
		'		: Qinod'	\
		'		: Qflags  |2'	\
		'		: Btime   %6d'	\
		'		: Ftime   %6d'
SHAR_EOF
fi
if test -f 'mount.sys53'
then
	echo shar: "will not over-write existing file 'mount.sys53'"
else
cat << \SHAR_EOF > 'mount.sys53'

SHAR_EOF
fi
if test -f 'syserr.sys5'
then
	echo shar: "will not over-write existing file 'syserr.sys5'"
else
cat << \SHAR_EOF > 'syserr.sys5'
#! /bin/sh

echo "Dumping the 'syserr' structure. Unique lines only, every 2 seconds"

nlist -atu -f7d -s 2 \
	'syserr		:InoOvf' \
	'		:FileOvf' \
	'		:TextOvf' \
	'		:ProcOvf' \
	'		:Silo' \
	'		:CrdRds' \
	'		:Alerts' \
	'		:Faults' \
	'		:Timeouts'
SHAR_EOF
fi
if test -f 'sysinfo1.sys5'
then
	echo shar: "will not over-write existing file 'sysinfo1.sys5'"
else
cat << \SHAR_EOF > 'sysinfo1.sys5'
#! /bin/sh

echo "Dumping part of the 'sysinfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f8d -s 2 \
	'sysinfo	:CPU_IDLE' \
	'		:CPU_USER' \
	'		:CPU_KERN' \
	'		:CPU_WAIT' \
	'		:WAIT_IO' \
	'		:WAIT_SWP' \
	'		:WAIT_PIO'
SHAR_EOF
fi
if test -f 'sysinfo2.sys5'
then
	echo shar: "will not over-write existing file 'sysinfo2.sys5'"
else
cat << \SHAR_EOF > 'sysinfo2.sys5'
#! /bin/sh

echo "Dumping part of the 'sysinfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f8d -s 2 \
	'sysinfo+28	:Breads' \
	'		:Bwrites' \
	'		:Lreads' \
	'		:Lwrites' \
	'		:Phreads' \
	'		:Phwrites' \
	'		:Swapin' \
	'		:Swapout' \
	'		:Bswapin' \
	'		:Bswapout'
SHAR_EOF
fi
if test -f 'sysinfo3.sys5'
then
	echo shar: "will not over-write existing file 'sysinfo3.sys5'"
else
cat << \SHAR_EOF > 'sysinfo3.sys5'
#! /bin/sh

echo "Dumping part of the 'sysinfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f8d -s 2 \
	'sysinfo+68	:PSwtches' \
	'		:Syscall' \
	'		:SReads' \
	'		:SWrites' \
	'		:SForks %6d' \
	'		:SExecs %6d' \
	'		:RunQ' \
	'		:RunOcc' \
	'		:SwpQ %5d' \
	'		:SwpOcc %5d'
SHAR_EOF
fi
if test -f 'sysinfo4.sys5'
then
	echo shar: "will not over-write existing file 'sysinfo4.sys5'"
else
cat << \SHAR_EOF > 'sysinfo4.sys5'
#! /bin/sh

echo "Dumping part of the 'sysinfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f9d -s 2 \
	'sysinfo+108		:Iget' \
	'			:Namei' \
	'			:Dirblks' \
	'			:Readch' \
	'			:Writech' \
	'#-2 100 * #-1 #-2 + /	: "% Reads"
SHAR_EOF
fi
if test -f 'sysinfo5.sys5'
then
	echo shar: "will not over-write existing file 'sysinfo5.sys5'"
else
cat << \SHAR_EOF > 'sysinfo5.sys5'
#! /bin/sh

echo "Dumping part of the 'sysinfo' structure. Unique lines only, every 2 seconds"

nlist -atu -f8d -s 2 \
	'sysinfo+128	:RcvInts' \
	'		:XmtInts' \
	'		:MdmInts' \
	'		:RawCh' \
	'		:CanCh' \
	'		:OutCh' \
	'		:Msg %4d' \
	'		:Sema %4d'
SHAR_EOF
fi
if test -f 'sar-s'
then
	echo shar: "will not over-write existing file 'sar-s'"
else
cat << \SHAR_EOF > 'sar-s'
#! /bin/sh

LOOPS=${1:-100}
echo "This will do what 'sar -s 1 1 ${LOOPS}' does"

echo "`uname -a`    `date +%D`\n"

nlist -xat -f7u -s 1 -h1 -l ${LOOPS}		\
	'@sysinfo'				\
	'@'					\
	'@'					\
	'@'					\
	'@#1 ^1 -'				\
	'@#2 ^2 -'				\
	'@#3 ^3 -'				\
	'@#4 ^4 -'				\
	'@#-1 #-2 #-3 #-4 +++'			\
	'#6 100 * #-1 /: "%usr"'		\
	'#7 100 * #-2 /: "%sys"'		\
	'#8 100 * #-3 /: "%wio"'		\
	'#5 100 * #-4 /: "%idle"'		| \
	awk 'BEGIN {l=0; u=0; s=0; w=0; i=0}	\
		   {printf "%8s %7s %7s %7s %7s\n", $1, $2, $3, $4, $5} \
		   $2 != "%usr" {l += 1; u += $2; s += $3; w += $4; i += $5} \
	     END   {printf "\nAverage  %7d %7d %7d %7d\n", u/l, s/l, w/l, i/l}'

SHAR_EOF
fi
exit 0
#	End of shell archive
---------------------------------- cut here -------------------------------
-- 
Chris Bertin	| -- CETIA -- 150, Av Marcelin Berthelot, Z.I. Toulon-Est
+33(94)212005	| 83088 Toulon Cedex, France
		| inria!cetia!chris