








                     How To Steal Code
                             or
               Inventing The Wheel Only Once


                       Henry Spencer

                  Zoology Computer Systems
                       25 Harbord St.
                   University of Toronto
                Toronto, Ont. M5S1A1  Canada
          {allegra,ihnp4,decvax,utai}!utzoo!henry


                          ABSTRACT

          Much  is  said  about  ``standing  on   other
     people's  shoulders, not their toes'', but in fact
     the wheel is re-invented every day in  the  Unix/C
     community.   Worse, often it is re-invented badly,
     with bumps, corners, and cracks.  There  are  ways
     of  avoiding  this: some of them bad, some of them
     good, most of them  under-appreciated  and  under-
     used.



Introduction

``Everyone knows'' that that the UNIX/C- community  and  its
programmers are the very paragons of re-use of software.  In
some ways this is true.  Brian Kernighan [1] and others have
waxed  eloquent about how outstanding UNIX is as an environ-
ment for software re-use.  Pipes, the shell, and the  design
of programs as `filters' do much to encourage programmers to
build on others' work rather  than  starting  from  scratch.
Major  applications can be, and often are, written without a
line of C.  Of course, there are always people who insist on
doing  everything  themselves,  often citing `efficiency' as
the compelling reason why they can't possibly build  on  the
work  of  others (see [2] for some commentary on this).  But
surely these are the lamentable exceptions, rather than  the
rule?

Well, in a word, no.

At the level of shell programming, yes, software  re-use  is
widespread in the UNIX/C community.  Not quite as widespread
_________________________
- UNIX is a trademark of AT&T Bell Laboratories.




                      October 5, 1991





                           - 2 -


or as effective as it might be, but definitely common.  When
the  time  comes to write programs in C, however, the situa-
tion changes.  It took a radical change in directory  format
to  make people use a library to read directories.  Many new
programs still contain hand-crafted code  to  analyze  their
arguments,  even though prefabricated help for this has been
available for years.   C  programmers  tend  to  think  that
``re-using  software''  means  being able to take the source
for an existing program and edit it to  produce  the  source
for  a new one.  While that is a useful technique, there are
better ways.

Why does it matter that re-invention is rampant?  Apart from
the  obvious, that programmers have more work to do, I mean?
Well, extra work for  the  programmers  is  not  exactly  an
unmixed  blessing,  even  from  the  programmers' viewpoint!
Time spent re-inventing facilities that are  already  avail-
able  is  time  that is not available to improve user inter-
faces, or to make the program run faster, or to  chase  down
the  proverbial  Last Bug.  Or, to get really picky, to make
the code readable and  clear  so  that  our  successors  can
understand it.

Even more seriously, re-invented wheels  are  often  square.
Every  time  that a line of code is re-typed is a new chance
for bugs to be introduced.  There will always be the tempta-
tion  to  take shortcuts based on how the code will be used-
shortcuts that may turn around and bite the programmer  when
the  program  is  modified or used for something unexpected.
An inferior  algorithm  may  be  used  because  it's  ``good
enough''  and  the  better  algorithms  are too difficult to
reproduce on the spur of the moment... but the definition of
``good enough'' may change later.  And unless the program is
well-commented [here we pause for laughter], the next person
who  works  on  it  will have to study the code at length to
dispel the suspicion that there is some  subtle  reason  for
the  seeming  re-invention.   Finally,  to quote [2], if you
re-invent the square wheel, you will not benefit when  some-
body else rounds off the corners.

In short, re-inventing the wheel ought to be a  rare  event,
occurring  only  for  the most compelling reasons.  Using an
existing wheel, or improving an  existing  one,  is  usually
superior  in  a variety of ways.  There is nothing dishonor-
able about stealing code* to make life easier and better.

Theft via the Editor

UNIX historically has flourished in  environments  in  which
full  sources for the system are available.  This led to the
_________________________
* Assuming no software licences,  copyrights,  patents,
etc. are violated!




                      October 5, 1991





                           - 3 -


most obvious and crudest way  of  stealing  code:  copy  the
source  of  an  existing program and edit it to do something
new.

This approach does have its advantages.  By its  nature,  it
is the most flexible method of stealing code.  It may be the
only viable approach when what is desired is some variant of
a complex algorithm that exists only within an existing pro-
gram; a good example was V7 dumpdir (which printed  a  table
of contents of a backup tape), visibly a modified copy of V7
restor (the only other program that understood  the  obscure
format of backup tapes).  And it certainly is easy.

On the other hand, this approach also has its problems.   It
creates  two subtly-different copies of the same code, which
have to be maintained separately.  Worse, they often have to
be maintained ``separately but simultaneously'', because the
new program inherits all the mistakes of the original.  Fix-
ing  the same bug repeatedly is so mind-deadening that there
is great temptation to fix it in only the  program  that  is
actually  giving  trouble... which means that when the other
gives trouble, re-doing the cure must  be  preceded  by  re-
doing  the  investigation  and diagnosis.  Still worse, such
non-simultaneous bug fixes cause the variants of the code to
diverge  steadily.   This  is  also true of improvements and
cleanup work.

A program created in this way may also be inferior, in  some
ways, to one created from scratch.  Often there will be ves-
tigial code left over from the program's evolutionary ances-
tors.   Apart from consuming resources (and possibly harbor-
ing bugs) without a  useful  purpose,  such  vestigial  code
greatly  complicates understanding the new program in isola-
tion.

There is also the  possibility  that  the  new  program  has
inherited  a poor algorithm from the old one.  This is actu-
ally a universal problem with stealing code, but it is espe-
cially  troublesome with this technique because the original
program probably was not built with  such  re-use  in  mind.
Even  if  its algorithms were good for its intended purpose,
they may not be versatile enough to do a good job  in  their
new role.

One relatively clean form of theft via editing is  to  alter
the  original  program's  source  to generate either desired
program by conditional compilation.  This eliminates most of
the  problems.   Unfortunately,  it  does so only if the two
programs are sufficiently similar that they can  share  most
of  the source.  When they diverge significantly, the result
can be a maintenance  nightmare,  actually  worse  than  two
separate  sources.   Given  a close similarity, though, this
method can work well.




                      October 5, 1991





                           - 4 -


Theft via Libraries

The obvious way of using somebody else's code is to  call  a
library  function.  Here, UNIX has had some success stories.
Almost everybody uses the stdio library rather than  invent-
ing their own buffered-I/O package.  (That may sound trivial
to those who never programmed on a V6 or earlier  UNIX,  but
in  fact  it's  a  great improvement on the earlier state of
affairs.) The simpler sorts of string manipulations are usu-
ally  done  with  the  strxxx functions rather than by hand-
coding them, although efficiency issues and the wide  diver-
sity  of  requirements  have limited these functions to less
complete success.  Nobody who knows about qsort  bothers  to
write his own sorting function.

However, these success stories are pleasant  islands  in  an
ocean  of mud.  The fact is that UNIX's libraries are a dis-
grace.  They are well enough implemented, and  their  design
flaws  are  seldom  more  than  nuisances,  but there aren't
enough of them!  Ironically,  UNIX's  ``poor  cousin'',  the
Software  Tools  community  [3,4],  has  done much better at
this.  Faced with a wild diversity  of  different  operating
systems, they were forced to put much more emphasis on iden-
tifying clean abstractions for system services.

For  example,  the  Software  Tools  version  of   ls   runs
unchanged,  without  conditional  compilation,  on dozens of
different operating systems [4].  By contrast, UNIX programs
that  read  directories invariably dealt with the raw system
data structures, until  Berkeley  turned  this  cozy  little
world  upside-down  with  a change to those data structures.
The Berkeley implementors were  wise  enough  to  provide  a
library  for  directory access, rather than just documenting
the new underlying structure.  However,  true  to  the  UNIX
pattern,  they  designed a library which quietly assumed (in
some of its naming conventions) that the  underlying  system
used  their  structures!  This particular nettle has finally
been grasped firmly by the IEEE POSIX project  [5],  at  the
cost of yet another slightly-incompatible interface.

The adoption of the new directory libraries is  not  just  a
matter  of  convenience  and  portability:  in  general  the
libraries are faster than the hand-cooked code they replace.
Nevertheless, Berkeley's original announcement of the change
was greeted with a storm of outraged protest.

Directories, alas, are not an isolated example.  The  UNIX/C
community  simply  hasn't made much of an effort to identify
common code and package it for re-use.  One of the two major
variants  of  UNIX still lacks a library function for binary
search, an algorithm which is notorious for both the perfor-
mance  boost  it  can produce and the difficulty of coding a
fully-correct version from scratch.   No  major  variant  of
UNIX  has a library function for either one of the following



                      October 5, 1991





                           - 5 -


code fragments, both omnipresent (or at least,  they  should
be  omnipresent  [6])  in  simple*  programs  that  use  the
relevant facilities:

        if ((f = fopen(filename, mode)) == NULL)
                print error message with filename, mode, and specific
                reason for failure, and then exit


        if ((p = malloc(amount)) == NULL)
                print error message and exit

These may sound utterly trivial,  but  in  fact  programmers
almost  never  produce as good an error message for fopen as
ten lines of library code can, and half the time the  return
value from malloc isn't checked at all!

These examples illustrate a general principle, a side  bene-
fit  of stealing code: the way to encourage standardization-
and quality is to make it easier to be careful and  standard
than to be sloppy and non-standard.  On systems with library
functions for error-checked fopen and malloc, it  is  easier
to use the system functions-which take some care to do ``the
right thing''-than to kludge it yourself.  This  makes  con-
verts very quickly.

These are not isolated examples.  Studying the libraries  of
most  any  UNIX  system  will  yield  other ideas for useful
library functions (as well as a lot of silly  nonsense  that
UNIX  doesn't  need, usually!).  A few years of UNIX systems
programming also leads to  recognition  of  repeated  needs.
Does your* UNIX have library functions to:

     + decide whether a filename is well-formed (contains no
       control  characters,  shell  metacharacters, or white
       space, and is  within  any  name-length  limits  your
_________________________
*  I  include  the  qualification  ``simple''   because
complex  programs  often  want  to  do more intelligent
error  recovery  than  these  code  fragments  suggest.
However,  most of the programs that use these functions
don't  need  fancy  error  recovery,  and   the   error
responses  indicated  are  better  than  the ones those
programs usually have now!
- Speaking of encouraging standardization: we  use  the
names  efopen  and  emalloc for the checked versions of
fopen and malloc, and arguments and returned values are
the  same  as  the  unchecked  versions except that the
returned value is guaranteed non-NULL if  the  function
returns at all.
* As you might guess, my system has all of these.  Most
of them are trivial  to  write,  or  are  available  in
public-domain forms.




                      October 5, 1991





                           - 6 -


       system sets)?

     + close all file descriptors except the standard ones?

     + compute  a  standard  CRC  (Cyclic  Redundancy  Check
       ``checksum'')?

     + operate on malloced unlimited-length strings?

     + do  what  access(2)  does  but  using  the  effective
       userid?

     + expand metacharacters in a filename the same way  the
       shell  does?  (the simplest way to make sure that the
       two agree is to use popen and echo for anything  com-
       plicated)

     + convert integer baud rates  to  and  from  the  speed
       codes used by your system's serial-line ioctls?

     + convert integer  file  modes  to  and  from  the  rwx
       strings used- to present such modes to humans?

     + do a binary search through a  file  the  way  look(1)
       does?

The above are fairly trivial examples of the sort of  things
that  ought  to  be  in  UNIX libraries.  More sophisticated
libraries can also be useful,  especially  if  the  language
provides  better  support  for  them  than C does; C++ is an
example [7].  Even in C, though,  there  is  much  room  for
improvement.

Adding library functions does have its  disadvantages.   The
interface to a library function is important, and getting it
right is hard.  Worse, once users  have  started  using  one
version  of an interface, changing it is very difficult even
when hindsight  clearly  shows  mistakes;  the  near-useless
return  values  of some of the common UNIX library functions
are obvious examples.  Satisfactory handling of error condi-
tions  can  be  difficult.  (For example, the error-checking
malloc mentioned earlier is very handy for programmers,  but
invoking  it from a library function would be a serious mis-
take, removing any possibility of more intelligent  response
to  that error.) And there is the perennial headache of try-
ing to get others to adopt your pet function, so  that  pro-
grams  using  it  can be portable without having to drag the
source of the function around too.  For  all  this,  though,
libraries  are  in  many  ways  the most satisfactory way of
_________________________
- If you think only ls uses these, consider that rm and
some  similar  programs  ought  to use rwx strings, not
octal modes, when requesting confirmation!




                      October 5, 1991





                           - 7 -


encouraging code theft.

Alas, encouraging code theft does not  guarantee  it.   Even
widely-available library functions often are not used nearly
as much as they should be.  A conspicuous example is getopt,
for  command-line  argument  parsing.   Getopt supplies only
quite modest help in parsing the command line, but the stan-
dardization  and  consistency that its use produces is still
quite valuable; there are far too many pointless  variations
in  command  syntax  in  the hand-cooked argument parsers in
most UNIX programs.  Public-domain implementations of getopt
have  been  available  for years, and AT&T has published (!)
the source for the System V implementation.  Yet people con-
tinue  to  write  their  own argument parsers.  There is one
valid reason for this, to be discussed in the next  section.
There are also a number of excuses, mostly the standard ones
for not using library functions:

     + ``It doesn't do quite what I want.'' But often it  is
       close  enough  to serve, and the combined benefits of
       code theft and  standardization  outweigh  the  minor
       mismatches.

     + ``Calling a library function  is  too  inefficient.''
       This  is mostly heard from people who have never pro-
       filed their  programs  and  hence  have  no  reliable
       information  about what their code's efficiency prob-
       lems are [2].

     + ``I didn't know  about  it.''  Competent  programmers
       know the contents of their toolboxes.

     + ``That  whole  concept  is  ugly,   and   should   be
       redesigned.''  (Often said of getopt, since the usual
       UNIX single-letter-option syntax that  getopt  imple-
       ments  is  widely  criticized  as  user-hostile.) How
       likely is it that the rest of the world will go along
       with  your  redesign  (assuming  you ever finish it)?
       Consistency and  a  high-quality  implementation  are
       valuable  even  if  the standard being implemented is
       suboptimal.

     + ``I would have done it differently.'' The triumph  of
       personal taste over professional programming.

Theft via Templates

Templates are a major and much-neglected  approach  to  code
sharing:    ``boilerplate''   programs   which   contain   a
carefully-written skeleton for some  moderately  stereotyped
task,  which  can  then  be adapted and filled in as needed.
This method has some of the vices of modifying existing pro-
grams,  but  the  template  can be designed for the purpose,
with attention to quality and versatility.



                      October 5, 1991





                           - 8 -


Templates can be particularly useful when library  functions
are  used  in a stereotyped way that is a little complicated
to write from scratch; getopt is an excellent example.   The
one  really valid objection to getopt is that its invocation
is not trivial, and typing  in  the  correct  sequence  from
scratch  is  a real test of memory.  The usual getopt manual
page contains a lengthy example which is essentially a  tem-
plate for a getopt-using program.

When the first public-domain  getopt  appeared,  it  quickly
became  clear that it would be convenient to have a template
for its use handy.  This template eventually grew to  incor-
porate  a  number  of  other  things: a useful macro or two,
definition of main, opening of files in  the  standard  UNIX
filter  fashion, checking for mistakes like opening a direc-
tory, filename and line-number tracking for error  messages,
and  some  odds  and  ends.  The full current version can be
found in the Appendix; actually it diverged  into  two  dis-
tinct versions when it became clear that some filters wanted
the illusion of a single input stream, while  others  wanted
to handle each input file individually (or didn't care).

The obvious objection to this line of development is  ``it's
more complicated than I need''.  In fact, it turns out to be
surprisingly convenient to have all this  machinery  presup-
plied.   It  is much easier to alter or delete lines of code
than to add them.  If directories are legitimate input, just
delete  the  code  that  catches  them.  If no filenames are
allowed as input, or exactly one must be present, change one
line  of  code  to enforce the restriction and a few more to
deal with the arguments correctly.  If the arguments are not
filenames  at  all, just delete the bits of code that assume
they are.  And so forth.

The job  of  writing  an  ordinary  filter-like  program  is
reduced  to filling in two or three blanks* in the template,
and then writing the code that actually processes the  data.
Even  quick  improvisations  become  good-quality  programs,
doing things the standard way with all the proper amenities,
because even a quick improvisation is easier to do by start-
ing from the template.  Templates are an  unmixed  blessing;
anyone  who  types  a non-trivial program in from scratch is
wasting his time and his employer's money.

Templates are also useful for other stereotyped files,  even
ones that are not usually thought of as programs.  Most ver-
sions of UNIX have a simple template for manual pages hiding
somewhere (in V7 it was /usr/man/man0/xx).  Shell files that
want to analyze complex argument lists have the same  getopt
problem  as  C  programs,  with the same solution.  There is
_________________________
* All marked with the string `xxx' to  make  them  easy
for a text editor to find.




                      October 5, 1991





                           - 9 -


enough machinery in a ``production-grade'' make file to make
a  template  worthwhile,  although  this  one  tends  to get
altered fairly heavily; our current one is in the Appendix.

Theft via Inclusion

Source inclusion (#include) provides a way of  sharing  both
data  structures  and  executable  code.  Header files (e.g.
stdio.h) in particular tend to be taken for granted.  Again,
those  who  haven't  been  around long enough to remember V6
UNIX may have trouble grasping what a revolution it was when
V7 introduced systematic use of header files!

However, even mundane header files could be rather more use-
ful  than  they normally are now.  Data structures in header
files are widely accepted, but there is somewhat less use of
them  to  declare the return types of functions.  One or two
common header files like stdio.h and  math.h  do  this,  but
programmers  are  still  used  to  the idea that the type of
(e.g.) atol has to be typed in by hand.  Actually,  all  too
often  the programmer says ``oh well, on my machine it works
out all right if I don't bother declaring  atol'',  and  the
result  is  dirty and unportable code.  The X3J11 draft ANSI
standard for C addresses this by defining some  more  header
files and requiring their use for portable programs, so that
the header files can do all the work and do it right.

In principle, source inclusion can be  used  for  more  than
just header files.  In practice, almost anything that can be
done with source inclusion can be  done,  and  usually  done
more  cleanly,  with  header files and libraries.  There are
occasional  specialized  exceptions,  such  as  using  macro
definitions  and source inclusion to fake parameterized data
types.

Theft via Invocation

Finally, it is often possible  to  steal  another  program's
code  simply  by invoking that program.  Invoking other pro-
grams via system or popen for things that are easily done in
C  is  a common beginner's error.  More experienced program-
mers can go too far the other  way,  however,  insisting  on
doing  everything  in  C,  even  when  a  leavening of other
methods would give better results.  The best way to  sort  a
large file is probably to invoke sort(1), not to do it your-
self.  Even invoking a shell file can be useful, although  a
bit  odd-seeming  to most C programmers, when elaborate file
manipulation is needed and efficiency is not critical.

Aside from invoking other programs at run time, it can  also
be useful to invoke them at compile time.  Particularly when
dealing with large tables, it is often better to dynamically
generate  the  C  code  from  some more compact and readable
notation.  Yacc and lex are familiar examples of this  on  a



                      October 5, 1991





                           - 10 -


large  scale,  but  simple  sed  and  awk programs can build
tables  in  more  specialized,  application-specific   ways.
Whether  this is really theft is debatable, but it's a valu-
able technique all the same.  It can neatly bypass a lot  of
objections that start with ``but C won't let me write...''.

An Excess of Invention

With all these varied methods, why is code  theft  not  more
widespread?  Why are so many programs unnecessarily invented
from scratch?

The most obvious answer is the  hardest  to  counter:  theft
requires  that  there be something to steal.  Use of library
functions is impossible unless somebody sets up  a  library.
Designing  the interfaces for library functions is not easy.
Worse, doing it well requires insight, which generally isn't
available  on demand.  The same is true, to varying degrees,
for the other forms of theft.

Despite its reputation as a hotbed of software re-use,  UNIX
is  actually  hostile  to some of these activities.  If UNIX
directories had been complex and obscure,  directory-reading
libraries would have been present from the beginning.  As it
is, it was simply too easy to do things  ``the  hard  way''.
There  still  is no portable set of functions to perform the
dozen or so useful manipulations of terminal  modes  that  a
user  program  might  want  to  do, a major nuisance because
changing those modes ``in the raw''  is  simple  but  highly
unportable.

Finally, there is the Not Invented Here  syndrome,  and  its
relatives,  Not  Good  Enough  and Not Understood Here.  How
else to explain AT&T  UNIX's  persistent  lack  of  the  dbm
library  for  hashed databases (even though it was developed
at Bell Labs and hence is available to AT&T),  and  Berkeley
UNIX's  persistent  lack of the full set of strxxx functions
(even though a public-domain implementation has existed  for
years)?   The  X3J11  and POSIX efforts are making some pro-
gress at developing a common nucleus of  functionality,  but
they  are aiming at a common subset of current systems, when
what is really wanted is a common superset.

Conclusion

In short, never build what you can  (legally)  steal!   Done
right, it yields better programs for less work.

References


[1] Brian W. Kernighan, The Unix System and Software  Reusa-
    bility,  IEEE  Transactions on Software Engineering, Vol
    SE-10, No. 5, Sept. 1984, pp. 513-8.



                      October 5, 1991





                           - 11 -


[2] Geoff Collyer and Henry Spencer, News Need Not Be  Slow,
    Usenix Winter 1987 Technical Conference, pp. 181-190.

[3] Brian W. Kernighan and  P.J.  Plauger,  Software  Tools,
    Addison-Wesley, Reading, Mass. 1976.

[4] Mike O'Dell, UNIX: The World View,  Usenix  Winter  1987
    Technical Conference, pp. 35-45.

[5] IEEE, IEEE Trial-Use Standard 1003.1 (April 1986): Port-
    able  Operating  System  for Computer Environments, IEEE
    and Wiley-Interscience, New York, 1986.

[6] Ian  Darwin  and  Geoff  Collyer,  Can't  Happen  or  /*
    NOTREACHED  */ or Real Programs Dump Core, Usenix Winter
    1985 Technical Conference, pp. 136-151.

[7] Bjarne  Stroustrup,  The   C++   Programming   Language,
    Addison-Wesley, Reading, Mass. 1986.

Appendix

Warning:  these templates  have  been  in  use  for  varying
lengths  of  time, and are not necessarily all entirely bug-
free.


C program, single stream of input
/*
 * name - purpose xxx
 */$Log$
#include <stdio.h>
#include <sys/stat.h>>
#include <string.h>
#define STREQ(a,5b)     (*(a) ==/*(b)r&&istrcmp((a),s(b))D=='0)use BUFSIZ! */
#ifndef lint
#endif char RCSid[] = "$Header$";
int debug = 0;
char *progname;
char *nullargv[] = { "-", NULL };       /* dummypargveforocasexofino(args */
long lineno;;                           /* linennumberrforsmessagescetc. */
FILE *in = NULL;                        /* current input file */
#ifdef UTZOOERRor(), exit();
#elsen char *mkprogname();
#endife mkprogname(a)   (a)
char *nextfile();
void fail();
/- main - parse arguments and handle options
main(argc, argv)
chara*argv[];
{       int c;
        externrint optind;
        voidrprocess();targ;
        progname = mkprogname(argv[0]);



                      October 5, 1991





                           - 12 -


        while ((switcht(c)({rgc, argv, "xxxd")) != EOF)
                case 'xxxxx     /* xxx meaning of option */
                case 'd':reak;  /* Debugging. */
                        break;+;
                default::
                        break;++;
        if (errflg) {
                fprintf(stderr, "xxxg[file]"...\n");me);
        }       exit(2);
        if (optind >= argc)
        else    argvp = nullargv;
        inname =anextfile();v[optind];
        if (innaprocess();)
}       exit(0);
/*
 */getline - get next line (internal version of fgets)
getline(ptr, size)
intrsize;;
{       register char *namep;
        while (fgets(ptr, size, in) == NULL) {
                ifm(namepe==fNULL);
                inname =rnamep;NULL);   /* only after we know it's good */
        lineno++;
}       return(ptr);
/*
 */nextfile - switch files
nextfile()                      /* filename */
{       register char *namep;
        extern FILE *efopen();
        if (in != NULL)
                (void) fclose(in);
        ifm(namepa==vNULL)      /* no more files */
        argvp++;return(NULL);
        if (STREQ(namep, "-")) {
                nameps=d"stdin";
        } else {in = efopen(namep, "r");
                if (fstaerror("can't,fstatt`%s'", namep);
                if ((staerror("`%s'eis directory!",_namep);
        }
        return(namep);
}
/- fail - complain and die
void
char(*s1;s2)
{har *s2;
        fprintf(stderr, s1,:s2);le `%s', line %ld) ", progname, inname, lineno);
        exit(1);stderr, "\n");
}
/- process - process input data
void
{rocess()
        char line[MAXSTR];
        while (gxxxine(line, (int)sizeof(line)) != NULL) {
}       }



                      October 5, 1991





                           - 13 -


C program, separate input files
/*
 * name - purpose xxx
 */$Log$
#include <stdio.h>
#include <sys/stat.h>>
#include <string.h>
#define STREQ(a,5b)     (*(a) ==/*(b)r&&istrcmp((a),s(b))D=='0)use BUFSIZ! */
#ifndef lint
#endif char RCSid[] = "$Header$";
int debug = 0;
char *progname;
long lineno;;                           /* linennumberrforsmessagescetc. */
extern void error(), exit();
extern charO*mkprogname();
#define mkprogname(a)   (a)
voidifail();
/*
 */main - parse arguments and handle options
intnargc;, argv)
{har *argv[];
        int errflg = 0;
        structistat statbuf;
        extern charo*optarg;
        voidrprocess();open();
        progname = mkprogname(argv[0]);
        while ((c = getopt(argc, argv, "xxxd")) != EOF)
                casec'xxx':{    /* xxx meaning of option */
                        break;
                case 'd'debug++;/* Debugging. */
                case '?':reak;
                default:errflg++;
                }       break;
        if (errffprintf(stderr, "usage: %s ", progname);
                exit(2);stderr, "xxx [file] ...\n");
        }
        if (optiprocess(stdin, "stdin");
        else    for (; optind < argc; optind++)
                        if (STREprocess(stdin,,"-");)
                        else {  in = efopen(argv[optind], "r");
                                if (fstaerror("can't,fstatt`%s'", argv[optind]);
                                if ((staerror("`%s'eis directory!",_argv[optind]);
                                (void)sfclose(in);ptind]);
        exit(0);        }
}
/- process - process input file
void
FILEe*in;n, name)
{har *name;
        char line[MAXSTR];
        lineno = 0;me;
        while (fgets(line, sizeof(line), in) != NULL) {
                xxxeno++;
}       }



                      October 5, 1991





                           - 14 -


/- fail - complain and die
void
char *s2;
{       fprintf(stderr, "%s: (file `%s', line %ld) ", progname, inname, lineno);
        fprintf(stderr, "\n"););
}       exit(1);


Make file
# Things you might want to put in ENV and LENV:
# -DCHARBITS=0377       compiler lacks unsigned char
# -DREGISTER=tern       machines withifew registersofor;registerwvariables
ENVD=T-DSTATIC=extern -DREGISTER=o-DUTZOOERRe error() function and friends
LENV = -Dvoid=int -DCHARBITS=0377 -DREGISTER= -DUTZOOERR
# -DDEBUGyou might want debugging hooks
TEST.= -DDEBUG          header files in current directory
# Things you might want to put in PROF:
# -pstatic='/* */'      profilerrything global so profiler can see it.
PROF =
LINTFLAGS-= $(LENV)$$(TEST)$-haOF)
LDFLAGS = -i
LSRC==xxxx
DTR = README dMakefile tests tests.good xxx.c
xxx:    $(CC) $(CFLAGS) $(LDFLAGS) xxx.o -o xxx
xxx.o:  xxx.h
lint:   $(LSRC)
        lint $(LINTFLAGS) $(LSRC) | tee lint
r:      xxx <testst>tests.new   # Regression test.
        diff -h tests.new tests.good && rm tests.new
# because humanojudgement isgneededntoedecidenwhenioutputtistgood.d"
good:   xxx <tests >tests.good
dtr:    r $(DTR)
        makedtr $(DTR) >dtr
dMakefilsed '/^L*ENV=/s/ *-DUTZOOERR//' Makefile >dMakefile
clean:
        rm -f *.o lint tests.new dMakefile dtr core mon.out xxx





















                      October 5, 1991


