[comp.lang.c] C structure printing program

tchrist@convexe.uucp (Tom Christiansen) (12/17/89)

I've many times wanted to know just exactly how a structure gets
laid out it memory.  Sometimes this is because I want to use adb 
on the kernel and check out some data structure.  Sometimes it's
because I'd like to see what kind of padding is happening on different
architectures.   Sometimes it's to see how to properly unpack a 
binary structure in perl.  Rather than hacking up a new program 
every time I want to figure out what a new structure looks like, 
I wrote "pstruct", which dumps out C struct and/or union declarations
along with their hex offsets from the start of the structure.
It's a perl script that reads C source and produces more C source.
It comes with an example and a man page.

The biggest bother is getting any include files right.  More work
could be done here to go straight to the final output without having
to munge for include files, but I found myself needing to know more
and more C syntax to do that (e.g. is this a typedef that I need to 
spit out later?) so I quit trying.

May you find this useful.

--tom

#!/bin/sh
#    This is a shell archive.
#    Run the following text with /bin/sh to extract.

sed -e 's/^X//' << \EOFMARK > pstruct.example
X
X% /lib/cpp /usr/include/sys/user.h | pstruct -v > user.c
X% ex +/#include user.c
Xd
Xa
X#include <sys/param.h>
X#include <sys/type.h>
X#include <sys/user.h>
X.
Xwq
X% cc user.c 
X% a.out
Xstruct rusage {
X    struct timeval ru_utime;                 00000000 rusage.ru_utime
X    struct timeval ru_stime;                 00000008 rusage.ru_stime
X    long ru_maxrss;                          00000010 rusage.ru_maxrss
X    long ru_ixrss;                           00000014 rusage.ru_ixrss
X    long ru_idrss;                           00000018 rusage.ru_idrss
X    long ru_isrss;                           0000001c rusage.ru_isrss
X    long ru_minflt;                          00000020 rusage.ru_minflt
X    long ru_majflt;                          00000024 rusage.ru_majflt
X    long ru_nswap;                           00000028 rusage.ru_nswap
X    long ru_inblock;                         0000002c rusage.ru_inblock
X    long ru_oublock;                         00000030 rusage.ru_oublock
X    long ru_msgsnd;                          00000034 rusage.ru_msgsnd
X    long ru_msgrcv;                          00000038 rusage.ru_msgrcv
X    long ru_nsignals;                        0000003c rusage.ru_nsignals
X    long ru_nvcsw;                           00000040 rusage.ru_nvcsw
X    long ru_nivcsw;                          00000044 rusage.ru_nivcsw
X    struct timeval ru_exutime;               00000048 rusage.ru_exutime
X};
Xstruct cvxrusage {
X    struct timeval ru_utime;                 00000000 cvxrusage.ru_utime
X    struct timeval ru_stime;                 00000008 cvxrusage.ru_stime
X    long ru_maxrss;                          00000010 cvxrusage.ru_maxrss
X    long ru_ixrss;                           00000014 cvxrusage.ru_ixrss
X    long ru_idrss;                           00000018 cvxrusage.ru_idrss
X    long ru_isrss;                           0000001c cvxrusage.ru_isrss
X    long ru_minflt;                          00000020 cvxrusage.ru_minflt
X    long ru_majflt;                          00000024 cvxrusage.ru_majflt
X    long ru_nswap;                           00000028 cvxrusage.ru_nswap
X    long ru_inblock;                         0000002c cvxrusage.ru_inblock
X    long ru_oublock;                         00000030 cvxrusage.ru_oublock
X    long ru_msgsnd;                          00000034 cvxrusage.ru_msgsnd
X    long ru_msgrcv;                          00000038 cvxrusage.ru_msgrcv
X    long ru_nsignals;                        0000003c cvxrusage.ru_nsignals
X    long ru_nvcsw;                           00000040 cvxrusage.ru_nvcsw
X    long ru_nivcsw;                          00000044 cvxrusage.ru_nivcsw
X    struct timeval ru_exutime;               00000048 cvxrusage.ru_exutime
X    unsigned long long ru_utotal;            00000050 cvxrusage.ru_utotal
X    unsigned long long ru_usamples;          00000058 cvxrusage.ru_usamples
X    unsigned long long ru_stotal;            00000060 cvxrusage.ru_stotal
X    unsigned long long ru_ssamples;          00000068 cvxrusage.ru_ssamples
X    unsigned long ru_deadlocks;              00000070 cvxrusage.ru_deadlocks
X    unsigned long ru_ncpucsw;                00000074 cvxrusage.ru_ncpucsw
X    unsigned long ru_nvflts;                 00000078 cvxrusage.ru_nvflts
X    unsigned long long ru_time;              0000007c cvxrusage.ru_time
X    unsigned long ru_filler[7];              00000084 cvxrusage.ru_filler
X};
Xstruct cvxprusage {
X    struct timeval pru_stime;                00000000 cvxprusage.pru_stime
X    struct timeval pru_utime;                00000008 cvxprusage.pru_utime
X    unsigned long long pru_utotal;           00000010 cvxprusage.pru_utotal
X    unsigned long long pru_usamples;         00000018 cvxprusage.pru_usamples
X    unsigned long long pru_stotal;           00000020 cvxprusage.pru_stotal
X    unsigned long long pru_ssamples;         00000028 cvxprusage.pru_ssamples
X    unsigned long long pru_filler[12];       00000030 cvxprusage.pru_filler
X};
Xstruct rlimit {
X    int rlim_cur;                            00000000 rlimit.rlim_cur
X    int rlim_max;                            00000004 rlimit.rlim_max
X};
Xstruct pattributes {
X    union {
X        struct {
X            int login:1;                     00000000 pattributes.un_pa.pa_b.login (00000001)
X            int pfixed:1;                    00000000 pattributes.un_pa.pa_b.pfixed (00000002)
X            int setaffin:1;                  00000000 pattributes.un_pa.pa_b.setaffin (00000004)
X            int pa_Fill0:29;                 00000000 pattributes.un_pa.pa_b.pa_Fill0 (fffffff8)
X        } pa_b;
X        int allbits;                         00000000 pattributes.un_pa.allbits
X    } un_pa;
X    unsigned char pattr_cpuaffin;            00000004 pattributes.pattr_cpuaffin
X    unsigned char pattr_Fill0[3];            00000005 pattributes.pattr_Fill0
X    int pattr_Fill1[2];                      00000008 pattributes.pattr_Fill1
X};
Xstruct user {
X    struct proc *u_procp;                    00000000 user.u_procp
X    char Unused;                             00000004 user.Unused
X    char u_comm[16 + 1];                     00000005 user.u_comm
X    bsema_t u_lck;                           00000016 user.u_lck
X    size_t u_tsize;                          00000018 user.u_tsize
X    size_t u_dsize;                          0000001c user.u_dsize
X    size_t u_ssize;                          00000020 user.u_ssize
X    struct vm_region *u_retext;              00000024 user.u_retext
X    struct vm_region *u_rebss;               00000028 user.u_rebss
X    struct vm_region *u_restack;             0000002c user.u_restack
X    time_t u_outime;                         00000030 user.u_outime
X    int (*u_signal[NSIG]) ();                00000034 user.u_signal
X    int u_sigmask[NSIG];                     000000b4 user.u_sigmask
X    int u_sigonstack;                        00000134 user.u_sigonstack
X    int u_sigintr;                           00000138 user.u_sigintr
X    int u_oldmask;                           0000013c user.u_oldmask
X    struct sigstack u_sigstack;              00000140 user.u_sigstack
X    struct file *u_ofile[NOFILE];            00000148 user.u_ofile
X    char u_pofile[NOFILE];                   00000548 user.u_pofile
X    int u_bcount[NOFILE];                    00000648 user.u_bcount
X    unsigned char u_errno[NOFILE];           00000a48 user.u_errno
X    short u_lastfile;                        00000b48 user.u_lastfile
X    char u_syncflag;                         00000b4a user.u_syncflag
X    char u_warp;                             00000b4b user.u_warp
X    struct vnode *u_cdir;                    00000b4c user.u_cdir
X    struct vnode *u_rdir;                    00000b50 user.u_rdir
X    struct tty *u_ttyp;                      00000b54 user.u_ttyp
X    dev_t u_ttyd;                            00000b58 user.u_ttyd
X    short u_cmask;                           00000b5a user.u_cmask
X    struct cvxrusage u_ru;                   00000b5c user.u_ru
X    struct rusage u_cru;                     00000bfc user.u_cru
X    long long u_ixrss;                       00000c4c user.u_ixrss
X    long long u_idrss;                       00000c54 user.u_idrss
X    long long u_isrss;                       00000c5c user.u_isrss
X    struct itimerval u_timer[3];             00000c64 user.u_timer
X    struct timempx u_exutime;                00000c94 user.u_exutime
X    struct timeval u_start;                  00000ca4 user.u_start
X    unsigned int u_FREE[1];                  00000cac user.u_FREE
X    unsigned short u_SFREE[1];               00000cb0 user.u_SFREE
X    short u_profmode;                        00000cb2 user.u_profmode
X    short u_acflag;                          00000cb4 user.u_acflag
X    struct rlimit u_rlimit[7];               00000cb8 user.u_rlimit
X    unsigned u_datasize;                     00000cf0 user.u_datasize
X    struct uprof {
X        union {
X            short *pr_sbase;                 00000cf4 user.u_prof.pr_base.pr_sbase
X            lprof_t *pr_lbase;               00000cf4 user.u_prof.pr_base.pr_lbase
X        } pr_base;
X        unsigned pr_size;                    00000cf8 user.u_prof.pr_size
X        unsigned pr_off;                     00000cfc user.u_prof.pr_off
X        unsigned pr_scale;                   00000d00 user.u_prof.pr_scale
X    } u_prof;
X    unsigned long u_hversion;                00000d04 user.u_hversion
X    unsigned long long u_oflags;             00000d08 user.u_oflags
X    int U_AVAIL[2];                          00000d10 user.U_AVAIL
X};
Xstruct ucred {
X    u_short cr_ref;                          00000000 ucred.cr_ref
X    short cr_uid;                            00000002 ucred.cr_uid
X    short cr_gid;                            00000004 ucred.cr_gid
X    int cr_groups[NGROUPS];                  00000008 ucred.cr_groups
X    short cr_ruid;                           00000028 ucred.cr_ruid
X    short cr_rgid;                           0000002a ucred.cr_rgid
X};
EOFMARK
sed -e 's/^X//' << \EOFMARK > pstruct.l
X.TH PSTRUCT 1L "20 November 1989"
X.de Sp
X.if t .sp .5v
X.if n .sp
X..
X.SH NAME
Xpstruct \- print out structure offsets
X.SH SYNOPSIS
X.B pstruct
X[
X.B \-v
X| 
X.B \-t
X] 
X[ file ... ]
X.br 
X.SH DESCRIPTION
X.I Pstruct
Xis a 
X.I perl
Xprogram that
Xreads each listed file, or 
X.I stdin
Xif none are given, 
Xand generates on 
X.I stdout
Xa 
X.I C
Xprogram that when compiled and run will list
Xthe hexidecimal offsets for the fields in all struct and
Xunion declarations in the original files.  Any
Xstructure elements that are actually bit-fields will have
Xthe necessary bit mask to access them printed out as well.
X.PP
XThe \fB-t\fP switch (trace only) will suppress generation
Xof the 
X.I C
Xprogram.  Instead, a properly indented
Xdump of the structures seen will be
Xoutput.  The \fB-v\fP switch (verbose mode) will generate
Xa 
X.I C
Xprogram that merges the structure dump with the offset
Xdump.
X.PP
X.I Pstruct 
Xignores all input except struct or union declarations.
XThis means that 
X.B .h
Xfiles work best as input.
XIt will ignore code outside of struct or union declarations, and it knows to 
Xskip over comments.  If the files listed on the command line
Xend in \s+1\fB.\s-1h\fP, a \fB#include\fP line for that file
Xwill be output at the top of the program.  Furthermore, if this
Xfile includes a pathname component that is either 
X\fBsys\fP or \fBh\fP, then 
X.I <sys/param.h> 
Xand 
X.I <sys/types.h>
Xwill also be \fB#include\fPd.  Nonethless, you still may have to edit the
X.I C
Xprogram to include other header files needed to resolve all
Xtype references.
X.SH RESTRICTIONS
X.I Pstruct
Xassumes that the 
X.I C
Xsource fed into it is syntatically correct and in Kernel Normal Form (KNF).
XIn particular, declaring a struct or
Xunion with the curly brace on the next line will cause that declaration
Xto be missed.
XPrograms that are not in KNF should be run through \fIindent\fP(1) before
Xbeing fed into \fIpstruct\fP.  
X.PP
XPstruct does not know 
X.I C 
Xpre-processor semantics, so it will skip
Xlines beginning with a sharp sign.  However, as this may cause incorrect
Xgeneration of structures due to included fields that would otherwise
Xbe \fBifdef\fPed out, you may wish to run complex include files through
X\fIcpp\fP(1) first.
X.ne 15
X.SH EXAMPLE
XHere is the 
X.I pattribute 
Xstructure definition from 
X.I <sys/resources.h>
X, which would be what 
X.I pstruct 
Xwould output given the 
X.B \-t
Xflag:
X.br
X.in +5n
X.nf
X\f(TA
Xstruct pattributes {
X    union {
X        struct {
X            int login:1;             
X            int pfixed:1;             
X            int setaffin:1;            
X            int pa_Fill0:29;
X        } pa_b;
X        int allbits;
X    } un_pa;
X    unsigned char pattr_cpuaffin;       
X    unsigned char pattr_Fill0[3];
X    int pattr_Fill1[2];
X};
X.in -5n
X.fi
X\fR
X.Sp
XWhen fed into \fIpstruct\fP, the appropriate 
X.B #include
Xdirectives placed into the resulting 
X.I C
Xprogram, and this program is compiled, it will produce this output:
X.Sp
X.ne 8
X.nf
X.in +5m
X\f(TA
X00000000 pattributes.un_pa.pa_b.login (00000001)
X00000000 pattributes.un_pa.pa_b.pfixed (00000002)
X00000000 pattributes.un_pa.pa_b.setaffin (00000004)
X00000000 pattributes.un_pa.pa_b.pa_Fill0 (fffffff8)
X00000000 pattributes.un_pa.allbits 
X00000004 pattributes.pattr_cpuaffin 
X00000005 pattributes.pattr_Fill0
X00000008 pattributes.pattr_Fill1
X.fi
X.in -5m
X\fR
X.PP
XIf the
X.B \-v
Xflag had been given, then the offsets would have been pasted
Xto the left of the appropriate lines from the structure 
Xdump as provided by the
X.B \-t
Xflag.  This form of output is best viewed on a terminal or
Xwindow with more than 80 columns.
X.SH DIAGNOSTICS
X.I    "usage: pstruct [-t / -v] [files]"
X.br
X.ti +4m
XAn unrecognized flag was supplied.
X.Sp
X.I "pstruct: can't have -v flag if -t flag already specified"
X.br
X.I "pstruct: can't have -t flag if -v flag already specified"
X.br
X.ti +4m
XThe two flags are mutually exclusive.
X.Sp
X.I "pstruct: too many bits in structure (%d)" 
X.br
X.in +4m
XYour structure definition included more than 32 in its bit-field
Xdeclarations.  The 
X.I C 
Xcompiler will probably not be pleased with this either.
X.in -4m
X.Sp
X.I "pstruct: missing top tag at %s"
X.br
X.I "pstruct: negative nesting level @ %s %s, tags: %s"
X.br
X.I "pstruct: unnamed (struct/union)"
X.br
X.in +4m
XThese errors should not occur in syntatically correct
X.I C
Xprograms.
X.in -4m
X.SH "SEE ALSO"
Xcc(1), cpp(1), indent(1), perl(1)
X.SH BUGS
X.I Pstruct
Xdoes not recognize aliases of the form
X.Sp
X.ti +.5i
X\f(TA#define w_retcode	w_T.w_Retcode\fP
X.SH AUTHOR
XTom Christiansen, \s-1CONVEX\s+1 Computer Corporation.
EOFMARK
sed -e 's/^X//' << \EOFMARK > pstruct.pl
X#!/usr/local/bin/perl
X
X( $iam = $0 ) =~ s:.*/::;
X
X# change this if the wordsize should ever change
X$BPW = 32;
X
Xwhile ($ARGV[0] =~ '^-') {
X    $_ = shift;
X    last if /^--/;
X    if (/^-v$/) {
X	$print_all++;
X	die "$iam: can't have -v flag if -t flag already specified\n"
X		if $trace_only;
X	next;
X    } 
X    if (/^-t$/) {
X	$trace_only++;
X	die "$iam: can't have -t flag if -v flag already specified\n"
X		if $print_all;
X	next;
X    }
X    die "usage: $iam [-t | -v] [files]\n";
X}
X
X@h_files = @ARGV;
X
X#
X# read all the (interesting) lines into @lines in LIFO order
X#
Xwhile ($line = &nextline) {
X    $line =~ s/\s+$//;
X    push(@lines,$line);
X} 
X$level = 0;   # this is the nesting level
X
X#
X# now process our input from the bottom up
X#
Xwhile ($_ = pop(@lines)) {
X    #
X    # \173 and \175 are innocuous euphemisms for {,} to let `%' work in vi.
X    #
X    if ( /\175\s*(\w*)[^;]*;/ ) {
X	#
X	# found end of struct/union declaration, so 
X	# read up the file looking for the structure tag
X	# (we need it for the top level, and will forget it 
X	#  if we're not at the outermost level)
X	#
X	$tag = $1;  # if we didn't get one, there'll be one at the top
X	$nest = 1;  # local nesting level while looking for tag
X
X	for ($i = $#lines; $i >= 0; $i--) {
X	    if ( $lines[$i] =~ /\175\s*(\w*)\s*;/ ) {
X		$nest++;  # another one of those nasties
X	    } elsif ($lines[$i] =~ /\b(struct|union)\s*(\w*)\s*\173/) { 
X		if (!--$nest) {
X		    # ok, we're on top, fill in tag if we missed it before
X		    # why do people put two tags on internal structs???
X		    $tag = $2 if $2 && (!$tag || !$level); 
X		    #
X		    # if we still don't have one, i give up
X		    die "$iam: missing top tag at $lines[$i]\n" unless $tag;
X		    # 
X		    # @tags is the list of currently active struct tags
X		    push (@tags,$tag);
X		    last;
X		} 
X	    }
X	} 
X	# save the struct closure if we're going to print the whole thing
X	#
X	$trace_only && unshift(@output, &indent.$_);
X	$print_all && unshift(@code, "%" . &indent . $_);
X	#
X	# descend one level deeper into the morass
X	$level++;
X    #
X    # we hit the top of a structure
X    #
X    } elsif (/\b(struct|union)\s*(\w*)\s*\173/) { 
X	# if at top level, save struct/union tag for code generation later
X	--$level || unshift(@structs,"$1 $2\n");
X	# 
X	die "$iam: negative nesting level @ $1 $2, tags: @tags"
X	    if $level < 0; # if i don't like it, the C compiler won't
X	pop @tags;
X	# store struct/union declaration for full code tracing
X	$trace_only && unshift(@output, &indent.$_);
X	$print_all && unshift(@code, "%" . &indent . $_);
X    } else {
X	next unless $level; # nothing outside struct {blah;} counts
X	next if /^#/;	    # i won't try to understand cpp lines
X	if ($trace_only) {
X	    unshift(@output,&indent.$_);
X	} else {
X	    do store_ident($_, $print_all ? sprintf("%-45s",&indent.$_) : "");
X	}
X    } 
X} 
X
X
Xif ($trace_only) {
X    $, = "\n";
X    print @output;
X    print "\n";
X    exit 0;
X} 
X
X
Xif ($#h_files > -1)  {
X    for $file (@h_files) {
X	if ( $file =~ m:/(sys|h)/: && !$needsys++) {
X	    print "#include <sys/param.h>\n";
X	    print "#include <sys/types.h>\n";
X	}
X	print '#include "', $file, '"', "\n"
X	    if $file =~ /\.h$/;
X    } 
X} else {
X    print '#include "because you forgot to replace this line."',"\n";
X} 
X
Xprint "\n", 'char mask[] = "%08x %s\n";', "\n\n";
X
X#
X# generate bogus null pointer variables for all the
X# struct/unions declarations we've seen
X#
Xfor (@structs) {
X    if (/^(struct|union) (\w+)$/) {
X	die "$iam: unnamed $1" unless $2; # XXX 
X	$varname = "_var_$2";
X	printf "%-6s %-12s * %-18s = (%s %s *) 0;\n", 
X	    $1, $2, $varname, $1, $2;
X    } 
X} 
Xprint "\nmain () {\n";
X
X$bits_seen = 0;		# for bit-field structures, which i hate
X
X$oname = '';		# previous bitfield structure name
X$odecl = '';		# previous code string
X
X#
X#  @code composed of strings like this: "fully-qualified field%literal code"
X#
Xfor (@code) {
X    ($ident, $decl) = split(/%/);
X    
X    # no field, so just struct begin or end
X    #
X    if ($ident eq '') {
X	print "\n" if $decl !~ /}/;
X	print "    ",'printf("%s",',"\n\t",'"',$decl,'\n");',"\n";
X	next;
X    } 
X
X    # multiple comma-separated vars get each field var entered
X    # but with duplicate literal code, which we'll strip here
X    if ($odecl eq $decl) {
X	$decl =~ s/\S/ /g;	
X    } else {
X	$odecl = $decl;
X    } 
X
X    # fieldname composed of 
X    #	"struct/union-name field-name array-or-bits"
X    # don't always have array ([]) or bits (:dd)
X    #
X    ($name, $field, $suffix) = ($ident =~ /(\S+) ([^: []+) *([:[]?.*)/);
X    print "    ",'printf("%s",',"\n\t",'"',$decl,'");',"\n";
X	
X    $sep = ( ($rname = $name) =~ s/\./->/ ) ? "." : "->"; # neat, eh? :-)
X
X    #
X    # bit-field.  go accumulating how many bits we've seen so
X    # we can print out the bit-mask to find this field 
X    #
X    if ($suffix =~ /^:(\d+)/ ) {
X	if ($name ne $oname) {
X	    $oname = $name;
X	    $bits_seen = 0;
X	} 
X	$bits = ((1 << $1) - 1) << $bits_seen;
X	$bits_seen += $1;
X	die "$iam: too many bits in structure ($bits_seen)\n" 
X	    if $bits_seen > $BPW; # the C compiler wouldn't approve either
X	printf "    printf(mask, &(_var_%s),\n\t\"%s.%s (%08x)\");\n",
X	    $rname, $name, $field, $bits ;
X    #
X    # an array reference, so we won't take the address
X    #
X    } elsif (ord($suffix) == ord("[")) {
X	$bits_seen = 0;
X	printf "    printf(mask, _var_%s$sep%s,\n\t\"%s.%s\");\n",
X	    $rname, $field, $name, $field;
X    #
X    # regular field, neither array nor bit-field
X    #
X    } else {
X	$bits_seen = 0;
X	printf "    printf(mask, &(_var_%s$sep%s),\n\t\"%s.%s\");\n", 
X	    $rname, $field, $name, $field;
X    } 
X} 
Xprint "}\n";  
X
Xexit 0;
X
X##########################################################################
X
Xsub indent { '    ' x $level; } 
X
X# returns next text line, stripped of comments and extra white-space
X# never returns blank lines.  taken from lwall's makelib.  returns
X# undef on EOF
X#
Xsub nextline {
X    local($_);
X
X    {
X	return undef if !defined($_ = <>);
X	{
X	    chop;
X	    while (/\\$/) {
X		chop;
X		$_ .= <>;
X		chop;
X	    }
X	    if (s:/\*:\200:g) {
X		s:\*/:\201:g;
X		s/\200[^\201]*\201//g;	# delete single line comments
X		if (s/\200.*//) {	# begin multi-line comment?
X		    $_ .= '/*';
X		    $_ .= <>;
X		    redo;
X		}
X	    }
X	}
X	s/[ \t]+/ /g;
X	s/^\s+//;
X	s/\s+$//;
X	redo if !$_;
X    }
X    return $_;
X}
X
Xsub store_ident {
X    local($_, $code) = @_;
X    local (@_);
X    local ($i);
X    local ($tag);
X
X    $tag = join('.',@tags);
X
X    s/[();*]//g;
X    s/\[[^\]]*\]/[]/g;
X    s/^\s+//;
X    s/\s+$//;
X
X
X    #
X    # look for comma-separated variable declarations
X    #
X    split(/([ \t,]+)/);
X    for ($i = $#_; $i >= 0; $i--) {
X	if ( $i % 2 && $i != $#_) {
X	    last unless $_[$i] =~ /,/;
X	    next;
X	}
X	unshift(@code, "$tag $_[$i] " . "%" .  $code);
X    }
X} 
EOFMARK

exit 0

    Tom Christiansen                       {uunet,uiucdcs,sun}!convex!tchrist 
    Convex Computer Corporation                            tchrist@convex.COM
		 "EMACS belongs in <sys/errno.h>: Editor too big!"