Port-amd64 archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
improved etcupdate (was: update 8.0_RC1->8.0)
EF> If you need to do this on a set of machines and get tired of answering
EF> the same questions over and over again, I have a patch to add
EF> record/replay support to etcupdate.
SB> Sounds useful. Yes, I have eight machines running NetBSD, and
SB> updating to a new release can be a real chore.
I'll attach my version for NetBSD 8. Please note that I didn't actually run
this version myself, only a former one I originally wrote for NetBSD 6.
The attached version also includes an updated fix for install/50838 (the whole
NEED_XXX logic looks broken to me but the PR is untouched for five years)
plus maybe some DESTDIR fixes (I implemented PREFIX/DESTDIR before NetBSD did
and NetBSD and me overlooked different places where you need to add it).
It also adds a way to bootstrap into automatic mode and a switch to
automatically add missing files/directories.
The idea is that you run -o file on one machine, answer the questions
manually, transfer file to the other machines and run -i file there.
If you have to merge, the logic only records the fact, not the details of
the merge -- you need to re-enter them.
.\" $NetBSD: etcupdate.8,v $
.\"
.\" Copyright (c) 2001-2008 The NetBSD Foundation, Inc.
.\" All rights reserved.
.\"
.\" This code is derived from software contributed to The NetBSD Foundation
.\" by Martti Kuparinen.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd June 12, 2014
.Dt ETCUPDATE 8
.Os
.Sh NAME
.Nm etcupdate
.Nd update the configuration and startup files in
.Pa /etc
.Sh SYNOPSIS
.Nm
.Op Fl abhlmv
.Op Fl d Ar destdir
.Op Fl p Ar pager
.Op Fl s Brq Ar srcdir | Ar tgzdir | Ar tgzfile
.Op Fl t Ar temproot
.Op Fl w Ar width
.Op Fl o Ar file
.Op Fl i Ar file
.Op Fl r Ar dir
.Sh DESCRIPTION
.Nm
is a tool that lets the administrator update the configuration and
startup files in
.Pa /etc
(and some other directories like
.Pa /dev ,
.Pa /root
and
.Pa /var )
without having to manually check and modify every file.
The administrator should run this script after performing an operating
system update (e.g. after running
.Pa make build
in
.Pa /usr/src
or after extracting new binary distribution files)
to update to the latest configuration and startup files.
.Pp
.Nm
compares the new configuration files against the currently installed files.
The user is given the option of installing, merging or deleting each
modified or missing file.
The user can also view the differences between the files.
By default, it shows the differences in the unified diff format.
The default format can be toggled to show the differences
in unified, context, or side by side formats or an user-defined
command may be used to view differences.
(And if
.Nm wdiff
is installed, it can also show differences on a word by word basis.)
.Pp
.Nm
also detects if the user installs certain special files and performs
corresponding tasks like remaking device nodes or rebuilding a database
from the
.Xr aliases 5
file.
Finally,
.Nm
runs
.Xr postinstall 8
to check the results.
.Pp
.Nm
needs a clean set of new configuration files to compare the
existing files against.
These files, called the
.Dq reference files
in this manual, may be derived from either a source or binary
distribution of
.Nx .
.Pp
If the user is updating from sources (which is the default mode),
.Nm
will first create a copy of the reference files
by running
.Pa make distribution
in
.Pa /usr/src/etc ,
installing the files to a so-called
.Pa temproot .
(See usage of the
.Fl s Ar srcdir
and
.Fl t Ar temproot
options later in this manual page.)
Although this is the default mode, it is not recommended
(see the
.Dq BUGS
section).
.Pp
Instead of using sources, it is recommended that the user should extract
one or more binary distribution sets in a special location and use those
as the reference files (see usage of the
.Fl s Ar tgzdir
option later in this manual page),
or specify one or more binary distribution sets directly
(see usage of the
.Fl s Ar tgzfile
option later in this manual page).
.Pp
The following options are available:
.Bl -tag -width XXtXtemprootX
.It Fl a
.Nm
can automatically update files which have not been modified locally.
The
.Fl a
flag instructs
.Nm
to store MD5 checksums in
.Pa /var/etcupdate
and use these checksums to determine if there have been any
local modifications.
.It Fl b
``bootstrap'' automatic
.Pf ( Fl a )
mode by initializing the checksums in
.Pa /var/etcupdate
from the reference files instead of updating anything.
Use this if you never ran in
.Fl a
mode before or lost your
.Pa /var/etcupdate
directory.
Note that unless you just ran a normal
.Nm
before, in this mode, the
.Fl s
arguments must refer to the ``old'' configuration files, i.e.\&
those the currently installed ones are based on.
.It Fl m
Automatically create missing directories
and install missing files.
.It Fl d Ar destdir
Use
.Ar destdir
instead of
.Pa /
as the top of the file system hierarchy to be updated.
For example,
.Ar destdir Ns Pa /etc
will be used instead of
.Pa /etc .
.It Fl h
Prints a help text.
.It Fl i Ar file
Read (input) the per-file decisions to be taken drom
.Pa file .
The contents of
.Pa file
is typically the output from a
.Nm Fl o Ar file
run on another machine, but may also be crafted manually.
The format is one line per file, starting with a single character
#/d/i/m for comment, keep (``Don't install''), overwrite (``Install'') or merge,
whitspace, file name and optionally a per-file comment starting with whitespace.
Note that in case of a merge,
.Pa file
only gives the information that a file has to be merged,
the merge itself has still be to performed interactively.
.It Fl l
Automatically skip files with unchanged RCS IDs.
This has the effect of leaving alone files that have been altered
locally but which have not been changed in the
reference files.
Since this works using RCS IDs, files without RCS IDs will not be
skipped even if only modified locally.
This flag may be used together with the
.Fl a
flag described above.
.It Fl o Ar file
Write (output) the per-file decisions taken to
.Pa file
in a format suitable to be read by the
.Fl i
option.
This can be used to record the decisions taken on a fully interactive run of
.Nm
and re-use that information on a subsequent invocation of
.Nm
on another machine.
Note that in case of a merge,
.Pa file
only records the fact that a merge was performed, not the details of the merge
operation.
.It Fl p Ar pager
The pager to use when displaying files.
By default this is
.Xr more 1
but it can be changed either with this option
or by defining the
.Ev PAGER
variable.
.It Fl r Ar dir
Install all files and directories under the
.Pa dir
directory. This is mainly useful for testing purposes.
Note that the
.Xr newaliases 1
command can't be run in this mode because it can't be given the name of the
configuration directory.
.It Fl s Brq Ar srcdir | Ar tgzdir | Ar tgzfile
The location of the reference files, or the
.Nx
source files used to create the reference files.
This may be specified in one of three ways:
.Bl -tag -width XXsXtgzfileXX
.It Fl s Ar srcdir
The top level directory of the
.Nx
source tree.
By default this is
.Pa /usr/src
but it can be changed either with this option
or the
.Ev SRCDIR
variable.
The reference files will be created by running
.Dq "make distribution"
in the
.Ar srcdir Ns Pa /etc
directory.
Note that
.Ar srcdir
should refer to the top of the source directory tree;
earlier versions of
.Nm
expected
.Ar srcdir
to refer to the
.Pa etc
subdirectory within the source tree.
.It Fl s Ar tgzdir
A directory in which reference files have been
extracted from a binary distribution of
.Nx .
The files that are distributed in the
.Dq Pa etc.tgz
set file must be present.
The files that are distributed in the
.Dq Pa xetc.tgz
set file are optional.
The reference files from the specified directory will be copied to the
.Pa temproot
directory.
.It Fl s Ar tgzfile
The location of a set file
(or
.Dq "tgz file" )
such as
.Dq Pa etc.tgz
or
.Dq Pa xetc.tgz
from a binary distribution of
.Nx .
Each set file is a compressed archive containing reference files,
which will be extracted to the
.Pa temproot
directory.
Multiple
.Fl s
options may be used to specify multiple set files.
The
.Dq Pa etc.tgz
set file must be specified.
The
.Dq Pa xetc.tgz
set file is optional.
.El
.It Fl t Ar temproot
Specifies the location of the
.Pa temproot
directory.
This directory will be used for a temporary copy of
the reference files created by running
.Dq "make distribution"
in the source directory specified by
.Fl s Ar srcdir ,
or a temporary copy of the reference files extracted from
the binary sets specified by
.Fl s Ar tgzfile ,
or a temporary copy of the reference files from the directory specified by
.Fl s Ar tempdir .
By default this is
.Pa /tmp/temproot
but can be changed either with this option or the
.Ev TEMPROOT
environment variable.
.It Fl v
Makes
.Nm
verbose about its actions.
.It Fl w Ar width
Sets screen width used during interactive merge.
By default this is the number of columns
.Xr stty 1
reports but it can be changed either with this
option or by defining the
.Ev WIDTH
variable.
This is useful for
.Xr xterm 1
users with wider shell windows.
.El
.Sh ENVIRONMENT
.Bl -tag -width IGNOREFILESXX
.It Ev TEMPROOT
Sets a default value for
.Pa temproot .
See
.Fl t
above.
.It Ev SRCDIR
The location of the
.Nx
sources files.
See
.Fl s
above.
.It Ev PAGER
The pager to use when displaying files.
See
.Fl p
above.
.It Ev WIDTH
The screen width used during interactive merge.
See
.Fl w
above.
.It Ev IGNOREFILES
A list of files that
.Nm
should ignore.
Files listed in this
variable will never be considered for updating by
.Nm .
.El
.Sh FILES
The environment variables can also be defined in the following configuration
files.
The user's personal configuration file settings override the global
settings.
.Pp
/etc/etcupdate.conf
.Pp
~~/.etcupdaterc
.Sh EXAMPLES
You have just upgraded your
.Nx
host from 3.0 to 4.0 and now it's time
to update the configuration files as well.
To update the configuration files from the sources (if you have the
.Pa /usr/src/etc
directory):
.Pp
.Dl etcupdate
.Pp
The default location of the source files is
.Pa /usr/src
but this may be overridden with the
.Fl s Ar srcdir
command line argument:
.Pp
.Dl etcupdate -s /some/where/src
.Pp
To update the configuration files from binary distribution sets
do something like this:
.Pp
.Dl etcupdate -s /some/where/etc.tgz -s /some/where/xetc.tgz
.Pp
or like this:
.Pp
.Dl mkdir /tmp/temproot
.Dl cd /tmp/temproot
.Dl tar -xpzf /some/where/etc.tgz
.Dl tar -xpzf /some/where/xetc.tgz
.Dl etcupdate -s /tmp/temproot
.Pp
You have modified only few files in the
.Pa /etc
directory so you would like install most of the updates without being asked.
To automatically update the unmodified configuration files:
.Pp
.Dl etcupdate -a
.Pp
To get a better idea what's going on, use the
.Fl v
flag:
.Pp
.Dl etcupdate -v
.Sh SEE ALSO
.Xr cmp 1 ,
.Xr more 1 ,
.Xr rcs 1 ,
.Xr sdiff 1 ,
.Xr stty 1 ,
.Xr aliases 5 ,
.Xr postinstall 8
.Sh HISTORY
The
.Nm
command appeared in
.Nx 1.6 .
.Pp
In
.Nx 4.0 ,
the
.Fl s Ar tgzfile
option was added, the
.Fl b Ar tempdir
option was converted to
.Fl s Ar tgzdir ,
and the
.Fl s Ar srcdir
option was changed to refer to the top of the
source directory tree rather than to the
.Pa etc
subdirectory.
.Pp
In
.Nx 5.0 ,
the ability to specify multiple colon-separated files with a single
.Fl s
option was deprecated,
and options deprecated in
.Nx 4.0
were removed.
.Pp
In
.Nx 7.0 ,
the ability to specify multiple colon-separated files with a single
.Fl s
option was removed (multiple
.Fl s
options must be used instead),
and the
.Fl d Ar destdir
option was added.
.Sh AUTHORS
The script was written by
.An Martti Kuparinen
.Aq martti%NetBSD.org@localhost
and improved by several other
.Nx
users.
.Pp
The idea for this script (including code fragments, variable names etc.)
came from the
.Fx
mergemaster (by Douglas Barton).
Unlike the
.Fx
mergemaster, this does not use CVS version tags by default to compare if
the files need to be updated.
Files are compared with
.Xr cmp 1
as this is more reliable and the only way if the version numbers are the
same even though the files are different.
.\" when exactly are the version the same even though the file changes?
.\" .Pp
.Sh BUGS
If a source directory is specified via the
.Dq Fl s Ar srcdir
option (or if the
.Pa /usr/src
directory is used by default), then
.Nm
will run
.Dq "make distribution"
in the
.Pa etc
subdirectory of the source directory, but it will not use the same
options or environment variables that would be used during a full build
of the operating system.
For this reason, use of the
.Dq Fl s Ar srcdir
option is not recommended, and use of the
.Dq Fl s Ar tgzdir
or
.Dq Fl s Ar tgzfile
options is recommended.
.\" .Pp
.\" Because of the use of
.\" .Xr cmp 1
.\" to compare files, rather than CVS versions, files that are locally changed
.\" from the distribution are always considered needing to be updated.
#!/bin/sh
#
# $NetBSD: etcupdate,v $
#
# Copyright (c) 2001-2008 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Martti Kuparinen.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# This script helps you to update the configuration files in /etc
# after an operating system upgrade. Instead of running "make distribution"
# in /usr/src/etc (and losing your current configuration) you can easily
# see the modifications and either install the new version or merge the
# changes in to your current configuration files.
#
# This script was written by Martti Kuparinen <martti%NetBSD.org@localhost> and
# improved by several other NetBSD users.
#
# The idea for this script (including code fragments, variable names etc.)
# came from the FreeBSD mergemaster (by Douglas Barton).
#
PATH="/sbin:/usr/sbin:/bin:/usr/bin:${PATH}"
# Default settings
PROG="${0##*/}"
DESTDIR="${DESTDIR:=}" # must not have a trailing slash
DESTDIR_BRE="" # basic regex to match ${DESTDIR}
TEMPROOT="${TEMPROOT:=/tmp/temproot}"
PAGER="${PAGER:=/usr/bin/more}"
SWIDTH="$(stty size | awk '{w=$2}END{if(w==0){w=80}print w}')"
WIDTH="${WIDTH:="${SWIDTH}"}"
DIFF_COMMAND="diff -u"
VERBOSE=false
CONTINUE=false
SOURCEMODE=false # true for "-s source_dir"
SRCDIR= # directory for SOURCEMODE
BINARYMODE=false # true for both BINARYDIRMODE and BINARYTGZMODE
BINARYDIRMODE=false # true for "-s extracted_dir"
BINARYDIR= # directory name for BINARYDIRMODE
BINARYTGZMODE=false # true for "-s etc.tgz"
TGZLIST= # quoted list list of files for BINARYTGZMODE
SRC_ARGLIST= # quoted list of "-s" args
N_SRC_ARGS=0 # number of "-s" args
AUTOMATIC=false
AUTO_MISSING=false
BOOT_AUTOMATIC=false
LOCALSKIP=false
MACHINE="${MACHINE:="$(uname -m)"}"
export MACHINE
MACHINE_ARCH="${MACHINE_ARCH:="$(uname -p)"}"
export MACHINE_ARCH
OUTFILE= # file name argument to -o
INFILE= # file name argument to -i
KEEP_FILES=" " # files to keep (Don't install)
OVERWRITE_FILES=" " # files to overwrite (Install)
MERGE_FILES=" " # files to merge
usage() {
cat << EOF >&2
Usage: ${PROG} [options]
Options:
-p pager Which pager to use (default: /usr/bin/more)
-s {srcdir|tgzfile|tempdir} (default: /usr/src)
Location of the source files. This may be any of the
following:
* A directory that contains a NetBSD source tree;
* A distribution set file such as "etc.tgz" or
"xetc.tgz". Pass multiple -s options to specify
multiple such files;
* A temporary directory in which one or both of
"etc.tgz" and "xetc.tgz" have been extracted.
-t temproot Where to store temporary files (default: /tmp/temproot)
-d destdir Destination prefix to check. (default: empty)
-w width Screen width (default: 80)
-a Automatically update unmodified files
-m Automatically create/install missing directories/files
-b Bootstrap automatic mode: Initialize reference checksums
from -s arguments
-l Automatically skip files with strictly local changes
(this option has no effect on files lacking RCS Ids)
-o file Write (output) per-file decisions taken to file
-i file Read (input) per-file decisions to be taken from file
-h This help text
-v Be more verbose
EOF
exit 1
}
verbose() {
# $* = message to display if in verbose mode
${VERBOSE} && echo "${@}"
}
yesno() {
# $* = message to display
echo -n "${@}? (y/[n]) "
read ANSWER
case "${ANSWER}" in
y|Y)
return 0
;;
*)
return 1
;;
esac
}
# Quote args to make them safe in the shell.
# Usage: quotedlist="$(shell_quote args...)"
#
# After building up a quoted list, use it by evaling it inside
# double quotes, like this:
# eval "set -- $quotedlist"
# or like this:
# eval "\$command $quotedlist \$filename"
#
shell_quote()
{(
local result=''
local arg qarg
LC_COLLATE=C ; export LC_COLLATE # so [a-zA-Z0-9] works in ASCII
for arg in "$@" ; do
case "${arg}" in
'')
qarg="''"
;;
*[!-./a-zA-Z0-9]*)
# Convert each embedded ' to '\'',
# then insert ' at the beginning of the first line,
# and append ' at the end of the last line.
# Finally, elide unnecessary '' pairs at the
# beginning and end of the result and as part of
# '\'''\'' sequences that result from multiple
# adjacent quotes in he input.
qarg="$(printf "%s\n" "$arg" | \
${SED:-sed} -e "s/'/'\\\\''/g" \
-e "1s/^/'/" -e "\$s/\$/'/" \
-e "1s/^''//" -e "\$s/''\$//" \
-e "s/'''/'/g"
)"
;;
*)
# Arg is not the empty string, and does not contain
# any unsafe characters. Leave it unchanged for
# readability.
qarg="${arg}"
;;
esac
result="${result}${result:+ }${qarg}"
done
printf "%s\n" "$result"
)}
# Convert arg $1 to a basic regular expression (as in sed)
# that will match the arg. This works by inserting backslashes
# before characters that are special in basic regular expressions.
# It also inserts backslashes before the extra characters specified
# in $2 (which defaults to "/,").
# XXX: Does not handle embedded newlines.
# Usage: regex="$(bre_quote "${string}")"
bre_quote()
{
local arg="$1"
local extra="${2-/,}"
printf "%s\n" "${arg}" | sed -e 's/[][^$.*\\'"${extra}"']/\\&/g'
}
install_dir() {
# $1 = target directory (relative to ${DESTDIR})
touch "${TEMPROOT}"/.etcupdate.NEED_ANYTHING
if ${AUTO_MISSING} || yesno "Create ${DESTDIR}${1}"; then
verbose "Creating ${DESTDIR}${1}"
mkdir -p "${DESTDIR}${1}" || exit 1
touch "${TEMPROOT}"/.etcupdate.NEED_MTREE
fi
}
install_file() {
# $1 = target file (relative to ${DESTDIR})
touch "${TEMPROOT}"/.etcupdate.NEED_ANYTHING
# Install the new file
verbose "Installing ${DESTDIR}${1}"
cp -p "${TEMPROOT}${1}" "${DESTDIR}${1}" && rm -f "${TEMPROOT}${1}"
# Check if this was a special file
case "${1}" in
/dev/MAKEDEV)
touch "${TEMPROOT}"/.etcupdate.NEED_MAKEDEV
;;
/dev/MAKEDEV.local)
touch "${TEMPROOT}"/.etcupdate.NEED_MAKEDEV
;;
/etc/mail/aliases)
touch "${TEMPROOT}"/.etcupdate.NEED_NEWALIASES
;;
/etc/master.passwd)
touch "${TEMPROOT}"/.etcupdate.NEED_PWD_MKDB
;;
/etc/services)
touch "${TEMPROOT}"/.etcupdate.NEED_SERVICES_MKDB
;;
esac
}
install_checksum() {
# $1 = target file (relative to ${DESTDIR})
${AUTOMATIC} || return
touch "${TEMPROOT}"/.etcupdate.NEED_ANYTHING
D="$(dirname "${1}")"
mkdir -p "${DESTDIR}/var/etcupdate/${D}"
verbose "Saving MD5 checksum for ${DESTDIR}${1} to" \
"${DESTDIR}/var/etcupdate/${1}"
# The sed part of the following pipeline changes things like
# "MD5 (/path/to/dest/dir/etc/filename) = abc123" to
# "MD5 (/etc/filename) = abc123".
md5 "${DESTDIR}${1}" | sed -e "s,(${DESTDIR_BRE},(," \
> "${DESTDIR}/var/etcupdate/${1}"
}
# Initialise the DIFF_EXTRA_OPTIONS variable.
init_diff_extra_options() {
#
# Start with a few options that are always available.
#
DIFF_EXTRA_OPTIONS=\
" su Show differences in unified format (\"diff -u\")
sc Show differences in context format (\"diff -c\")
ss Show differences side by side (\"sdiff -w${WIDTH}\")"
#
# wdiff is not part of the base system, but the
# user might have installed it from pkgsrc. It is
# useful to show differences on a word by word basis
# instead of line by line. If it is executable
# then offer to use it in the menu.
#
if (wdiff /dev/null /dev/null) >/dev/null 2>&1 ; then
DIFF_EXTRA_OPTIONS="${DIFF_EXTRA_OPTIONS}
sw Show differences word by word (\"wdiff -n -l\")"
fi
#
# End with an option to use a user-specified diff-like command.
#
DIFF_EXTRA_OPTIONS="${DIFF_EXTRA_OPTIONS}
scommand Show differences using the specified diff-like command"
}
record_action() {
# $1 = action, $2 = target file
test -n "${OUTFILE}" || return
printf "%c %s\n" "$1" "$2" >>${OUTFILE}
}
diff_and_merge_file() {
# $1 = target file (relative to ${DESTDIR})
if cmp -s "${TEMPROOT}${1}" "${DESTDIR}${1}"; then
verbose "===> ${1} (ok)"
rm -f "${TEMPROOT}${1}"
install_checksum "${1}"
return
fi
if ${AUTOMATIC} && [ -f "${DESTDIR}/var/etcupdate/${1}" ]; then
SUM1="$(md5 "${DESTDIR}${1}")"
SUM2="$(cat "${DESTDIR}/var/etcupdate/${1}")"
if [ "${SUM1}" = "${SUM2}" ]; then
install_file "${1}"
install_checksum "${1}"
return
fi
fi
if ${AUTO_MISSING} && [ ! -e "${DESTDIR}${1}" ]; then
install_file "${1}"
install_checksum "${1}"
return
fi
if ${LOCALSKIP}; then
ID1="$(ident -q "${TEMPROOT}${1}" | sed -n 2p)"
ID1="${ID1:-0}"
ID2="$(ident -q "${DESTDIR}${1}" | sed -n 2p)"
ID2="${ID2:-1}"
if [ "${ID1}" = "${ID2}" ]; then
verbose "===> ${1} (ok:RCS)"
rm -f "${TEMPROOT}${1}"
return
fi
fi
AUTOANSWER=""
case ${KEEP_FILES} in
*\ ${1}\ *)
verbose "===> ${DESTDIR}${1} (KEEP)"
AUTOANSWER="d"
;;
esac
case ${OVERWRITE_FILES} in
*\ ${1}\ *)
verbose "===> ${DESTDIR}${1} (OVERWRITE)"
AUTOANSWER="i"
;;
esac
case ${MERGE_FILES} in
*\ ${1}\ *)
verbose "===> ${DESTDIR}${1} (MERGE)"
AUTOANSWER="m"
;;
esac
if [ -z "${AUTOANSWER}" ]; then
clear
if [ ! -f "${DESTDIR}${1}" ]; then
verbose "===> ${DESTDIR}${1} (missing)"
DOES_EXIST=false
else
verbose "===> ${DESTDIR}${1} (modified)"
verbose ""
DOES_EXIST=true
diff -u "${DESTDIR}${1}" "${TEMPROOT}${1}" | ${PAGER}
fi
fi
STAY_HERE=true
ALREADY_MERGED=false
# Determine name for the backup file (/foo/._etcupdate.bar)
D="$(dirname "${TEMPROOT}${1}")"
F="$(basename "${TEMPROOT}${1}")"
B="${D}/.etcupdate.${F}"
F="${D}/${F}"
while ${STAY_HERE}; do
if [ -n "${AUTOANSWER}" ]; then
ANSWER="${AUTOANSWER}"
AUTOANSWER=""
else
# Ask the user if (s)he wants to install the new
# version or perform a more complicated manual work.
echo ""
echo -n "File: ${DESTDIR}${1}"
if [ ! -f "${DESTDIR}${1}" ]; then
echo -n " (missing)"
else
echo -n " (modified)"
fi
echo ""
echo ""
echo "Please select one of the following operations:"
echo ""
if ! ${DOES_EXIST}; then
cat << EOF
d Don't install the missing file
i Install the missing file
v Show the missing file
EOF
elif ! ${ALREADY_MERGED}; then
cat << EOF
d Don't install the new file (keep your old file)
i Install the new file (overwrites your local modifications!)
m Merge the currently installed and new files
s Show the differences between the currently installed and new files
${DIFF_EXTRA_OPTIONS}
v Show the new file
EOF
else
cat << EOF
d Don't install the merged file (keep your old file)
i Install the merged file (overwrites your old file)
m Merge again (your old file against the result from the previous merge)
s Show the differences between the currently installed and new merged files
${DIFF_EXTRA_OPTIONS}
u Undo merge (start again with the original version of the new file)
v Show the merged file
EOF
fi
echo -n "What do you want to do? [Leave it for later] "
read ANSWER
fi
case "${ANSWER}" in
[dD])
verbose "Removing ${TEMPROOT}${1}"
rm -f "${TEMPROOT}${1}"
record_action "d" "${1}"
STAY_HERE=false
;;
[iI])
install_file "${1}"
if ! ${ALREADY_MERGED}; then
record_action "i" "${1}"
install_checksum "${1}"
else
record_action "m" "${1}"
fi
STAY_HERE=false
;;
[mM])
${DOES_EXIST} || continue
[ ! -f "${B}" ] && cp "${F}" "${B}"
cp "${TEMPROOT}${1}" "${TEMPROOT}${1}.merged"
sdiff -o "${TEMPROOT}${1}.merged" \
--width=${WIDTH} \
--suppress-common-lines --text \
"${DESTDIR}${1}" "${TEMPROOT}${1}"
mv -f "${TEMPROOT}${1}.merged" "${TEMPROOT}${1}"
ALREADY_MERGED=true
;;
[sS]*)
${DOES_EXIST} || continue
case "${ANSWER}" in
[sS]) : no change ;;
[sS]u) DIFF_COMMAND="diff -u" ;;
[sS]c) DIFF_COMMAND="diff -c" ;;
[sS]s) DIFF_COMMAND="sdiff -w${WIDTH}" ;;
[sS]w) DIFF_COMMAND="wdiff -n -l" ;;
[sS]*) DIFF_COMMAND="${ANSWER#?}" ;;
esac
${DIFF_COMMAND} "${DESTDIR}${1}" "${TEMPROOT}${1}" \
| ${PAGER}
;;
[uU])
if [ -f "${B}" ]; then
echo "*** Restoring ${F}"
mv -f "${B}" "${F}"
fi
ALREADY_MERGED=false
;;
[vV])
${PAGER} "${TEMPROOT}${1}"
;;
"")
STAY_HERE=false
;;
*)
echo "*** Invalid selection!"
;;
esac
done
rm -f "$B"
}
# Set the environment for make.
set_makeenv() {
#
# INSTALL_DONE=1 prevents installation of unwanted
# files (things that are not part of the etc set).
# BUILD=1 allows building of files that are wanted.
#
MAKE_ENV=" \
NETBSDSRCDIR=$(shell_quote "${SRCDIR}") \
DESTDIR=$(shell_quote "${TEMPROOT}") \
MAKE=make \
MTREE=mtree \
TOOL_MTREE=mtree \
INSTALL_DONE=1 \
BUILD=1 \
USETOOLS=never"
}
#
# main()
#
# Read global configuration
GLOBALRC="/etc/${PROG}.conf"
[ -r ${GLOBALRC} ] && . ${GLOBALRC}
# Read user configuration
USERRC="${HOME}/.{PROG}rc"
[ -r ${USERRC} ] && . ${USERRC}
# Read command line arguments
while getopts abd:hi:lmo:p:s:t:vw: i
do
case "${i}" in
a)
AUTOMATIC=true
;;
b)
BOOT_AUTOMATIC=true
;;
d)
DESTDIR="${OPTARG}"
;;
h)
usage
;;
i)
INFILE="${OPTARG}"
;;
l)
LOCALSKIP=true
;;
m)
AUTO_MISSING=true
;;
o)
OUTFILE="${OPTARG}"
;;
p)
PAGER="${OPTARG}"
;;
s)
# Three cases:
# -s tgzfile (may be repeated)
# -s srcdir (may not be repeated)
# -s extracted_dir (may not be repeated)
arg="${OPTARG}"
qarg="$(shell_quote "${OPTARG}")"
N_SRC_ARGS=$(( N_SRC_ARGS + 1 ))
SRC_ARGLIST="${SRC_ARGLIST}${SRC_ARGLIST:+ }-s ${qarg}"
if [ -f "${arg}" ]; then
# arg refers to a *.tgz file.
# This may happen twice, for both etc.tgz and
# xetc.tgz, so we build up a list in TGZLIST.
BINARYMODE=true
BINARYTGZMODE=true
TGZLIST="${TGZLIST}${TGZLIST:+ }${qarg}"
elif [ -d "${arg}" ] && [ -f "${arg}/etc/Makefile" ]; then
# arg refers to a source directory
SOURCEMODE=true
SRCDIR="${arg}"
elif [ -d "${arg}" ] && [ -d "${arg}/etc" ] \
&& ! [ -f "${arg}/etc/Makefile" ]
then
# arg refers to a directory where the
# sets have already been extracted
BINARYMODE=true
BINARYDIRMODE=true
BINARYDIR="${arg}"
else
echo "*** Nonexistent or invalid file or directory" \
"for -s ${arg}"
usage
fi
;;
t)
TEMPROOT="${OPTARG}"
;;
v)
VERBOSE=true
;;
w)
WIDTH="${OPTARG}"
;;
*)
# getopts should already have printed an error message
usage
break
;;
esac
done
# Last minute sanity checks
if [ -z "${DESTDIR}" ] && [ "$(id -u)" -ne 0 ]; then
echo "*** ERROR: You MUST be root"
exit 1
fi
DESTDIR="${DESTDIR%/}" # remove trailing slash, if any. result might be "".
DESTDIR_BRE="$(bre_quote "${DESTDIR}")"
if [ "${N_SRC_ARGS}" -gt 1 ] && ( ${SOURCEMODE} || ${BINARYDIRMODE} ); then
echo "*** ERROR: Multiple -s args are allowed only with tgz files"
usage
fi
case "${TEMPROOT}" in
/*) : OK ;;
*) new="${PWD:-$(pwd)}/${TEMPROOT}"
echo "*** NOTE: Using TEMPROOT \"${new}\" instead of \"${TEMPROOT}\""
TEMPROOT="${new}"
;;
esac
if ${BINARYDIRMODE}; then
SRCDIR="${TEMPROOT}"
fi
if ${BINARYTGZMODE}; then
SRCDIR="${TEMPROOT}"
fi
if [ "${N_SRC_ARGS}" -eq 0 ]; then
# default if no "-s" option was specified
SOURCEMODE=true
SRCDIR="/usr/src"
SRC_ARGLIST="-s $(shell_quote "${SRCDIR}")"
fi
if [ -z "${SRCDIR}" -o -z "${TEMPROOT}" ]; then
echo "*** ERROR: One of the following variables is undefined"
echo ""
echo "SRCDIR=\"${SRCDIR}\""
echo "TEMPROOT=\"${TEMPROOT}\""
echo ""
exit 1
fi
if [ -r "${TEMPROOT}" ]; then
if ${BOOT_AUTOMATIC}; then
echo ""
echo "*** ERROR: ${TEMPROOT} already exists"
echo ""
exit 1
else
echo ""
echo "*** WARNING: ${TEMPROOT} already exists"
echo ""
if yesno "Continue previously aborted update"; then
CONTINUE=true
elif yesno "Remove the old ${TEMPROOT}"; then
echo "*** Removing ${TEMPROOT}"
rm -rf "${TEMPROOT}"
fi
fi
fi
if ! ${CONTINUE}; then
# Create the temporary root directory
echo "*** Creating ${TEMPROOT}"
mkdir -p "${TEMPROOT}"
if [ ! -d "${TEMPROOT}" ]; then
echo "*** ERROR: Unable to create ${TEMPROOT}"
exit 1
fi
# Are we using the sources or binaries?
if ${BINARYTGZMODE}; then
# Populate ${TEMPROOT} from ${TGZLIST}
eval "set -- ${TGZLIST}"
for tgz in "$@"; do
if [ ! -f "${tgz}" ]; then
echo "*** ERROR: Unable to find ${tgz}"
exit 1
fi
echo "*** Populating ${TEMPROOT} from ${tgz}"
tar -zxpf "${tgz}" -C "${TEMPROOT}"
[ $? -ne 0 ] && exit 1
done
elif ${BINARYDIRMODE}; then
# Populate ${TEMPROOT} from ${SRCDIR} by copying.
# Copy only the files that belong to the etc and xetc sets.
echo "*** Populating ${TEMPROOT} from ${BINARYDIR} (copying)"
for setname in etc xetc; do
mtreefile="${BINARYDIR}/etc/mtree/set.${setname}"
if ${VERBOSE}; then vflag="-v"; else vflag=""; fi
if [ -f "${mtreefile}" ]; then
echo "*** Copying files belonging to" \
"${setname} set"
(cd "${BINARYDIR}" \
&& pax -rwdM ${vflag} "${TEMPROOT%/}/."
) <"${mtreefile}"
[ $? -ne 0 ] && exit 1
else
echo "*** Not copying files belonging to" \
"${setname} set: ${mtreefile} not found"
fi
done
elif ${SOURCEMODE}; then
# Populate ${TEMPROOT} from ${SRCDIR} by running make
if [ ! -f "${SRCDIR}/etc/Makefile" ]; then
echo "*** ERROR: Unable to find ${SRCDIR}/etc/Makefile"
exit 1
fi
set_makeenv
echo "*** Populating ${TEMPROOT} from ${SRCDIR} (make distribution)"
cd ${SRCDIR}/etc
if ! ${VERBOSE}; then
eval "${MAKE_ENV} make distribution > /dev/null"
else
eval "${MAKE_ENV} make distribution"
fi
[ $? -ne 0 ] && exit 1
fi
if ! [ -f "${TEMPROOT}/etc/mtree/set.etc" ]; then
echo "*** ERROR: Files from the etc.tgz set are missing"
exit 1
fi
if [ ! -f "${TEMPROOT}/dev/MAKEDEV" ]; then
echo ""
echo "*** WARNING: ${TEMPROOT}/dev/MAKEDEV not found"
echo "Make sure you update /dev/MAKEDEV later and run"
echo "(cd /dev && ./MAKEDEV all) to rebuild the device nodes"
echo ""
fi
# Ignore the following files during comparision
rm -f "${TEMPROOT}"/etc/passwd
rm -f "${TEMPROOT}"/etc/pwd.db
rm -f "${TEMPROOT}"/etc/spwd.db
find "${TEMPROOT}" -type f -size 0 -exec rm {} \;
# Ignore files we're told to ignore
if [ ! -z "${IGNOREFILES}" ]; then
echo "*** Ignoring files: ${IGNOREFILES}"
for file in ${IGNOREFILES}; do
rm -f "${TEMPROOT}"${file}
done
fi
if ${BOOT_AUTOMATIC}; then
echo "*** Initializing checksums"
find "${TEMPROOT}" -type f -a ! -name \*.etcupdate.\* | \
while read i; do
F="${i#"${TEMPROOT}"}"
D="$(dirname "${F}")"
mkdir -p "${DESTDIR}/var/etcupdate/${D}"
verbose "Saving original MD5 checksum for ${F} to ${DESTDIR}/var/etcupdate/${F}"
# unfortunately, the files in /var/etcupdate contain the plain output of the md5 command, including the filename
SUM1="$(md5 "${i}")"
SUM1="${SUM1##*= }"
printf "MD5 (%s) = %s\n" "$F" "$SUM1" > "${DESTDIR}/var/etcupdate/${F}"
done
echo "*** Removing ${TEMPROOT}"
rm -rf "${TEMPROOT}"
echo "*** Done"
exit 0
fi
# Are there any new directories?
echo "*** Checking for new directories"
exec 3<&0
find "${TEMPROOT}" -type d | \
while read i; do
D="${i#"${TEMPROOT}"}"
[ "x${i}" = "x${TEMPROOT}" ] && continue
[ ! -d "${DESTDIR}${D}" ] && install_dir "${D}" <&3
done
fi
if [ -n "${OUTFILE}" ]; then
echo "# $0 $(date)" >${OUTFILE}
fi
if [ -n "${INFILE}" ]; then
while read cmd file comment; do
case ${cmd} in
\#) ;;
d) KEEP_FILES="${KEEP_FILES}${file} " ;;
i) OVERWRITE_FILES="${OVERWRITE_FILES}${file} " ;;
m) MERGE_FILES="${MERGE_FILES}${file} " ;;
*) echo "*** ERROR: unknown command $cmd in ${INFILE}"; exit 1 ;;
esac
done <${INFILE}
fi
# Start the comparision
echo "*** Checking for added/modified files"
init_diff_extra_options
exec 3<&0
find "${TEMPROOT}" -type f -a ! -name \*.etcupdate.\* | \
while read i; do
D="${i#"${TEMPROOT}"}"
diff_and_merge_file "${D}" <&3
done
# Do we have files which were not processed?
REMAINING="$(find "${TEMPROOT}" -type f -a ! -name \*.etcupdate.\*)"
if [ ! -z "${REMAINING}" ]; then
echo ""
echo "*** The following files need your attention:"
echo ""
echo "${REMAINING}" | sed -e 's/^/ /'
echo ""
elif test -f "${TEMPROOT}"/.etcupdate.NEED_ANYTHING; then
rm "${TEMPROOT}"/.etcupdate.NEED_ANYTHING
echo ""
echo "*** No changes were needed"
echo ""
fi
# Clean up after "make distribution"
if ${SOURCEMODE}; then
echo "*** Cleaning up in ${SRCDIR}/etc"
set_makeenv
cd ${SRCDIR}/etc
if ! ${VERBOSE}; then
eval "${MAKE_ENV} make clean > /dev/null"
else
eval "${MAKE_ENV} make clean"
fi
fi
# Do some post-installation tasks
if test -f "${TEMPROOT}"/.etcupdate.NEED_PWD_MKDB; then
rm "${TEMPROOT}"/.etcupdate.NEED_PWD_MKDB
pwd_mkdb_cmd="$(shell_quote \
pwd_mkdb ${DESTDIR:+-d "${DESTDIR}"} \
-p "${DESTDIR}/etc/master.passwd")"
if yesno "Do you want to rebuild the password databases from the" \
"new ${DESTDIR}/etc/master.passwd"
then
verbose "Running pwd_mkdb"
eval "${pwd_mkdb_cmd}"
else
echo ""
echo "*** You MUST rebuild the password databases to make" \
"the changes visible"
echo "*** This is done by running \"${pwd_mkdb_cmd}\" as root"
echo ""
fi
fi
if ! test -f "${TEMPROOT}"/.etcupdate.NEED_SERVICES_MKDB; then
if test -e "${DESTDIR}/var/db/services.db" \
-a ! -e "${DESTDIR}/var/db/services.cdb"; then
touch "${TEMPROOT}"/.etcupdate.NEED_SERVICES_MKDB
fi
fi
if test -f "${TEMPROOT}"/.etcupdate.NEED_SERVICES_MKDB; then
rm "${TEMPROOT}"/.etcupdate.NEED_SERVICES_MKDB
services_mkdb_cmd="$(shell_quote services_mkdb -V cdb \
-o "${DESTDIR}/var/db/services.cdb" \
"${DESTDIR}/etc/services")"
if yesno "Do you want to rebuild the services databases from the" \
"new ${DESTDIR}/etc/services"
then
verbose "Running services_mkdb"
eval "${services_mkdb_cmd}"
else
echo ""
echo "*** You SHOULD rebuild the services databases to make" \
"the changes visible"
echo "*** This is done by running \"${services_mkdb_cmd}\"" \
"as root"
echo ""
fi
fi
if test -f "${TEMPROOT}"/.etcupdate.NEED_MTREE; then
rm "${TEMPROOT}"/.etcupdate.NEED_MTREE
if yesno "You have created new directories. Run mtree to set" \
"permissions"
then
(cd "${DESTDIR:-/}" && \
mtree -Udef "${DESTDIR}/etc/mtree/NetBSD.dist")
fi
fi
if test -f "${TEMPROOT}"/.etcupdate.NEED_MAKEDEV; then
rm "${TEMPROOT}"/.etcupdate.NEED_MAKEDEV
makedev_cmd="($(shell_quote cd "${DESTDIR}/dev") && ./MAKEDEV all)"
if yesno "Do you want to rebuild the device nodes in ${DESTDIR}/dev"
then
verbose "Running MAKEDEV in /dev"
eval "${makedev_cmd}"
else
echo ""
echo "*** You SHOULD rebuild the device nodes in" \
"${DESTDIR}/dev"
echo "*** This is done by running \"${makedev_cmd}\" as root."
echo ""
fi
fi
if test -f "${TEMPROOT}"/.etcupdate.NEED_NEWALIASES; then
rm "${TEMPROOT}"/.etcupdate.NEED_NEWALIASES
newaliases_cmd="newaliases"
# XXX newaliases doesn't work with DESTDIR.
# We could check whether the system configuration is
# sufficiently standard, and then run postalias(1) with the
# right args to make it work, but changes to /etc/mail/aliases
# are so rare that it doesn't seem worth the effort of checking
# that the system's mail configuration is standard.
if [ -z "${DESTDIR}" ] && \
yesno "Do you want to rebuild the mail alias database"
then
verbose "Running newaliases"
eval "${newaliases_cmd}"
else
echo ""
echo "*** You MUST rebuild the mail alias database to make" \
"the changes visible"
echo "*** This is done by running \"${newaliases_cmd}\" as root"
if [ -n "${DESTDIR}" ]; then
postalias_cmd="$(shell_quote \
postalias "hash:${DESTDIR}/etc/mail/aliases")"
echo "*** but it won't work with DESTDIR=${DESTDIR}."
echo "*** If you use postfix(1) with the default" \
"configuration, then you can try"
echo "*** running \"${postalias_cmd}\" as root."
fi
echo ""
fi
fi
if yesno "Remove ${TEMPROOT}"; then
echo "*** Removing ${TEMPROOT}"
rm -rf "${TEMPROOT}"
else
echo "*** Keeping ${TEMPROOT}"
fi
if [ -x "${DESTDIR}/usr/sbin/postinstall" ]; then
postinstall_cmd="$(shell_quote "${DESTDIR}/usr/sbin/postinstall" \
${DESTDIR:+-d "${DESTDIR}"}) ${SRC_ARGLIST} check"
echo "*** Running ${DESTDIR}/usr/sbin/postinstall"
eval "${postinstall_cmd}"
fi
echo "*** All done"
Home |
Main Index |
Thread Index |
Old Index