[comp.sources.unix] v24i066: Purdue software product installation system, Part04/07

rsalz@bbn.com (Rich Salz) (03/21/91)

Submitted-by: Kevin Braunsdorf <ksb@cc.purdue.edu>
Posting-number: Volume 24, Issue 66
Archive-name: pucc-install/part04

#!/bin/sh
# This is part 04 of pucc-1b
# ============= install.d/install.1l ==============
if test ! -d 'install.d'; then
    echo 'x - creating directory install.d'
    mkdir 'install.d'
fi
if test -f 'install.d/install.1l' -a X"$1" != X"-c"; then
	echo 'x - skipping install.d/install.1l (File already exists)'
else
echo 'x - extracting install.d/install.1l (Text)'
sed 's/^X//' << 'Purdue' > 'install.d/install.1l' &&
.\" $Id: install.1l,v 7.0 90/09/17 09:41:50 ksb Exp $
.\" Copyright 1990 Purdue Research Foundation, West Lafayette, Indiana
.\" 47907.  All rights reserved.
.\"
.\" Written by Kevin S Braunsdorf, ksb@cc.purdue.edu, purdue!ksb
.\"	       Jeff Smith, jsmith@cc.purdue.edu, purdue!jsmith
.\"
.\" This software is not subject to any license of the American Telephone
.\" and Telegraph Company or the Regents of the University of California.
.\"
.\" Permission is granted to anyone to use this software for any purpose on
.\" any computer system, and to alter it and redistribute it freely, subject
.\" to the following restrictions:
.\"
.\" 1. Neither the authors nor Purdue University are responsible for any
.\"    consequences of the use of this software.
.\"
.\" 2. The origin of this software must not be misrepresented, either by
.\"    explicit claim or by omission.  Credit to the authors and Purdue
.\"    University must appear in documentation and sources.
.\"
.\" 3. Altered versions must be plainly marked as such, and must not be
.\"    misrepresented as being the original software.
.\"
.\" 4. This notice may not be removed or altered.
.\"
.\" $Laser: ${tbl-tbl} %f | ${ltroff-ltroff} -man
.\" $Compile: ${tbl-tbl} %f | ${nroff-nroff} -man | ${PAGER-${more-more}}
.TH INSTALL 1L PUCC
.SH NAME
install \- update files or directories in a controlled environment
.SH SYNOPSIS
\fBinstall\fP [\-\fB1Dclnpqsv\fP] [\-\fBC\fP\fIchecklist\fP] [\-\fBH\fP\fIhardlinks\fP] [\-\fBS\fP\fIsymlinks\fP] [\-\fBg\fP\fIgroup\fP] [\-\fBm\fP\fImode\fP] [\-\fBo\fP\fIowner\fP] \fIfiles\fP \fIdestination\fP
.sp 1
\fBinstall\fP \-\fBd\fP [\-\fBhnqrv\fP] [\-\fBC\fP\fIchecklist\fP] [\-\fBg\fP\fIgroup\fP] [\-\fBm\fP\fImode\fP] [\-\fBo\fP\fIowner\fP] \fIdirectory\fP
.sp 1
\fBinstall\fP \-[\fBhV\fP] [\-\fBC\fP\fIchecklist\fP]
.sp 1
\fBinstall\fP \-\fBR\fP [\-\fB1dlnqsv\fP] [\-\fBC\fP\fIchecklist\fP] [\-\fBH\fP\fIhardlinks\fP] [\-\fBS\fP\fIsymlinks\fP] [\-\fBg\fP\fIgroup\fP] [\-\fBm\fP\fImode\fP] [\-\fBo\fP\fIowner\fP] \fIdestination\fP
.SH DESCRIPTION
.PP
.I Install
is a tool for updating system files in a controlled environment.
The design philosophy of
.I install
is to automate the installation process and put
it as much out of reach of human carelessness as possible, while
providing a flexible, easy to use tool.
.I Install
provides the following features:
.IP \(bu
.I Install
increases system security by providing a controlled installation
environment.  It checks the actions of the superuser against a
configuration file that you build (either by hand or using
.IR instck (1L)),
and can prevent grievous mistakes such as installing a shell setuid
to a privileged user, or installing a world\(hywritable password file.
An appropriate configuration file can guarantee that files are
installed with correct owner, group and mode, that
.IR strip (1)
is run on binaries and 
.IR ranlib (1)
is run on libraries.
Regardless of whether you create a configuration file,
.I install
warns you of many possible errors, unless you make it quiet
with its
.B \-q
option.  For instance, if you install
a new version of the
.IR ex (1)
editor and forget to update its links,
.I install
will notice the extra links to the existing version and warn
you that you might be making a mistake.
.IP \(bu
Installed files do not overwrite current versions.  The current version
is backed up to a subdirectory of its dot directory (named \*(lqOLD\*(rq by
default), where it may be easily
re\(hyinstalled in the case of an unforeseen bug.  The companion program
.IR purge (1L)
removes these backed\(hyup files after a user\(hyspecified interval.
By default, if you repeatedly install new versions of a file,
.I install
creates a series of backups, providing a primitive form of version
control.
.IP \(bu
.I Install
increases accountability by logging the actions of the superuser.
This makes it easier to track down errors after the fact.
.IP \(bu
.I Install
simplifies Makefiles by allowing you to combine operations that
would require several steps into a single one (e.g., you can specify
in a single command line a file's ownership, group, mode, whether to run
.IR strip (1)
or
.IR ranlib (1),
and which hard or soft links should be made).
.IP \(bu
Despite its power and potential complexity,
.I install
is easy to use interactively because it intuits appropriate
installation parameters for you, either by using those associated with
an existing file, or its compilation\(hydependent defaults.  In most
cases you do not have to specify a file's owner, group, or mode unless
you want them to be different from an existing file or the
compilation\(hydependent defaults.
Typical interactive usage is simply \*(lqinstall file destination.\*(rq
.IP \(bu
.I Install
is as careful as it can be to complete an installation once it is
begun.  There is a point in the code where
.IR unlink (2)
and
.IR rename (2)
must be executed in close succession, and that is the only window in
which a system crash or a signal might leave system files in
an inconsistent state (unfortunately, this is not true under operating
systems that do not provide an atomic
.IR rename (2)
system call).
This is true even when
.I install
must copy files across file system boundaries.
.IP \(bu
.I Install
may also be used to remove (de\(hyinstall) files in a controlled manner.
.IP \(bu
Finally,
.I install
currently runs on a variety of architectures and operating systems
and is easy to port to new platforms.
.SH USAGE
.SS Terminology
.PP
The user specifies one or more
.I files
to install, and a
.IR destination ,
which may be either a full or relative pathname ending
in a file name or an existing directory name (if the
directory does not exist,
.I install
thinks you mean to create a new file).
The special name \*(lq\-\*(rq may be used for the
.I file
argument to indicate stdin (see EXAMPLES).  In this case,
.I destination
.B must not
be a directory, since
.I install
cannot guess the name the file should have when installed.
.PP
Because the user may specify more than one
.IR files ,
and
.I destination
may be an existing directory or a file which may or may not
exist,
.I install
must also internally keep track of a
.I destdir
(\*(lqdestination directory\*(rq, i.e., the directory in which to
place the file to install), and a
.IR target
(the full pathname that each file to install
will have when it is installed in
.IR destdir ).
The
.I target
and
.I destdir
are constructed from the
.I files
and
.I destination
arguments as described below.
.PP
For each name in
.IR files ,
.I install
determines a
.I target
name as follows:
If
.I destination
is an existing directory,
.I install
catenates the last component of
.I file
to
.I destination
to arrive at the
.I target
name.  If
.I destination
does not exist or is an existing file,
.I install
takes
.I destination
to be the
.IR target .
In the latter case
.I destdir
is simply
.I destination
minus its last component.
If this reduction leaves
.I destdir
empty then it is set to \*(lq.\*(rq.
(E.g., if
.I destination
were
.I /etc/motd
then
.I destdir
would be
.IR /etc ,
but if
.I destination
were just
.I motd
then
.I destdir
would be \*(lq.\*(rq.)
N.B.:  If more than one
.I files
are specified,
.I destination
.B must
be an existing directory.
.PP
Examples are the easiest way to clarify this terminology.
.RS
.sp 1
In the command \*(lqinstall motd /etc/motd\*(rq:
.sp 1
.RS
.TS
l l .
file:	motd
destination:	/etc/motd
destdir:	/etc
target:	/etc/motd
.TE
.RE
.sp 1
In the command \*(lqinstall motd /etc\*(rq:
.sp 1
.RS
.TS
l l .
file:	motd
destination:	/etc
destdir:	/etc
target:	/etc/motd
.TE
.RE
.sp 1
In the command sequence \*(lqcd /etc; install motd.new motd\*(rq:
.sp 1
.RS
.TS
l l .
file:	motd.new
destination:	motd
destdir:	. (dot)
target:	./motd
.TE
.RE
.RE
.sp 1
.SS Installation Parameters
.PP
If the file permissions, ownership or group ownership are not specified on
the command line and
.I target
exists,
.I install
duplicates its group, ownership, and mode.
Otherwise, if the
.I target
doesn't exist and the invoker is the superuser,
.I install
uses its compilation\(hydependent defaults.
Otherwise,
.I install
uses the effective uid, the effective gid,
and a compilation\(hydependent mode.
.I Install
may also be configured to inherit the mode and ownerships
from the
.IR destdir .
(Use the
.B \-V
option to view the compilation\(hydependent defaults.)
.PP
Note:
.I install
can only change ownership if invoked by the superuser;
however, any user may specify a different group
as long as the group is allowed by
.IR chgrp (1).
.SS Method of Operation
.PP
.I Install
first checks the proposed owner, group, and mode against the
configuration file.  It also checks whether the
.I target
should have
.IR strip (1)
or
.IR ranlib (1)
run on it after installation. 
.I Install
aborts if it finds discrepancies between the configuration
file and the proposed installation parameters.  (If necessary,
you can override the configuration file
with \*(lq\fB\-C\fP \fI/dev/null\fP.\*(rq)
.PP
.I Install
then looks for an existing
.I target
and backs it up to a subdirectory of
.I destdir
named \*(lqOLD\*(rq, which
.I install
will create if it doesn't exist.
The backup is actually just a hard link to the existing
.IR target .
(If a backup file of the same name already exists in \*(lqOLD\*(rq,
.I install
first renames it by appending its process id).
For security reasons,
.I install
drops setuid and setgid bits when files are moved to the \*(lqOLD\*(rq
directory.
After backing up an existing
.IR target ,
.I install
temporarily moves
.I file
to
.IR destdir/OLD/random-name .
This step is taken to ensure that both
.I file
and
.I target
are in the same file system so that
.IR rename (2)
may be used for the final installation.  This reduces the window
in which files might be left in an inconsistent state due to a
system crash or signal.  Consequently the \*(lqOLD\*(rq
subdirectory must not be a file system mount point, since the
.IR rename (2)
would fail.
.PP
.I Install
then unlinks the existing
.I target
(leaving the backup) and renames
.IR destdir/OLD/random-name
to
.IR target .
.PP
Next
.I install
updates any hard links and soft (symbolic) links
given under the
.B \-H
or
.B \-S
options.  All links point at the
installed
.IR target .
Existing symbolic links which point to the
correct file are left unchanged, otherwise removed and
replaced with the correct spelling.  Existing correct hard links
are unlinked and replaced, without a
backup.  Links which point to a file other than the
.I target
are backed up to \*(lqOLD\*(rq and linked.
.I Install
prints a warning in this case.
.SH OPTIONS
.TP
.B \-1
After a successful installation
.I install
removes any previous
backup in \*(lqOLD\*(rq.
Thus
.I install
will keep exactly one previous revision of
the installed file.
.TP
.B \-c
Do not unlink the
.IR files .
.TP
.BI \-C checkfile
Search 
.I checkfile
for an expression that matches the
.IR target .
If
.I install
finds such an expression it will check the proposed modes (etc.)
against those listed in the checkfile; any differences cause
.I install
to abort the installation.
This mechanism is provided to protect the superuser from installing,
for instance, the shell setuid root, as in:
.br
.sp 1
.in 1.5i
install \-m7555 \-o root \-g wheel sh /bin
.sp 1
.in -.5i
.br
(note the extra \*(lq5\*(rq).
See
.IR install.cf (5L)
and
.IR instck (1L).
.TP
.BI \-d
Build a directory rather than a plain file.
If the directory is an OLD directory it is built with appropriate modes
(see
.B \-V
below).
.TP
.B \-D
Don't back up an existing
.I target
(the \*(lqDestroy\*(rq option).
This is useful when a trivially correctable problem such as a
spelling error in a print statement is found in a recently installed
product, or when the
.I target
can be regenerated easily and is installed frequently.  Sites that
do not wish to keep backups but still want to take advantage of the
checkfile could set this option in the environment.
.TP
.BI \-g group
Install the file with group ownership
.IR group .
If no
.B \-g
option is given
.I install
will decide the group to use:
.br
.in 1.5i
\(bu if there is an existing
.I target
use its group
.br
\(bu if we are the superuser use a compilation\(hydependent group
.br
\(bu else use the effective group id
.TP
.in -.5i
.B \-h
Print a summary of
.IR install 's
usage (the \*(lqhelp\*(rq option).
.TP
.BI \-H hardlinks
Specify a colon separated list of hard links
that should be made to the
.I target
after it is installed.
(See EXAMPLES below.)
.TP
.BI \-l
Run
.IR ranlib ( 1 )
on the installed
.IR targets .
Under System V this option has no significance, but
.B must
be given to pass the default checkfile
(see
.IR install.cf (5L)).
This allows Makefiles to work under all versions of UNIX.
.TP
.BI \-m mode
Install the file with permissions
.IR mode .
.I Mode
may be given in either octal mode or
in the symbolic form used by
.IR ls (1)
(e.g., \*(lq755\*(rq) is equivalent to \*(lqrwxr-xr-x\*(rq).
If the
.B \-m
option is not given,
.I install
will decide the mode to use:
.br
.in 1.5i
\(bu if there is an existing
.I target
use its mode
.br
\(bu if we are the superuser use a compilation\(hydependent mode
.br
\(bu else use a compilation\(hydependent user mode
.in -.5i
.TP
.B \-n
Give an approximate execution trace by printing the (almost) equivalent
shell commands, but don't do anything.
This is useful for debugging
.I install
or seeing what a
difficult command line would do if you ran it.
.TP
.BI \-o owner
Change ownership of
.I file
to
.I owner
(superuser only).
If no
.B \-o
option is given
.I install
will decide the owner to use:
.br
.in 1.5i
\(bu if there is an existing
.I target
use its owner
.br
\(bu if we are the superuser use a compilation\(hydependent owner
.br
\(bu else use our effective uid
.TP
.in -.5i
.B \-p
Preserve the time stamp of
.I files
in
.IR targets .
.TP
.B \-q
Normally
.I install
informs you about a variety of possible errors.  This option
turns off that behavior and is not recommended except for
special circumstances.  Caveat emptor.
.TP
.B \-r
Under
.B \-d
build all intervening directories between \*(lq/\*(rq
and
.IR destination.
.TP
.B \-R
Remove (de\(hyinstall) a file by moving it into the OLD
subdirectory.  A temporary shell script is created to replace
the
.IR target ,
installed (which removes the existing
.I target
to \*(lqOLD\*(rq), and removed.
.TP
.B \-s
Run
.IR strip (1)
on the installed
.IR targets .
.TP
.BI \-S symlinks
Specify a colon separated list of symbolic links that
.I install
should
point at the installed file.  (See EXAMPLES below.)
.TP
.B \-v
Be verbose.  Run
.IR ls (1)
on the backed up file and the
.IR target .
Notify the user of all side effects of this installation.
.TP
.B \-V
View
.IR install 's
version and compilation\(hydependent owner, group and mode tables.
.SH EXAMPLES
.TP
install motd /etc
Install
.I motd
as
.IR /etc/motd .
If
.I /etc/motd
exists move it to
.I /etc/OLD/motd
and duplicate its
ownership, group and mode; otherwise,
use defaults.  Create the directory
.I /etc/OLD
if it does not exist.
.TP
install \-c1 \-m 755 foo.sh /etc/foo
Install
.IR foo.sh .
as
.IR /etc/foo .
Do not unlink
.I foo.sh
after the installation.  Set permissions appropriate for a shell script on
.IR /etc/foo .
If
.I /etc/OLD/foo
exists, overwrite it instead of moving it to a new name (i.e., retain a
single backup of
.I /etc/foo
in
.IR /etc/OLD ).
.TP
install foo bar baz /usr/lib
Install the files
.IR foo ,
.I bar
and
.I baz
as
.IR /usr/lib/foo ,
.IR /usr/lib/bar ,
and
.IR /usr/lib/baz .
Use the modes of the existing files or defaults.
.TP
install \-vsm6751 \-oroot \-gkmem sendmail /usr/lib
Install
.I sendmail
as
.IR /usr/lib/sendmail ,
owned by root, grouped to kmem, and with
the setuid and setgid permission bits set.
Strip
.I /usr/lib/sendmail
after its installation and be verbose.
.TP
install \-d \-m \-rwxrwxrwt /tmp
Build the directory
.I /tmp
with the default owner and group,
mode 777, and with the \*(lqsticky\*(rq bit set.
.TP
install \-c \-m1755 \-Hview:vi:edit:e:/usr/bin/ex a.out /usr/ucb/ex
Install the
.I ex
editor with all of its hard links
.RI ( /usr/ucb/view ,
.IR /usr/ucb/vi ,
.IR /usr/ucb/edit ,
.IR /usr/ucb/e ,
.RI and /usr/bin/ex ).
Replacing
.B \-H
with
.B \-S
would cause
.I install
to build symbolic links on machines which support them.
.TP
install \-d \-r /usr/local/lib/mk
Recursively build any and all of the directories
.IR /usr ,
.IR /usr/local ,
.IR /usr/local/lib ,
and
.I /usr/local/lib/mk
that do not already exist.  Silently do nothing if they
already exist (useful in Makefiles).
.TP
install \-V
Output the version of install and a table of compiled in defaults.
Output when run as the superuser might look similar to this,
depending on the compilation defaults:
.RS
.TS
l s s
l s s
l l l.
install: version: $\&Id: main.c,v 6.7 64/02/15 16:21:41 ksb Exp $
install: configuration file: /usr/local/etc/install.cf
install: syslog facility: 144
install: superuser defaults:
install: owner is file=root	dir=root	OLD=root
install: group is file=binary	dir=binary	OLD=binary
install: mode is  file=0755	dir=0755	OLD=inherited
.TE
.RE
.TP
rsh some.other.host install \- /etc/motd < motd.some.other.host
This example shows a way to use \*(lq\-\*(rq to advantage.  It is
often useful when
.IR rdist (1)
is overkill or otherwise inappropriate.  For instance, if you had
a Makefile that generated files named
.IR host1.motd ,
.IR host2.motd ,
.IR host3.motd ,
etc., and wanted to install them on those hosts, you could do
something like this:
.sp 1
.nf
.na
.KS
.RS
for host in host1 host2 host3; do
.RS
rsh $host install \- /etc/motd < $host.motd
.RE
done
.KE
.RE
.fi
.ad
.sp 1
.SH DIAGNOSTICS
.KS
.PP
Unless made quiet by
.BR \-q ,
.I install
will warn the installer if:
.RS
.br
\(bu the owner, group, or mode changes
.br
\(bu the setuid, setgid, or sticky bits change
.br
\(bu a setuid program is loaded with the \*(lq#!\*(rq magic number
.br
\(bu \fItarget\fP does not exist (this is a prophylactic against
typographical errors\(emsee CAVEATS below)
.KE
.RE
.PP
.KS
.I Install
will abort the installation if:
.RS
.br
\(bu
.I install
cannot make a backup of an existing
.I target
.br
\(bu a setuid program\'s owner was not specified with the mode
.br
\(bu a setgid program\'s group was not specified with the mode
.br
\(bu the specified owner, group, or mode failed to match the checkfile
.br
\(bu the superuser installs a setuid program that is not in the checkfile
(this is a compile time option)
.RE
.KE
.SH ENVIRONMENT
.PP
The environment variable
.B INSTALL
may be used to
set command line options.
Such options are read before any explicit command line options, e.g.
.br
.RS
.sp 1
INSTALL=-v ; export INSTALL
.sp 1
.RE
.br
will turn on \*(lqverbose\*(rq mode for all subsequent
invocations of
.IR install .
.SH BUGS
.PP
.I Install
does not use file locking, so it's possible for two competing
.I install
processes to lose data.
.PP
The trace option
.RB ( \-n )
doesn't always show exactly what
.I install
would do.  It will show OLD directories being made several times, and
inherited modes don't propagate correctly under
.BR \-drn .
.SH CAVEATS
.PP \(bu
.I Install
will not build character special files.
Use
.IR mknod (8)
instead.
.PP
If /bin doesn't exist, the command:
.sp 1
.RS
install ls /bin
.RE
.sp 1
will make
.I /bin
be a copy of the binary
.IR ls .
This is an unavoidable consequence of allowing the
.I destination
to be a directory.
You can avoid this by
using \*(lqinstall \-d \fIdirectory\fP\*(rq in Makefiles to ensure
that destination directories exist, e.g.,
.sp 1
.nf
.na
.RS
install: product
.RS
install -d /bin
install product /bin
.RE
.RE
.fi
.ad
.sp 1
.I Install
does nothing  and exits normally if the directory already exists,
so it's safe to include lines like this in your Makefiles.
You can also use
.IR instck (1L)
to ensure that all system
directories are built correctly.
.PP \(bu
.I Install
can cover up mistakes of omission in Makefiles by copying the modes
on a previously installed target.
For example, the invocation of
.I install
in a Makefile might not specify that the
.I destination
should be setuid root, but
as long as the
.I target
existed
.I install
would hide the error by
copying the modes of the existing
.IR target .
The problem in the Makefile would not be found unless the
.I target
were removed prior to installation.
Use
.IR instck (1L)
to generate a configuration file to avoid this problem.
.PP \(bu
.I Install
and
.IR purge (1L)
assume that they own the file namespace in the \*(lqOLD\*(rq
subdirectories.  If other programs create, modify or delete
files in the \*(lqOLD\*(rq subdirectories, they will probably
collide with one of
.I install
or
.IR purge (1L)
eventually.
.SH FILES
.TS
l l.
/usr/local/lib/install.cf	the default check list file
.../OLD/#inst*	temporary files made for installations
.../OLD/#bogus*	links made to running binaries to avoid ETXTBSY
.../OLD/*	copies of previously installed files
.TE
.SH AUTHORS
Kevin Braunsdorf, Purdue University Computing Center (ksb@cc.purdue.edu)
.br
Jeff Smith, Purdue University Computing Center (jsmith@cc.purdue.edu)
.br
Copyright \*(co 1990 Purdue Research Foundation.  All rights reserved.
.SH "SEE ALSO"
chgrp(1), instck(1L), ls(1), make(1), ranlib(1), strip(1),
intro(2),
syslog(3),
install.cf(5L),
chown(8), mknod(8), purge(8L)
Purdue
chmod 0444 install.d/install.1l ||
echo 'restore of install.d/install.1l failed'
Wc_c="`wc -c < 'install.d/install.1l'`"
test 21337 -eq "$Wc_c" ||
	echo 'install.d/install.1l: original size 21337, current size' "$Wc_c"
fi
# ============= purge/purge.c ==============
if test ! -d 'purge'; then
    echo 'x - creating directory purge'
    mkdir 'purge'
fi
if test -f 'purge/purge.c' -a X"$1" != X"-c"; then
	echo 'x - skipping purge/purge.c (File already exists)'
else
echo 'x - extracting purge/purge.c (Text)'
sed 's/^X//' << 'Purdue' > 'purge/purge.c' &&
/*
X * a C version of purge(8L)						(ksb)
X *
X * Copyright 1990 Purdue Research Foundation, West Lafayette, Indiana
X * 47907.  All rights reserved.
X *
X * Written by Kevin S Braunsdorf, ksb@cc.purdue.edu, purdue!ksb
X *
X * This software is not subject to any license of the American Telephone
X * and Telegraph Company or the Regents of the University of California.
X *
X * Permission is granted to anyone to use this software for any purpose on
X * any computer system, and to alter it and redistribute it freely, subject
X * to the following restrictions:
X *
X * 1. Neither the authors nor Purdue University are responsible for any
X *    consequences of the use of this software.
X *
X * 2. The origin of this software must not be misrepresented, either by
X *    explicit claim or by omission.  Credit to the authors and Purdue
X *    University must appear in documentation and sources.
X *
X * 3. Altered versions must be plainly marked as such, and must not be
X *    misrepresented as being the original software.
X *
X * 4. This notice may not be removed or altered.
X *
X *
X * The old shell version of purge suffered from 4 problems:
X *
X * 1\ it used the -ctime option to find that is local to PUCC
X *
X * 2\ it didn't handle symbolic links correctly
X *
X * 3\ it didn't handle error conditions well (st_nlink > 1)
X *
X * 4\ it was not useful to general users
X *
X * READ THIS!  N.B. if you want to change purge:
X *
X *	Do *NOT* change purge to delete empty OLD dirs.  This will race
X *	with install and cause install the fail. (As is won't build the
X *	OLD dir, the dir will go away, install will try to build a link
X *	in it and fail.)
X */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/time.h>
X
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
X
#include "configure.h"
#include "install.h"
#include "purge.h"
#include "filedup.h"
#include "main.h"
X
#if BSDDIR
#include <sys/dir.h>
#else
#include <ndir.h>
#endif
X
#if !defined(ENAMETOOLONG)
#define ENAMETOOLONG E2BIG
#endif
X
extern char *malloc(), *realloc(), *strcpy(), *strrchr();
X
#ifndef lint
static char copyright[] =
"@(#) Copyright 1990 Purdue Research Foundation.\nAll rights reserved.\n";
#endif
X
static time_t
X	tCutOff,		/* the last install time we keep	*/
X	tCopyKeep;		/* last temp copy to keep		*/
static char acRoot[] =		/* could be "root" "lroot" or "0"	*/
X	DEFDIROWNER;
static char acOld[] =		/* the name of our OLD dirs		*/
X	OLDDIR;
static char acBogus[] =		/* component key for busy links		*/
X	TMPBOGUS;
static char acCopy[] =		/* component key for temp copies	*/
X	TMPINST;
static FILEDUPS FDLinks;	/* hard links database			*/
static int
X	bHaveRoot,		/* are effective uid lets us be root	*/
X	iCopy,			/* strlen of acCopy			*/
X	iBogus;			/* strlen of acBogus			*/
X
#define LSTAT	lstat
X
struct {
X	int size;		/* the size of the uid array itself	*/
X	int count;		/* number of ids we are keeping		*/
X	uid_t *puids;		/* the uid array we are keeping		*/
X	char **ppclogins;	/* the login names we found		*/
} ids = {0, 0, (uid_t *)0};
X
/* this routine should check for all the OLD dir owners			(ksb)
X * (like `root', `news', `ingress', etc)
X */
int
isSysId(uid)
uid_t uid;
{
X	register int j;
X
X	if (fAnyOwner)
X		return 1;
X	for (j = 0; j < ids.count; ++j) {
X		if (ids.puids[j] == uid) {
X			return 1;
X		}
X	}
X	return 0;
}
X
/*
X * is a string all digits						(ksb)
X */
int
AllDigits(pcTemp)
char *pcTemp;
{
X	for (; '\000' != *pcTemp; ++pcTemp) {
X		if (!isdigit(*pcTemp))
X			return 0;
X	}
X	return 1;
}
X
/*
X * this routine adds a new valid OLD dir owner to the list		(ksb)
X */
int
AddHer(pcLogin)
char *pcLogin;
{
X	register int j;
X	register struct passwd *pwdAdd;
X
X	if (AllDigits(pcLogin)) {
X		if ((struct passwd *)0 == (pwdAdd = getpwuid(atoi(pcLogin)))) {
X			fprintf(stderr, "%s: getpwuid: %s: %s\n", progname, pcLogin, strerror(errno));
X			return 1;
X		}
X	} else if ((struct passwd *)0 == (pwdAdd = getpwnam(pcLogin))) {
X		fprintf(stderr, "%s: getpwname: %s: %s\n", progname, pcLogin, strerror(errno));
X		return 1;
X	}
X	if (ids.size < ids.count+1) {
X		ids.size += 8;
X		if ((uid_t *)0 == ids.puids) {
X			ids.puids = (uid_t *)malloc(ids.size*sizeof(uid_t));
X		} else {
X			ids.puids = (uid_t *)realloc((char *)ids.puids, ids.size*sizeof(uid_t));
X		}
X		if ((char **)0 == ids.ppclogins) {
X			ids.ppclogins = (char **)malloc(ids.size*sizeof(char *));
X		} else {
X			ids.ppclogins = (char **)realloc((char *)ids.ppclogins, ids.size*sizeof(char *));
X		}
X		if ((uid_t *)0 == ids.puids || (char **)0 == ids.ppclogins) {
X			fprintf(stderr, "%s: out of memory in realloc\n", progname);
X			exit(2);
X		}
X	}
X	for (j = 0; j < ids.count; ++j) {
X		if (ids.puids[j] != pwdAdd->pw_uid) {
X			continue;
X		}
X		return 0;
X	}
X	ids.puids[j] = pwdAdd->pw_uid;
X	ids.ppclogins[j] = pcLogin;
X	++ids.count;
X	return 0;
}
X
/*
X * this routine added the default user (the envoker) to the OLD		(ksb)
X * owners list (set up the time flags too)
X */
void
InitAll()
{
X	register char *pcUser, *pcX;
X	struct passwd *pwdUser;
X	extern char *getenv();
X	extern long time();
X
X	/* remove the XXXXX from the mktemp templates
X	 */
X	pcX = acBogus + (sizeof(acBogus)-2);
X	while ('X' == *pcX)
X		*pcX-- = '\000';
X	pcX = acCopy + (sizeof(acCopy)-2);
X	while ('X' == *pcX)
X		*pcX-- = '\000';
X	iBogus = strlen(acBogus);
X	iCopy = strlen(acCopy);
X
X	FDInit(& FDLinks);
X	if (0 == getuid())
X		fSuperUser = 1;
X	(void)time(&tCutOff);
X	tCopyKeep = tCutOff - ((time_t)(60*60*24));
X	tCutOff -= ((time_t)(60*60*24))*iDays;
X
X	(void)setpwent();
X
X	bHaveRoot = 0 == geteuid();
X
#if defined(INST_FACILITY)
X	if (bHaveRoot && fExec) {
X		openlog(progname, 0, INST_FACILITY);
X	}
#endif
X	if (fSuperUser) {
X		(void)AddHer(acRoot);
X		return;
X	}
X
X	pcUser = getenv("USER");
X	if ((char *)0 == pcUser || '\000' == pcUser[0]) {
X		pcUser = getenv("LOGNAME");
X	}
X	if ((char *)0 == pcUser || '\000' == pcUser[0]) {
X		if ((struct passwd *)0 == (pwdUser = getpwuid(getuid()))) {
X			fprintf(stderr, "%s: getpwuid: %d: %s\n", progname, getuid(), strerror(errno));
X			exit(1);
X		}
X		pcUser = pwdUser->pw_name;
X	}
X	pcUser = strcpy(malloc((strlen(pcUser)|7)+1), pcUser);
X	(void)AddHer(pcUser);
}
X
X
/*
X * show the user what we are compiled to do, as best we can		(ksb)
X */
int
Version()
{
X	register int i;
X
X	printf("%s: $Id: purge.c,v 3.1 90/11/26 12:26:41 ksb Exp $\n", progname);
X	printf("%s: default superuser login name: %s\n", progname, acRoot);
X	printf("%s: backup directory name: %s\n", progname, acOld);
X	printf("%s: valid backup directory owner", progname);
X	if (fAnyOwner) {
X		printf(": <any>");
X	} else {
X		if (ids.count > 1)
X			printf("s");
X		for (i = 0; i < ids.count; ++i)
X			printf("%c %s", i == 0 ? ':' : ',', ids.ppclogins[i]);
X	}
#if defined(INST_FACILITY)
X	printf("\n%s: syslog facility %d\n", progname, INST_FACILITY);
#else
X	printf("\n");
#endif
X	return 0;
}
X
X
/*
X * get the user-level name for this node `plain file' or `socket'	(ksb)
X *	return string, ref-out single char
X */
char *
NodeType(mType, pcChar)
unsigned int mType;
char *pcChar;
{
X	auto char acWaste[2];
X
X	if ((char *)0 == pcChar)
X		pcChar = acWaste;
X
X	switch (mType & S_IFMT) {
X	case S_IFDIR:
X		*pcChar = 'd';
X		return "directory";
#if defined(S_IFSOCK)
X	case S_IFSOCK:
X		*pcChar = 's';
X		return "socket";
#endif	/* no sockets */
#if defined(S_IFIFO)
X	case S_IFIFO:
X		*pcChar = 'p';
X		return "fifo";
#endif
#if HAVE_SLINKS
X	case S_IFLNK:
X		*pcChar = 'l';
X		return "symbolic link";
#endif
X	case S_IFBLK:
X		*pcChar = 'b';
X		return "block device";
X	case S_IFCHR:
X		*pcChar = 'c';
X		return "character device";
X	case S_IFREG:
X	case 0:
X		*pcChar = '-';
X		return "plain file";
X	default:
X		break;
X	}
X	*pcChar = '?';
X	return "type unknown";
}
X
/*
X * avoid putting . and .. in the dirs to check, we would loop		(ksb)
X */
int
DotSel(pDE)
struct direct *pDE;
{
X	register char *pcName = pDE->d_name;
X
X	return ! ('.' == pcName[0] && ('\000' == pcName[1] || '.' == pcName[1] && '\000' == pcName[2]));
}
X
/*
X * remove the file, or fake the removal					(ksb)
X */
void
Remove(pcFile)
char *pcFile;
{
X	if (fVerbose) {
X		printf("%s: rm -f %s\n", progname, pcFile);
X	}
X	if (fExec && -1 == unlink(pcFile)) {
#if defined(INST_FACILITY)
X		if (EBUSY == errno && fExec && bHaveRoot) {
X			syslog(LOG_INFO, "still busy `%s\'", pcFile);
X		}
#endif
X		fprintf(stderr, "%s: unlink: %s: %s\n", progname, pcFile, strerror(errno));
X		return;
X	}
#if defined(INST_FACILITY)
X	if (fExec && bHaveRoot) {
X		syslog(LOG_INFO, "rm `%s\'", pcFile);
X	}
#endif
}
X
/*
X * purge an OLD directory of outdated backup files			(ksb)
X * remove bogus links on any age
X * do not touch #inst$$ files unless ctime is > boot time?
X */
void
Purge(pcOld, pstOld)
char *pcOld;
struct stat *pstOld;
{
X	register struct direct *pDE;
X	register int i;
X	auto struct direct **ppDEDir;
X	auto char acFile[MAXPATHLEN+2], *pcLast;
X	auto int iLen;
X	auto struct stat stFile;
X
X	iLen = MAXPATHLEN - strlen(pcOld);
X	if (iLen <= 0) {
X		fprintf(stderr, "%s: Purge: %.40s... %s\n", progname, pcOld, strerror(ENAMETOOLONG));
X		return;
X	}
X	(void)strcpy(acFile, pcOld);
X	pcLast = acFile + (MAXPATHLEN - iLen);
X	if ('/' != pcLast[-1])
X		*pcLast++ = '/', --iLen;
X	if (-1 == (i = scandir(pcOld, &ppDEDir, DotSel, (int (*)())0))) {
X		fprintf(stderr, "%s: scandir: %s: %s\n", progname, pcOld, strerror(errno));
X		return;
X	}
X	for (; i > 0; free((char *)pDE)) {
X		pDE = ppDEDir[--i];
X		if (pDE->d_namlen > iLen) {
X			fprintf(stderr, "%s: Purge: ...%s/%.20s... %s\n", progname, ((20 < MAXPATHLEN - iLen) ? pcOld : pcLast-20), pDE->d_name, strerror(ENAMETOOLONG));
X			continue;
X		}
X		(void)strcpy(pcLast, pDE->d_name);
X		if (-1 == LSTAT(acFile, & stFile)) {
X			if (errno != ENOENT) {
X				fprintf(stderr, "%s: stat: %s: %s\n", progname, acFile, strerror(errno));
X			}
X			continue;
X		}
X		switch (stFile.st_mode & S_IFMT) {
X		default:
X			fprintf(stderr, "%s: %s: is a %s?\n", progname, acFile, NodeType(stFile.st_mode, (char *)0));
X			continue;
#if HAVE_SLINKS
X		case S_IFLNK:
#endif
X		case S_IFREG:
X		case 0:
X			break;
X		}
X		/* if the file is a bogus link to a running program,
X		 * try to remove it always...
X		 */
X		if (0 == strncmp(pcLast, acBogus, iBogus)) {
X			Remove(acFile);
X			continue;
X		}
X
X		/* if the file is a temp file used by install ignore it
X		 * for at least 1 day, then remove it
X		 */
X		if (0 == strncmp(pcLast, acCopy, iCopy)) {
X			if (tCopyKeep <= stFile.st_ctime) {
X				continue;
X			}
X		} else if (tCutOff <= stFile.st_ctime) {
X			continue;
X		}
X
X		/* sticky problem -- we should not remove the evidence of a
X		 * poor install (the product might still be installed in a
X		 * binary directory) so we need to keep this node here...
X		 * but if we find it again we can remove it, see?
X		 */
X		if (stFile.st_nlink > 1) {
X			register char *pcLink;
X
X			pcLink = FDAdd(&FDLinks, acFile, &stFile);
X			if (0 == strcmp(acFile, pcLink)) {
X				continue;
X			}
X		}
X		Remove(acFile);
X	}
X	free((char *)ppDEDir);
}
X
/*
X * scan under a directory of all the OLD dirs and purge them		(ksb)
X */
void
Scan(pcDir)
char *pcDir;
{
X	register struct direct *pDE;
X	register int i;
X	auto struct direct **ppDEDir;
X	auto char acDown[MAXPATHLEN+2], *pcLast;
X	auto struct stat stDown;
X	auto int iLen;
X
X	if ((char *)0 == pcDir || '\000' == *pcDir)
X		pcDir = ".";
X	iLen = MAXPATHLEN - strlen(pcDir);
X	if (iLen <= 0) {
X		fprintf(stderr, "%s: Scan: %.40s... %s\n", progname, pcDir, strerror(ENAMETOOLONG));
X		return;
X	}
X	(void)strcpy(acDown, pcDir);
X	pcLast = acDown + (MAXPATHLEN - iLen);
X	if ('/' != pcLast[-1])
X		*pcLast++ = '/', --iLen;
X	if (-1 == (i = scandir(pcDir, &ppDEDir, DotSel, (int (*)())0))) {
X		fprintf(stderr, "%s: scandir: %s: %s\n", progname, pcDir, strerror(errno));
X		return;
X	}
X	for (; i > 0; free((char *)pDE)) {
X		pDE = ppDEDir[--i];
X		if (pDE->d_namlen > iLen) {
X			fprintf(stderr, "%s: Scan: ...%s/%.20s... %s\n", progname, ((20 < MAXPATHLEN - iLen) ? pcDir : pcLast-20), pDE->d_name, strerror(ENAMETOOLONG));
X			continue;
X		}
X		(void)strcpy(pcLast, pDE->d_name);
X		if (-1 == LSTAT(acDown, & stDown)) {
X			if (errno != ENOENT) {
X				fprintf(stderr, "%s: stat: %s: %s\n", progname, acDown, strerror(errno));
X			}
X			continue;
X		}
X		if (0 == strcmp(acOld, pDE->d_name)) {
X			if (isSysId((uid_t)stDown.st_uid)) {
X				Purge(acDown, & stDown);
X			}
X			continue;
X		}
X		/* if this directory an nfs (NFS) mount point?
X		 * We used to use:
X		 *	(255 == major(stDown.st_dev))
X		 */
X		if (0 != (0x80 & major(stDown.st_dev))) {
X			continue;
X		}
X		if (S_IFDIR == (stDown.st_mode & S_IFMT)) {
X			Scan(acDown);
X		}
X	}
X	free((char *)ppDEDir);
}
X
/*
X * let purge read paths to purge from find(1) output			(ksb)
X */
static void
DoStdin()
{
X	static int fBeenHere = 0;
X	register char *pcNl;
X	auto char acIn[MAXPATHLEN+2];
X	auto int c;
X
X	if (fBeenHere++) {
X		fprintf(stderr, "%s: will not recurse on stdin (skipped)\n", progname);
X		return;
X	}
X	while ((char *)0 != fgets(acIn, MAXPATHLEN+1, stdin)) {
X		if ((char *)0 == (pcNl = strchr(acIn, '\n'))) {
X			fprintf(stderr, "%s: line too long on stdin\n", progname);
X			while (EOF != (c = getc(stdin))) {
X				if ('\n' == c)
X					break;
X			}
X			continue;
X		}
X		*pcNl = '\000';
X		Which(acIn);
X	}
X	clearerr(stdin);
X	--fBeenHere;
}
X
/*
X * the user may have either given us /bin or /bin/OLD to purge.		(ksb)
X * choose the correct routine and purge it.
X *
X * if an old script gives us a number of days do / with that number (yucko).
X */
void
Which(pcDir)
char *pcDir;
{
X	register char *pcSlash;
X	auto struct stat stDir;
X
X	if (AllDigits(pcDir) && -1 == access(pcDir, 0)) {
X		fprintf(stderr, "%s: warning: old style [ndays] option\n", progname);
X		iDays = atoi(pcDir);
X		pcDir = "/";
X	}
X	if ('-' == pcDir[0] && '\000' == pcDir[1]) {
X		DoStdin();
X		return;
X	}
X	if ((char *)0 == (pcSlash = strrchr(pcDir, '/'))) {
X		pcSlash = pcDir;
X	} else {
X		++pcSlash;
X	}
X	if (0 == strcmp(acOld, pcSlash)) {
X		if (-1 == LSTAT(pcDir, & stDir)) {
X			fprintf(stderr, "%s: stat: %s: %s\n", progname, pcDir, strerror(errno));
X		} else if (!isSysId((uid_t)stDir.st_uid)) {
X			fprintf(stderr, "%s: %s: %d: not a user to purge\n", progname, pcDir, stDir.st_uid);
X		} else {
X			Purge(pcDir, &stDir);
X		}
X	} else {
X		Scan(pcDir);
X	}
}
X
/*
X * this checks to see if we recovered from the errors we found		(ksb)
X */
static int
StillBad(pAE)
AE_ELEMENT *pAE;
{
X	auto struct stat stThis;
X	register char *pcSlash;
X
X	if (-1 != LSTAT(pAE->pcname, &stThis) && stThis.st_nlink > 1) {
X		if ((char *)0 != (pcSlash = strrchr(pAE->pcname, '/'))) {
X			*pcSlash = '\000';
X			fprintf(stderr, "%s: %s/%s: link count %d, use `instck -i %s\' to repair\n", progname, pAE->pcname, pcSlash+1, stThis.st_nlink, pAE->pcname);
X			*pcSlash = '/';
X		} else {
X			fprintf(stderr, "%s: %s: has a link count > 1, use instck to repair\n", progname, pAE->pcname);
X		}
X	} else {
X		Remove(pAE->pcname);
X	}
X	return 0;
}
X
/*
X * When done scan any file that have bad link counts and see if we 	(ksB)
X * removed the other links via purging OLD dirs
X */
void
Done()
{
X	(void)FDScan(FDLinks, StillBad);
X
#if defined(INST_FACILITY)
X	if (bHaveRoot && fExec) {
X		closelog();
X	}
#endif
}
Purdue
chmod 0444 purge/purge.c ||
echo 'restore of purge/purge.c failed'
Wc_c="`wc -c < 'purge/purge.c'`"
test 14969 -eq "$Wc_c" ||
	echo 'purge/purge.c: original size 14969, current size' "$Wc_c"
fi
# ============= install.d/special.i ==============
if test -f 'install.d/special.i' -a X"$1" != X"-c"; then
	echo 'x - skipping install.d/special.i (File already exists)'
else
echo 'x - extracting install.d/special.i (Text)'
sed 's/^X//' << 'Purdue' > 'install.d/special.i' &&
/*
X * $Id: special.i,v 7.2 90/10/22 11:47:15 ksb Exp $
X * Copyright 1990 Purdue Research Foundation, West Lafayette, Indiana
X * 47907.  All rights reserved.
X *
X * Written by Kevin S Braunsdorf, ksb@cc.purdue.edu, purdue!ksb
X *
X * This software is not subject to any license of the American Telephone
X * and Telegraph Company or the Regents of the University of California.
X *
X * Permission is granted to anyone to use this software for any purpose on
X * any computer system, and to alter it and redistribute it freely, subject
X * to the following restrictions:
X *
X * 1. Neither the authors nor Purdue University are responsible for any
X *    consequences of the use of this software.
X *
X * 2. The origin of this software must not be misrepresented, either by
X *    explicit claim or by omission.  Credit to the authors and Purdue
X *    University must appear in documentation and sources.
X *
X * 3. Altered versions must be plainly marked as such, and must not be
X *    misrepresented as being the original software.
X *
X * 4. This notice may not be removed or altered.
X */
X
/*
X * this (long) C routine is expanded twice in instck, and only once
X * in install.  It is the one that searchs a config file.
X */
X
#if defined(INSTCK)
/*
X * Scan the gien dirs for pats in the check file list?  		(ksb)
X * config file format:
X *	#file	mode		owner	group	strip	message
X *	/unix	-r??r--r--	root	*	n
X * `*' = any
X * `"' = last
X * `=' = the default for this col
X * `!' = not (!mode -> not exist, !group/!owner -> any but the one listed)
X */
void
DirCk(iCount, ppcDirs, pcConfig, pCL)
int iCount;		/* who many dirs to search			*/
char **ppcDirs;		/* dirs to search				*/
#else /* install */
void
/*
X * Is the named file in the check file list?  If so is it OK?		(ksb)
X *	FAIL is file it in list and modes are bad
X *	else SUCCESS
X * config file format:
X *	#file	mode		owner	group	strip	message
X *	/unix	-r??r--r--	root	*	n
X *	core	!r?-?--?--	*	*	n
X * `*' = any
X * `"' = last
X * `=' = the default for this col
X * `!' = not (!mode -> not exist, !group/!owner -> any but the one listed)
X */
Special(pcFile, pcConfig, pCL)
char *pcFile;		/* file we are searching for			*/
#endif
char *pcConfig;		/* check list file name to read			*/
CHLIST *pCL;		/* pointer to the check list to build/use	*/
{
X	extern char *malloc();
X	static FILE *fpCnf = (FILE *)0;
X	static char *pcCnf = "jms & ksb";
X	static char acLine[MAXCNFLINE];
X	static char acMesg[MAXCNFLINE];
X	static char acEq[] = "=", acAny[] = "*";
X	register char *pcSkip, *pcBlank;
X	register int iLine;
#if defined(INSTCK)
X	register int i;
X	auto char *pcCurGlob;
#else
X	auto char *pcLastComp;
X	auto char acFPath[MAXPATHLEN+1];
#endif
X
X	pCL->ffound = FALSE;
X
X	if ((char *)0 == pcConfig || '\000' == pcConfig[0]) {
#if defined(INSTCK)
X		(void)printf("%s: no configuration file\n", progname);
#endif
X		return;
X	}
X	if (NULL == fpCnf || pcCnf != pcConfig) {
X		if (NULL != fpCnf)
X			(void)fclose(fpCnf);
X		pcCnf = pcConfig;
X		if (NULL == (fpCnf = fopen(pcCnf, "r"))) {
X			(void)fprintf(stderr, "%s: fopen (checklist): %s: %s\n", progname, pcCnf, strerror(errno));
X			exit(EXIT_OPT);
X		}
#if defined(F_SETFD)
X		/* if we can we set the close-on-exec bit to be neat */
X		(void)fcntl(fileno(fpCnf), F_SETFD, 1);
#endif	/* have close on exec bit to twidle	*/
X	} else {
X		(void)rewind(fpCnf);
X	}
X	pCL->pcspecial = pcCnf;
X
#if defined(INSTALL)
X	if ('/' != pcFile[0]) {
X		(void)getwd(acFPath);
X		(void)strcat(acFPath, "/");
X		(void)strcat(acFPath, pcFile);
X		pcFile = CompPath(acFPath);
X	} else {
X		(void)strcpy(acFPath, pcFile);	/* don't munge argument! */
X		pcFile = CompPath(acFPath);
X	}
X
X	if ((char *)0 == (pcLastComp = strrchr(pcFile, '/'))) {
X		Die("nil pointer");
X	}
X	++pcLastComp;
#endif
X
X	/* we set the defaults:
X	 * not found, plain file, mode, owner, group, no strip
X	 */
X	iLine = 0;
X	(void)strcpy(acLine, "* = = = =\n");
X	(void)strcpy(acMesg, "default message");
X	pCL->pcmesg = acMesg;
X	pCL->pclink = (char *)0;
X	pCL->acmode[0] = pCL->acmode[1] = '\000';
X
X	do {
X		if ((char *)0 == (pcBlank = strchr(acLine, '\n'))) {
X			(void)fprintf(stderr, "%s: %s(%d) line too long\n", progname, pcCnf, iLine);
X			exit(EXIT_OPT);
X		}
X		do {
X			*pcBlank = '\000';
X		} while (pcBlank != acLine && (--pcBlank, isspace(*pcBlank)));
X		for (pcSkip = acLine; isspace(*pcSkip); ++pcSkip)
X			/*empty*/;
X		if ('\000' == pcSkip[0] || '#' == pcSkip[0])
X			continue;
X
X		for (pcBlank = pcSkip; ! isspace(*pcBlank); ++pcBlank)
X			/*empty*/;
X		while (isspace(*pcBlank))
X			*pcBlank++ = '\000';
X		pCL->iline = iLine;
X		pCL->pcpat = pcSkip;
#if defined(INSTCK)
X		pcCurGlob = pcSkip;
#else
X		if (0 != iLine) {
X			pCL->ffound = SamePath(pcSkip, RJust(pcSkip, pcFile, pcLastComp));
X		}
#endif
X
X		/* mode or link */
X		pcSkip = pcBlank;
X		for (pcBlank = pcSkip; ! isspace(*pcBlank); ++pcBlank)
X			/*empty*/;
X		while (isspace(*pcBlank))
X			*pcBlank++ = '\000';
X
X		/* file is a link */
X		if ('@' == pcSkip[0] || ':' == pcSkip[0]) {
X			pCL->pclink = strcpy(malloc(strlen(pcSkip)+1),pcSkip);
X			goto at_comment;
X		} else if ((char *)0 != pCL->pclink) {
X			free(pCL->pclink);
X			pCL->pclink = (char *)0;
X		}
X		if ('~' == pcSkip[0] || '!' == pcSkip[0] || '*' == pcSkip[0]) {
X			(void)strcpy(pCL->acmode, pcSkip);
X			if ('\000' != pcSkip[1]) {
X				CvtMode(pcSkip+1, &pCL->mmust, &pCL->moptional);
X				pCL->mtype = pCL->mmust & S_IFMT;
X				pCL->mmust &= 07777;
X			} else {
X				pCL->mtype = 0;
X				pCL->mmust = 0;
X				pCL->moptional = 0644;
X			}
X		} else if (EQUAL(pcSkip, acEq)) {
X			if ((char *)0 == DEFMODE) {
X				pCL->acmode[0] = '*';
X			} else {
X				CvtMode(DEFMODE, &pCL->mmust, &pCL->moptional);
X				pCL->acmode[0] = '\000';
X			}
X		} else if ('\"' == pcSkip[0] && '\000' == pcSkip[1]) {
X			/* use previous */;
X		} else {
X			CvtMode(pcSkip, &pCL->mmust, &pCL->moptional);
X			pCL->moptional &= 07777;
X			pCL->acmode[0] = '\000';
X		}
X		if ('\000' == pCL->acmode[0]) {
X			pCL->mtype = pCL->mmust & S_IFMT;
X			pCL->mmust &= 07777;
X			(void)NodeType(pCL->mtype, pCL->acmode);
X		}
X		if (0 == pCL->mtype) {
X			pCL->mtype = S_IFREG;
X		}
X
X		/* owner */
X		pcSkip = pcBlank;
X		for (pcBlank = pcSkip; ! isspace(*pcBlank); ++pcBlank)
X			/*empty*/;
X		while (isspace(*pcBlank))
X			*pcBlank++ = '\000';
X		pCL->fbangowner = 0;
X		if ('!' == *pcSkip) {
X			pCL->fbangowner = 1;
X			++pcSkip;
X		}
X		if (EQUAL(pcSkip, acEq)) {
X			if ((struct passwd *)0 != pwdDef) {
X				(void)strcpy(pCL->acowner, pwdDef->pw_name);
X			} else if ((char *)0 == DEFOWNER) {
X				(void)strcpy(pCL->acowner, acAny);
X			} else {
X				(void)strcpy(pCL->acowner, DEFOWNER);
X			}
X		} else if (! EQUAL(pcSkip, "\"")) {
X			(void)strcpy(pCL->acowner, pcSkip);
X		}
X
X		/* group */
X		pcSkip = pcBlank;
X		for (pcBlank = pcSkip; ! isspace(*pcBlank); ++pcBlank)
X			/*empty*/;
X		while (isspace(*pcBlank))
X			*pcBlank++ = '\000';
X		pCL->fbanggroup = 0;
X		if ('!' == *pcSkip) {
X			pCL->fbanggroup = 1;
X			++pcSkip;
X		}
X		if (EQUAL(pcSkip, acEq)) {
X			if ((struct group *)0 != grpDef) {
X				(void)strcpy(pCL->acgroup, grpDef->gr_name);
X			} else if ((char *)0 == DEFGROUP) {
X				(void)strcpy(pCL->acgroup, acAny);
X			} else {
X				(void)strcpy(pCL->acgroup, DEFGROUP);
X			}
X		} else if (! EQUAL(pcSkip, "\"")) {
X			(void)strcpy(pCL->acgroup, pcSkip);
X		}
X
X		/* strip or not */
X		pcSkip = pcBlank;
X		for (pcBlank = pcSkip; '\000' != *pcBlank && ! isspace(*pcBlank); ++pcBlank)
X			/*empty*/;
X		while (isspace(*pcBlank))
X			*pcBlank++ = '\000';
X		if (EQUAL(pcSkip, acEq)) {
X			pCL->chstrip = CF_NONE;
X		} else switch (*pcSkip) {
X		case 's':		/* yeah we should take these too */
X		case 'S':
X			pCL->chstrip = CF_STRIP;
X			break;
X		case 'l':
X		case 'L':
X			pCL->chstrip = CF_RANLIB;
X			break;
X		default:
X			(void)fprintf(stderr, "%s: unknown strip indicator `%s\'\n", progname, pcSkip);
X			/* fall through to recover */
X		case '-':
X		case 'n':
X		case 'N':
X			pCL->chstrip = CF_NONE;
X			break;
X		case '*':
X		case '\?':
X			pCL->chstrip = CF_ANY;
X			break;
X		case '\"':
X			break;
X		}
X
X	at_comment:
X		if (! EQUAL(pcBlank, "\"")) {
X			(void)strcpy(acMesg, pcBlank);
X		}
X
X		if (0 == iLine)
X			continue;
X
#if defined(INSTALL)
X		if (!pCL->ffound) {
X			continue;
X		}
#endif
X
X		if ('*' != pCL->acowner[0] || '\000' != pCL->acowner[1]) {
X			struct passwd *pwd;
X			if ((struct passwd *)0 != pwdDef && 0 == strcmp(pCL->acowner, pwdDef->pw_name)) {
X				pCL->uid = pwdDef->pw_uid;
X			} else if ((struct passwd *)0 == (pwd = getpwnam(pCL->acowner))) {
X				fprintf(stderr, "%s: getpwname: %s: no such user\n", progname, pCL->acowner);
X				continue;
X			} else {
X				pCL->uid = pwd->pw_uid;
X			}
X		} else if (0 != (S_ISUID & pCL->mmust)) {
X			BadSet(iLine, 'u', "user", pCL->acmode);
X		}
X		if ('*' != pCL->acgroup[0] || '\000' != pCL->acgroup[1]) {
X			struct group *grp;
X			if ((struct group *)0 != grpDef && 0 == strcmp(pCL->acgroup, grpDef->gr_name)) {
X				pCL->gid = grpDef->gr_gid;
X			} else if ((struct group *)0 == (grp = getgrnam(pCL->acgroup))) {
X				fprintf(stderr, "%s: getgrname: %s: no such group\n", progname, pCL->acgroup);
X				continue;
X			} else {
X				pCL->gid = grp->gr_gid;
X			}
X		} else if (0 != (S_ISGID & pCL->mmust)) {
X			BadSet(iLine, 'g', "group", pCL->acmode);
X		}
X
X		ModetoStr(pCL->acmode+1, pCL->mmust, pCL->moptional);
#if defined(INSTCK)
X		iMatches = 0;
X		if ('/' == *pcCurGlob) {
X			FileMatch(pcCurGlob+1, "/", DoCk);
X			if (0 == iMatches) {
X				NoMatches(pcCurGlob);
X			}
X		} else {
X			/* never a no-matches check here, might not have
X			 * any command line dirs to search, etc [ksb]
X			 */
X			for (i = 0; i < iCount; ++i) {
X				FileMatch(pcCurGlob, ppcDirs[i], DoCk);
X			}
X		}
#else
X		if (FALSE != fVerbose || FALSE != fTrace) {
X			(void)printf("%s: found `%s\' in %s\n", progname, pcFile, pcConfig);
X		}
X		break;
#endif
X	} while (++iLine, NULL != fgets(acLine, MAXCNFLINE, fpCnf));
}
Purdue
chmod 0444 install.d/special.i ||
echo 'restore of install.d/special.i failed'
Wc_c="`wc -c < 'install.d/special.i'`"
test 9712 -eq "$Wc_c" ||
	echo 'install.d/special.i: original size 9712, current size' "$Wc_c"
fi
# ============= purge/Makefile ==============
if test -f 'purge/Makefile' -a X"$1" != X"-c"; then
	echo 'x - skipping purge/Makefile (File already exists)'
else
echo 'x - extracting purge/Makefile (Text)'
sed 's/^X//' << 'Purdue' > 'purge/Makefile' &&
#	$Id: Makefile.plain,v 7.2 90/11/28 08:52:39 ksb Exp $
#
#	Makefile for purge
#
X
PROG=	purge
BIN=	${DESTDIR}/usr/local/etc
MANROOT= ${DESTDIR}/usr/man
X
# where is the source code for install(1L)?
INSTALLD= ../install.d
#INSTALLD= /usr/src/usr.bin/install.d
#INSTALLD= /usr/src/local/cmd/install.d
X
# which type of links to build
#LN=ln
LN=ln -s
X
L=../libopt
#L=/usr/include/local
I=/usr/include
S=/usr/include/sys
P=
X
INCLUDE= -I$L
DEBUG=	-O
CDEFS=  
CFLAGS= ${DEBUG} ${CDEFS} ${INCLUDE}
X
LINKH=	configure.h install.h
LINKC=	
LINK=	${LINKH} ${LINKC}
GENH=	${LINKH}
GENC=	${LINKC}
GEN=	${GENC} ${GENH}
HDR=	main.h purge.h filedup.h
SRC=	main.c purge.c filedup.c
DEP=	${GENC} ${SRC}
OBJ=	main.o purge.o filedup.o
MAN=	purge.8l
OTHER=	README purge.m
SOURCE=	Makefile ${OTHER} ${MAN} ${HDR} ${SRC}
X
all: ${PROG}
X
${PROG}:$P ${OBJ}
#	${CC} -o $@ ${CFLAGS} ${OBJ} -lopt
#	${CC} -o $@ ${CFLAGS} ${OBJ} -L /usr/local/lib -lopt
X	${CC} -o $@ ${CFLAGS} ${OBJ} ../libopt/libopt.a
X
#main.h: main.c
#
#main.c: ${PROG}.m
#	mkcmd std_help.m ${PROG}.m
#	-(cmp -s prog.c main.c || (cp prog.c main.c && echo main.c updated))
#	-(cmp -s prog.h main.h || (cp prog.h main.h && echo main.h updated))
#	rm -f prog.[ch]
X
swap: ${PROG}.m
X	mv Makefile Makefile.plain
X	mv Makefile.mkcmd Makefile
X	mkcmd std_help.m ${PROG}.m
X	-cmp -s prog.c main.c || echo main.c changed
X	-cmp -s prog.h main.h || echo main.h changed
X	rm -f prog.[ch]
X	make depend
X
links: ${LINK}
X
${LINK}:
X	${LN} ${INSTALLD}/$@ ./$@
X
clean: FRC
X	rm -f Makefile.bak ${PROG} ${GEN} *.o a.out core errs lint.out tags
X
calls: ${SRC} ${HDR} ${GEN} FRC
X	calls ${CDEFS} ${INCLUDE} ${DEP}
X
depend: ${SRC} ${HDR} ${GEN} FRC
X	maketd ${CDEFS} ${INCLUDE} ${DEP}
X
dirs: ${BIN}
X
install: all dirs FRC
X	install -cs ${PROG} ${BIN}/${PROG}
X
lint: ${SRC} ${HDR} ${GEN} FRC
X	lint -h ${CDEFS} ${INCLUDE} ${DEP}
X
mkcat: ${MAN}
X	mkcat -r${MANROOT} ${MAN}
X
print: source FRC
X	lpr -J'${PROG} source' ${SOURCE}
X
source: ${SOURCE}
X
spotless: clean
X	rcsclean ${SOURCE}
X
tags: ${HDR} ${SRC} ${GEN}
X	ctags -t ${HDR} ${SRC} ${GEN}
X
/ ${BIN}:
X	install -dr $@
X
${SOURCE}:
X	co -q $@
X
FRC:
X
# DO NOT DELETE THIS LINE - maketd DEPENDS ON IT
X
main.o: $L/getopt.h configure.h install.h main.c purge.h
X
purge.o: configure.h filedup.h install.h main.h purge.c purge.h
X
filedup.o: filedup.c filedup.h
X
# *** Do not add anything here - It will go away. ***
Purdue
chmod 0644 purge/Makefile ||
echo 'restore of purge/Makefile failed'
Wc_c="`wc -c < 'purge/Makefile'`"
test 2355 -eq "$Wc_c" ||
	echo 'purge/Makefile: original size 2355, current size' "$Wc_c"
fi
# ============= install.d/TODO ==============
if test -f 'install.d/TODO' -a X"$1" != X"-c"; then
	echo 'x - skipping install.d/TODO (File already exists)'
else
echo 'x - extracting install.d/TODO (Text)'
sed 's/^X//' << 'Purdue' > 'install.d/TODO' &&
# $Id: TODO,v 7.0 90/09/17 09:41:37 ksb Exp $
X
Last thing install should do:
X
in the configuration file one should be able to note that a file
should be owned (grouped/moded) to the same owner (group/mode) as
another file in the file system:
X
/bin/df		-rwxr-s?-x	root	:/dev/rf0a	s	reads disk
X
let the colon indicate that it is a file to stat to get the
group from....
X
kayessbee
Purdue
chmod 0444 install.d/TODO ||
echo 'restore of install.d/TODO failed'
Wc_c="`wc -c < 'install.d/TODO'`"
test 379 -eq "$Wc_c" ||
	echo 'install.d/TODO: original size 379, current size' "$Wc_c"
fi
# ============= install.d/myself.cf ==============
if test -f 'install.d/myself.cf' -a X"$1" != X"-c"; then
	echo 'x - skipping install.d/myself.cf (File already exists)'
else
echo 'x - extracting install.d/myself.cf (Text)'
sed 's/^X//' << 'Purdue' > 'install.d/myself.cf' &&
# this configuration file is used *just* to install install, itself	(ksb)
# $Id: myself.cf,v 7.0 90/10/08 11:39:55 ksb Exp $
X
/usr/bin/install         -rwxr-xr-x      root    *	?
OLD                      drwxr-xr-x      root    *	-
Purdue
chmod 0444 install.d/myself.cf ||
echo 'restore of install.d/myself.cf failed'
Wc_c="`wc -c < 'install.d/myself.cf'`"
test 232 -eq "$Wc_c" ||
	echo 'install.d/myself.cf: original size 232, current size' "$Wc_c"
fi
true || echo 'restore of install.d/special.c failed'
echo End of part 4, continue with part 5
exit 0

exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.