config root man

Current Path : /usr/src/usr.sbin/portsnap/portsnap/

FreeBSD hs32.drive.ne.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #1: Wed Jan 14 12:18:08 JST 2015 root@hs32.drive.ne.jp:/sys/amd64/compile/hs32 amd64
Upload File :
Current File : //usr/src/usr.sbin/portsnap/portsnap/portsnap.sh

#!/bin/sh

#-
# Copyright 2004-2005 Colin Percival
# All rights reserved
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted providing 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 AUTHOR ``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 AUTHOR 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.

# $FreeBSD: release/9.1.0/usr.sbin/portsnap/portsnap/portsnap.sh 235310 2012-05-12 00:49:14Z eadler $

#### Usage function -- called from command-line handling code.

# Usage instructions.  Options not listed:
# --debug	-- don't filter output from utilities
# --no-stats	-- don't show progress statistics while fetching files
usage() {
	cat <<EOF
usage: `basename $0` [options] command ... [path]

Options:
  -d workdir   -- Store working files in workdir
                  (default: /var/db/portsnap/)
  -f conffile  -- Read configuration options from conffile
                  (default: /etc/portsnap.conf)
  -I           -- Update INDEX only. (update command only)
  -k KEY       -- Trust an RSA key with SHA256 hash of KEY
  -l descfile  -- Merge the specified local describes file into the INDEX.
  -p portsdir  -- Location of uncompressed ports tree
                  (default: /usr/ports/)
  -s server    -- Server from which to fetch updates.
                  (default: portsnap.FreeBSD.org)
  path         -- Extract only parts of the tree starting with the given
                  string.  (extract command only)
Commands:
  fetch        -- Fetch a compressed snapshot of the ports tree,
                  or update an existing snapshot.
  cron         -- Sleep rand(3600) seconds, and then fetch updates.
  extract      -- Extract snapshot of ports tree, replacing existing
                  files and directories.
  update       -- Update ports tree to match current snapshot, replacing
                  files and directories which have changed.
EOF
	exit 0
}

#### Parameter handling functions.

# Initialize parameters to null, just in case they're
# set in the environment.
init_params() {
	KEYPRINT=""
	EXTRACTPATH=""
	WORKDIR=""
	PORTSDIR=""
	CONFFILE=""
	COMMAND=""
	COMMANDS=""
	QUIETREDIR=""
	QUIETFLAG=""
	STATSREDIR=""
	XARGST=""
	NDEBUG=""
	DDSTATS=""
	INDEXONLY=""
	SERVERNAME=""
	REFUSE=""
	LOCALDESC=""
}

# Parse the command line
parse_cmdline() {
	while [ $# -gt 0 ]; do
		case "$1" in
		-d)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${WORKDIR}" ]; then usage; fi
			shift; WORKDIR="$1"
			;;
		--debug)
			QUIETREDIR="/dev/stderr"
			STATSREDIR="/dev/stderr"
			QUIETFLAG=" "
			NDEBUG=" "
			XARGST="-t"
			DDSTATS=".."
			;;
		-f)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${CONFFILE}" ]; then usage; fi
			shift; CONFFILE="$1"
			;;
		-h | --help | help)
			usage
			;;
		-I)
			INDEXONLY="YES"
			;;
		-k)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${KEYPRINT}" ]; then usage; fi
			shift; KEYPRINT="$1"
			;;
		-l)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${LOCALDESC}" ]; then usage; fi
			shift; LOCALDESC="$1"
			;;
		--no-stats)
			if [ -z "${STATSREDIR}" ]; then
				STATSREDIR="/dev/null"
				DDSTATS=".. "
			fi
			;;
		-p)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${PORTSDIR}" ]; then usage; fi
			shift; PORTSDIR="$1"
			;;
		-s)
			if [ $# -eq 1 ]; then usage; fi
			if [ ! -z "${SERVERNAME}" ]; then usage; fi
			shift; SERVERNAME="$1"
			;;
		cron | extract | fetch | update | alfred)
			COMMANDS="${COMMANDS} $1"
			;;
		up)
			COMMANDS="${COMMANDS} update"
			;;
		*)
			if [ $# -gt 1 ]; then usage; fi
			if echo ${COMMANDS} | grep -vq extract; then
				usage
			fi
			EXTRACTPATH="$1"
			;;
		esac
		shift
	done

	if [ -z "${COMMANDS}" ]; then
		usage
	fi
}

# If CONFFILE was specified at the command-line, make
# sure that it exists and is readable.
sanity_conffile() {
	if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
		echo -n "File does not exist "
		echo -n "or is not readable: "
		echo ${CONFFILE}
		exit 1
	fi
}

# If a configuration file hasn't been specified, use
# the default value (/etc/portsnap.conf)
default_conffile() {
	if [ -z "${CONFFILE}" ]; then
		CONFFILE="/etc/portsnap.conf"
	fi
}

# Read {KEYPRINT, SERVERNAME, WORKDIR, PORTSDIR} from the configuration
# file if they haven't already been set.  If the configuration
# file doesn't exist, do nothing.
# Also read REFUSE (which cannot be set via the command line) if it is
# present in the configuration file.
parse_conffile() {
	if [ -r "${CONFFILE}" ]; then
		for X in KEYPRINT WORKDIR PORTSDIR SERVERNAME; do
			eval _=\$${X}
			if [ -z "${_}" ]; then
				eval ${X}=`grep "^${X}=" "${CONFFILE}" |
				    cut -f 2- -d '=' | tail -1`
			fi
		done

		if grep -qE "^REFUSE[[:space:]]" ${CONFFILE}; then
			REFUSE="^(`
				grep -E "^REFUSE[[:space:]]" "${CONFFILE}" |
				    cut -c 7- | xargs echo | tr ' ' '|'
				`)"
		fi

		if grep -qE "^INDEX[[:space:]]" ${CONFFILE}; then
			INDEXPAIRS="`
				grep -E "^INDEX[[:space:]]" "${CONFFILE}" |
				    cut -c 7- | tr ' ' '|' | xargs echo`"
		fi
	fi
}

# If parameters have not been set, use default values
default_params() {
	_QUIETREDIR="/dev/null"
	_QUIETFLAG="-q"
	_STATSREDIR="/dev/stdout"
	_WORKDIR="/var/db/portsnap"
	_PORTSDIR="/usr/ports"
	_NDEBUG="-n"
	_LOCALDESC="/dev/null"
	for X in QUIETREDIR QUIETFLAG STATSREDIR WORKDIR PORTSDIR	\
	    NDEBUG LOCALDESC; do
		eval _=\$${X}
		eval __=\$_${X}
		if [ -z "${_}" ]; then
			eval ${X}=${__}
		fi
	done
}

# Perform sanity checks and set some final parameters
# in preparation for fetching files.  Also chdir into
# the working directory.
fetch_check_params() {
	export HTTP_USER_AGENT="portsnap (${COMMAND}, `uname -r`)"

	_SERVERNAME_z=\
"SERVERNAME must be given via command line or configuration file."
	_KEYPRINT_z="Key must be given via -k option or configuration file."
	_KEYPRINT_bad="Invalid key fingerprint: "
	_WORKDIR_bad="Directory does not exist or is not writable: "

	if [ -z "${SERVERNAME}" ]; then
		echo -n "`basename $0`: "
		echo "${_SERVERNAME_z}"
		exit 1
	fi
	if [ -z "${KEYPRINT}" ]; then
		echo -n "`basename $0`: "
		echo "${_KEYPRINT_z}"
		exit 1
	fi
	if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
		echo -n "`basename $0`: "
		echo -n "${_KEYPRINT_bad}"
		echo ${KEYPRINT}
		exit 1
	fi
	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
		echo -n "`basename $0`: "
		echo -n "${_WORKDIR_bad}"
		echo ${WORKDIR}
		exit 1
	fi
	cd ${WORKDIR} || exit 1

	BSPATCH=/usr/bin/bspatch
	SHA256=/sbin/sha256
	PHTTPGET=/usr/libexec/phttpget
}

# Perform sanity checks and set some final parameters
# in preparation for extracting or updating ${PORTSDIR}
# Complain if ${PORTSDIR} exists but is not writable,
# but don't complain if ${PORTSDIR} doesn't exist.
extract_check_params() {
	_WORKDIR_bad="Directory does not exist: "
	_PORTSDIR_bad="Directory is not writable: "

	if ! [ -d "${WORKDIR}" ]; then
		echo -n "`basename $0`: "
		echo -n "${_WORKDIR_bad}"
		echo ${WORKDIR}
		exit 1
	fi
	if [ -d "${PORTSDIR}" ] && ! [ -w "${PORTSDIR}" ]; then
		echo -n "`basename $0`: "
		echo -n "${_PORTSDIR_bad}"
		echo ${PORTSDIR}
		exit 1
	fi

	if ! [ -d "${WORKDIR}/files" -a -r "${WORKDIR}/tag"	\
	    -a -r "${WORKDIR}/INDEX" -a -r "${WORKDIR}/tINDEX" ]; then
		echo "No snapshot available.  Try running"
		echo "# `basename $0` fetch"
		exit 1
	fi

	MKINDEX=/usr/libexec/make_index
}

# Perform sanity checks and set some final parameters
# in preparation for updating ${PORTSDIR}
update_check_params() {
	extract_check_params

	if ! [ -r ${PORTSDIR}/.portsnap.INDEX ]; then
		echo "${PORTSDIR} was not created by portsnap."
		echo -n "You must run '`basename $0` extract' before "
		echo "running '`basename $0` update'."
		exit 1
	fi

}

#### Core functionality -- the actual work gets done here

# Use an SRV query to pick a server.  If the SRV query doesn't provide
# a useful answer, use the server name specified by the user.
# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
# from that; or if no servers are returned, use ${SERVERNAME}.
# This allows a user to specify "portsnap.freebsd.org" (in which case
# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
# (in which case portsnap will use that particular server, since there
# won't be an SRV entry for that name).
#
# We ignore the Port field, since we are always going to use port 80.

# Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
# no mirrors are available for any reason.
fetch_pick_server_init() {
	: > serverlist_tried

# Check that host(1) exists (i.e., that the system wasn't built with the
# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
	if ! which -s host; then
		: > serverlist_full
		return 1
	fi

	echo -n "Looking up ${SERVERNAME} mirrors... "

# Issue the SRV query and pull out the Priority, Weight, and Target fields.
# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
# "$name server selection ..."; we allow either format.
	MLIST="_http._tcp.${SERVERNAME}"
	host -t srv "${MLIST}" |
	    sed -nE "s/${MLIST} (has SRV record|server selection) //p" |
	    cut -f 1,2,4 -d ' ' |
	    sed -e 's/\.$//' |
	    sort > serverlist_full

# If no records, give up -- we'll just use the server name we were given.
	if [ `wc -l < serverlist_full` -eq 0 ]; then
		echo "none found."
		return 1
	fi

# Report how many mirrors we found.
	echo `wc -l < serverlist_full` "mirrors found."

# Generate a random seed for use in picking mirrors.  If HTTP_PROXY
# is set, this will be used to generate the seed; otherwise, the seed
# will be random.
	if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
		RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
		    tr -d 'a-f' |
		    cut -c 1-9`
	else
		RANDVALUE=`jot -r 1 0 999999999`
	fi
}

# Pick a mirror.  Returns 1 if we have run out of mirrors to try.
fetch_pick_server() {
# Generate a list of not-yet-tried mirrors
	sort serverlist_tried |
	    comm -23 serverlist_full - > serverlist

# Have we run out of mirrors?
	if [ `wc -l < serverlist` -eq 0 ]; then
		echo "No mirrors remaining, giving up."
		return 1
	fi

# Find the highest priority level (lowest numeric value).
	SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`

# Add up the weights of the response lines at that priority level.
	SRV_WSUM=0;
	while read X; do
		case "$X" in
		${SRV_PRIORITY}\ *)
			SRV_W=`echo $X | cut -f 2 -d ' '`
			SRV_WSUM=$(($SRV_WSUM + $SRV_W))
			;;
		esac
	done < serverlist

# If all the weights are 0, pretend that they are all 1 instead.
	if [ ${SRV_WSUM} -eq 0 ]; then
		SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
		SRV_W_ADD=1
	else
		SRV_W_ADD=0
	fi

# Pick a value between 0 and the sum of the weights - 1
	SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`

# Read through the list of mirrors and set SERVERNAME.  Write the line
# corresponding to the mirror we selected into serverlist_tried so that
# we won't try it again.
	while read X; do
		case "$X" in
		${SRV_PRIORITY}\ *)
			SRV_W=`echo $X | cut -f 2 -d ' '`
			SRV_W=$(($SRV_W + $SRV_W_ADD))
			if [ $SRV_RND -lt $SRV_W ]; then
				SERVERNAME=`echo $X | cut -f 3 -d ' '`
				echo "$X" >> serverlist_tried
				break
			else
				SRV_RND=$(($SRV_RND - $SRV_W))
			fi
			;;
		esac
	done < serverlist
}

# Check that we have a public key with an appropriate hash, or
# fetch the key if it doesn't exist.  Returns 1 if the key has
# not yet been fetched.
fetch_key() {
	if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
		return 0
	fi

	echo -n "Fetching public key from ${SERVERNAME}... "
	rm -f pub.ssl
	fetch ${QUIETFLAG} http://${SERVERNAME}/pub.ssl \
	    2>${QUIETREDIR} || true
	if ! [ -r pub.ssl ]; then
		echo "failed."
		return 1
	fi
	if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
		echo "key has incorrect hash."
		rm -f pub.ssl
		return 1
	fi
	echo "done."
}

# Fetch a snapshot tag
fetch_tag() {
	rm -f snapshot.ssl tag.new

	echo ${NDEBUG} "Fetching snapshot tag from ${SERVERNAME}... "
	fetch ${QUIETFLAG} http://${SERVERNAME}/$1.ssl		\
	    2>${QUIETREDIR} || true
	if ! [ -r $1.ssl ]; then
		echo "failed."
		return 1
	fi

	openssl rsautl -pubin -inkey pub.ssl -verify		\
	    < $1.ssl > tag.new 2>${QUIETREDIR} || true
	rm $1.ssl

	if ! [ `wc -l < tag.new` = 1 ] ||
	    ! grep -qE "^portsnap\|[0-9]{10}\|[0-9a-f]{64}" tag.new; then
		echo "invalid snapshot tag."
		return 1
	fi

	echo "done."

	SNAPSHOTDATE=`cut -f 2 -d '|' < tag.new`
	SNAPSHOTHASH=`cut -f 3 -d '|' < tag.new`
}

# Sanity-check the date on a snapshot tag
fetch_snapshot_tagsanity() {
	if [ `date "+%s"` -gt `expr ${SNAPSHOTDATE} + 31536000` ]; then
		echo "Snapshot appears to be more than a year old!"
		echo "(Is the system clock correct?)"
		echo "Cowardly refusing to proceed any further."
		return 1
	fi
	if [ `date "+%s"` -lt `expr ${SNAPSHOTDATE} - 86400` ]; then
		echo -n "Snapshot appears to have been created more than "
		echo "one day into the future!"
		echo "(Is the system clock correct?)"
		echo "Cowardly refusing to proceed any further."
		return 1
	fi
}

# Sanity-check the date on a snapshot update tag
fetch_update_tagsanity() {
	fetch_snapshot_tagsanity || return 1

	if [ ${OLDSNAPSHOTDATE} -gt ${SNAPSHOTDATE} ]; then
		echo -n "Latest snapshot on server is "
		echo "older than what we already have!"
		echo -n "Cowardly refusing to downgrade from "
		date -r ${OLDSNAPSHOTDATE}
		echo "to `date -r ${SNAPSHOTDATE}`."
		return 1
	fi
}

# Compare old and new tags; return 1 if update is unnecessary
fetch_update_neededp() {
	if [ ${OLDSNAPSHOTDATE} -eq ${SNAPSHOTDATE} ]; then
		echo -n "Latest snapshot on server matches "
		echo "what we already have."
		echo "No updates needed."
		rm tag.new
		return 1
	fi
	if [ ${OLDSNAPSHOTHASH} = ${SNAPSHOTHASH} ]; then
		echo -n "Ports tree hasn't changed since "
		echo "last snapshot."
		echo "No updates needed."
		rm tag.new
		return 1
	fi

	return 0
}

# Fetch snapshot metadata file
fetch_metadata() {
	rm -f ${SNAPSHOTHASH} tINDEX.new

	echo ${NDEBUG} "Fetching snapshot metadata... "
	fetch ${QUIETFLAG} http://${SERVERNAME}/t/${SNAPSHOTHASH} \
	    2>${QUIETREDIR} || return
	if [ "`${SHA256} -q ${SNAPSHOTHASH}`" != ${SNAPSHOTHASH} ]; then
		echo "snapshot metadata corrupt."
		return 1
	fi
	mv ${SNAPSHOTHASH} tINDEX.new
	echo "done."
}

# Warn user about bogus metadata
fetch_metadata_freakout() {
	echo
	echo "Portsnap metadata is correctly signed, but contains"
	echo "at least one line which appears bogus."
	echo "Cowardly refusing to proceed any further."
}

# Sanity-check a snapshot metadata file
fetch_metadata_sanity() {
	if grep -qvE "^[0-9A-Z.]+\|[0-9a-f]{64}$" tINDEX.new; then
		fetch_metadata_freakout
		return 1
	fi
	if [ `look INDEX tINDEX.new | wc -l` != 1 ]; then
		echo
		echo "Portsnap metadata appears bogus."
		echo "Cowardly refusing to proceed any further."
		return 1
	fi
}

# Take a list of ${oldhash}|${newhash} and output a list of needed patches
fetch_make_patchlist() {
	grep -vE "^([0-9a-f]{64})\|\1$" | 
		while read LINE; do
			X=`echo ${LINE} | cut -f 1 -d '|'`
			Y=`echo ${LINE} | cut -f 2 -d '|'`
			if [ -f "files/${Y}.gz" ]; then continue; fi
			if [ ! -f "files/${X}.gz" ]; then continue; fi
			echo "${LINE}"
		done
}

# Print user-friendly progress statistics
fetch_progress() {
	LNC=0
	while read x; do
		LNC=$(($LNC + 1))
		if [ $(($LNC % 10)) = 0 ]; then
			echo -n $LNC
		elif [ $(($LNC % 2)) = 0 ]; then
			echo -n .
		fi
	done
	echo -n " "
}

# Sanity-check an index file
fetch_index_sanity() {
	if grep -qvE "^[-_+./@0-9A-Za-z]+\|[0-9a-f]{64}$" INDEX.new ||
	    fgrep -q "./" INDEX.new; then
		fetch_metadata_freakout
		return 1
	fi
}

# Verify a list of files
fetch_snapshot_verify() {
	while read F; do
		if [ "`gunzip -c snap/${F} | ${SHA256} -q`" != ${F} ]; then
			echo "snapshot corrupt."
			return 1
		fi
	done
	return 0
}

# Fetch a snapshot tarball, extract, and verify.
fetch_snapshot() {
	while ! fetch_tag snapshot; do
		fetch_pick_server || return 1
	done
	fetch_snapshot_tagsanity || return 1
	fetch_metadata || return 1
	fetch_metadata_sanity || return 1

	rm -rf snap/

# Don't ask fetch(1) to be quiet -- downloading a snapshot of ~ 35MB will
# probably take a while, so the progrees reports that fetch(1) generates
# will be useful for keeping the users' attention from drifting.
	echo "Fetching snapshot generated at `date -r ${SNAPSHOTDATE}`:"
	fetch -r http://${SERVERNAME}/s/${SNAPSHOTHASH}.tgz || return 1

	echo -n "Extracting snapshot... "
	tar -xz --numeric-owner -f ${SNAPSHOTHASH}.tgz snap/ || return 1
	rm ${SNAPSHOTHASH}.tgz
	echo "done."

	echo -n "Verifying snapshot integrity... "
# Verify the metadata files
	cut -f 2 -d '|' tINDEX.new | fetch_snapshot_verify || return 1
# Extract the index
	rm -f INDEX.new
	gunzip -c snap/`look INDEX tINDEX.new |
	    cut -f 2 -d '|'`.gz > INDEX.new
	fetch_index_sanity || return 1
# Verify the snapshot contents
	cut -f 2 -d '|' INDEX.new | fetch_snapshot_verify || return 1
	echo "done."

# Move files into their proper locations
	rm -f tag INDEX tINDEX
	rm -rf files
	mv tag.new tag
	mv tINDEX.new tINDEX
	mv INDEX.new INDEX
	mv snap/ files/

	return 0
}

# Update a compressed snapshot
fetch_update() {
	rm -f patchlist diff OLD NEW filelist INDEX.new

	OLDSNAPSHOTDATE=`cut -f 2 -d '|' < tag`
	OLDSNAPSHOTHASH=`cut -f 3 -d '|' < tag`

	while ! fetch_tag latest; do
		fetch_pick_server || return 1
	done
	fetch_update_tagsanity || return 1
	fetch_update_neededp || return 0
	fetch_metadata || return 1
	fetch_metadata_sanity || return 1

	echo -n "Updating from `date -r ${OLDSNAPSHOTDATE}` "
	echo "to `date -r ${SNAPSHOTDATE}`."

# Generate a list of wanted metadata patches
	join -t '|' -o 1.2,2.2 tINDEX tINDEX.new |
	    fetch_make_patchlist > patchlist

# Attempt to fetch metadata patches
	echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
	echo ${NDEBUG} "metadata patches.${DDSTATS}"
	tr '|' '-' < patchlist |
	    lam -s "tp/" - -s ".gz" |
	    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
	    2>${STATSREDIR} | fetch_progress
	echo "done."

# Attempt to apply metadata patches
	echo -n "Applying metadata patches... "
	while read LINE; do
		X=`echo ${LINE} | cut -f 1 -d '|'`
		Y=`echo ${LINE} | cut -f 2 -d '|'`
		if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
		gunzip -c < ${X}-${Y}.gz > diff
		gunzip -c < files/${X}.gz > OLD
		cut -c 2- diff | join -t '|' -v 2 - OLD > ptmp
		grep '^\+' diff | cut -c 2- |
		    sort -k 1,1 -t '|' -m - ptmp > NEW
		if [ `${SHA256} -q NEW` = ${Y} ]; then
			mv NEW files/${Y}
			gzip -n files/${Y}
		fi
		rm -f diff OLD NEW ${X}-${Y}.gz ptmp
	done < patchlist 2>${QUIETREDIR}
	echo "done."

# Update metadata without patches
	join -t '|' -v 2 tINDEX tINDEX.new |
	    cut -f 2 -d '|' /dev/stdin patchlist |
		while read Y; do
			if [ ! -f "files/${Y}.gz" ]; then
				echo ${Y};
			fi
		done > filelist
	echo -n "Fetching `wc -l < filelist | tr -d ' '` "
	echo ${NDEBUG} "metadata files... "
	lam -s "f/" - -s ".gz" < filelist |
	    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
	    2>${QUIETREDIR}

	while read Y; do
		if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
			mv ${Y}.gz files/${Y}.gz
		else
			echo "metadata is corrupt."
			return 1
		fi
	done < filelist
	echo "done."

# Extract the index
	gunzip -c files/`look INDEX tINDEX.new |
	    cut -f 2 -d '|'`.gz > INDEX.new
	fetch_index_sanity || return 1

# If we have decided to refuse certain updates, construct a hybrid index which
# is equal to the old index for parts of the tree which we don't want to
# update, and equal to the new index for parts of the tree which gets updates.
# This means that we should always have a "complete snapshot" of the ports
# tree -- with the caveat that it isn't actually a snapshot.
	if [ ! -z "${REFUSE}" ]; then
		echo "Refusing to download updates for ${REFUSE}"	\
		    >${QUIETREDIR}

		grep -Ev "${REFUSE}" INDEX.new > INDEX.tmp
		grep -E "${REFUSE}" INDEX |
		    sort -m -k 1,1 -t '|' - INDEX.tmp > INDEX.new
		rm -f INDEX.tmp
	fi

# Generate a list of wanted ports patches
	join -t '|' -o 1.2,2.2 INDEX INDEX.new |
	    fetch_make_patchlist > patchlist

# Attempt to fetch ports patches
	echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
	echo ${NDEBUG} "patches.${DDSTATS}"
	tr '|' '-' < patchlist | lam -s "bp/" - |
	    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
	    2>${STATSREDIR} | fetch_progress
	echo "done."

# Attempt to apply ports patches
	echo -n "Applying patches... "
	while read LINE; do
		X=`echo ${LINE} | cut -f 1 -d '|'`
		Y=`echo ${LINE} | cut -f 2 -d '|'`
		if [ ! -f "${X}-${Y}" ]; then continue; fi
		gunzip -c < files/${X}.gz > OLD
		${BSPATCH} OLD NEW ${X}-${Y}
		if [ `${SHA256} -q NEW` = ${Y} ]; then
			mv NEW files/${Y}
			gzip -n files/${Y}
		fi
		rm -f diff OLD NEW ${X}-${Y}
	done < patchlist 2>${QUIETREDIR}
	echo "done."

# Update ports without patches
	join -t '|' -v 2 INDEX INDEX.new |
	    cut -f 2 -d '|' /dev/stdin patchlist |
		while read Y; do
			if [ ! -f "files/${Y}.gz" ]; then
				echo ${Y};
			fi
		done > filelist
	echo -n "Fetching `wc -l < filelist | tr -d ' '` "
	echo ${NDEBUG} "new ports or files... "
	lam -s "f/" - -s ".gz" < filelist |
	    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
	    2>${QUIETREDIR}

	while read Y; do
		if [ `gunzip -c < ${Y}.gz | ${SHA256} -q` = ${Y} ]; then
			mv ${Y}.gz files/${Y}.gz
		else
			echo "snapshot is corrupt."
			return 1
		fi
	done < filelist
	echo "done."

# Remove files which are no longer needed
	cut -f 2 -d '|' tINDEX INDEX | sort > oldfiles
	cut -f 2 -d '|' tINDEX.new INDEX.new | sort | comm -13 - oldfiles |
	    lam -s "files/" - -s ".gz" | xargs rm -f
	rm patchlist filelist oldfiles

# We're done!
	mv INDEX.new INDEX
	mv tINDEX.new tINDEX
	mv tag.new tag

	return 0
}

# Do the actual work involved in "fetch" / "cron".
fetch_run() {
	fetch_pick_server_init && fetch_pick_server

	while ! fetch_key; do
		fetch_pick_server || return 1
	done

	if ! [ -d files -a -r tag -a -r INDEX -a -r tINDEX ]; then
		fetch_snapshot || return 1
	fi
	fetch_update || return 1
}

# Build a ports INDEX file
extract_make_index() {
	if ! look $1 ${WORKDIR}/tINDEX > /dev/null; then
		echo -n "$1 not provided by portsnap server; "
		echo "$2 not being generated."
	else
	gunzip -c "${WORKDIR}/files/`look $1 ${WORKDIR}/tINDEX |
	    cut -f 2 -d '|'`.gz" |
	    cat - ${LOCALDESC} |
	    ${MKINDEX} /dev/stdin > ${PORTSDIR}/$2
	fi
}

# Create INDEX, INDEX-5, INDEX-6
extract_indices() {
	echo -n "Building new INDEX files... "
	for PAIR in ${INDEXPAIRS}; do
		INDEXFILE=`echo ${PAIR} | cut -f 1 -d '|'`
		DESCRIBEFILE=`echo ${PAIR} | cut -f 2 -d '|'`
		extract_make_index ${DESCRIBEFILE} ${INDEXFILE} || return 1
	done
	echo "done."
}

# Create .portsnap.INDEX; if we are REFUSEing to touch certain directories,
# merge the values from any exiting .portsnap.INDEX file.
extract_metadata() {
	if [ -z "${REFUSE}" ]; then
		sort ${WORKDIR}/INDEX > ${PORTSDIR}/.portsnap.INDEX
	elif [ -f ${PORTSDIR}/.portsnap.INDEX ]; then
		grep -E "${REFUSE}" ${PORTSDIR}/.portsnap.INDEX	\
		    > ${PORTSDIR}/.portsnap.INDEX.tmp
		grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort |
		    sort -m - ${PORTSDIR}/.portsnap.INDEX.tmp	\
		    > ${PORTSDIR}/.portsnap.INDEX
		rm -f ${PORTSDIR}/.portsnap.INDEX.tmp
	else
		grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort \
		    > ${PORTSDIR}/.portsnap.INDEX
	fi
}

# Do the actual work involved in "extract"
extract_run() {
	mkdir -p ${PORTSDIR} || return 1

	if !
		if ! [ -z "${EXTRACTPATH}" ]; then
			grep "^${EXTRACTPATH}" ${WORKDIR}/INDEX
		elif ! [ -z "${REFUSE}" ]; then
			grep -vE "${REFUSE}" ${WORKDIR}/INDEX
		else
			cat ${WORKDIR}/INDEX
		fi | tr '|' ' ' | while read FILE HASH; do
		echo ${PORTSDIR}/${FILE}
		if ! [ -r "${WORKDIR}/files/${HASH}.gz" ]; then
			echo "files/${HASH}.gz not found -- snapshot corrupt."
			return 1
		fi
		case ${FILE} in
		*/)
			rm -rf ${PORTSDIR}/${FILE%/}
			mkdir -p ${PORTSDIR}/${FILE}
			tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
			    -C ${PORTSDIR}/${FILE}
			;;
		*)
			rm -f ${PORTSDIR}/${FILE}
			tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
			    -C ${PORTSDIR} ${FILE}
			;;
		esac
	done; then
		return 1
	fi
	if [ ! -z "${EXTRACTPATH}" ]; then
		return 0;
	fi

	extract_metadata
	extract_indices
}

# Do the actual work involved in "update"
update_run() {
	if ! [ -z "${INDEXONLY}" ]; then
		extract_indices >/dev/null || return 1
		return 0
	fi

	if sort ${WORKDIR}/INDEX |
	    cmp -s ${PORTSDIR}/.portsnap.INDEX -; then
		echo "Ports tree is already up to date."
		return 0
	fi

# If we are REFUSEing to touch certain directories, don't remove files
# from those directories (even if they are out of date)
	echo -n "Removing old files and directories... "
	if ! [ -z "${REFUSE}" ]; then 
		sort ${WORKDIR}/INDEX |
		    comm -23 ${PORTSDIR}/.portsnap.INDEX - | cut -f 1 -d '|' |
		    grep -vE "${REFUSE}" |
		    lam -s "${PORTSDIR}/" - |
		    sed -e 's|/$||' | xargs rm -rf
	else
		sort ${WORKDIR}/INDEX |
		    comm -23 ${PORTSDIR}/.portsnap.INDEX - | cut -f 1 -d '|' |
		    lam -s "${PORTSDIR}/" - |
		    sed -e 's|/$||' | xargs rm -rf
	fi
	echo "done."

# Install new files
	echo "Extracting new files:"
	if !
		if ! [ -z "${REFUSE}" ]; then
			grep -vE "${REFUSE}" ${WORKDIR}/INDEX | sort
		else
			sort ${WORKDIR}/INDEX
		fi |
	    comm -13 ${PORTSDIR}/.portsnap.INDEX - |
	    while read LINE; do
		FILE=`echo ${LINE} | cut -f 1 -d '|'`
		HASH=`echo ${LINE} | cut -f 2 -d '|'`
		echo ${PORTSDIR}/${FILE}
		if ! [ -r "${WORKDIR}/files/${HASH}.gz" ]; then
			echo "files/${HASH}.gz not found -- snapshot corrupt."
			return 1
		fi
		case ${FILE} in
		*/)
			mkdir -p ${PORTSDIR}/${FILE}
			tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
			    -C ${PORTSDIR}/${FILE}
			;;
		*)
			tar -xz --numeric-owner -f ${WORKDIR}/files/${HASH}.gz \
			    -C ${PORTSDIR} ${FILE}
			;;
		esac
	done; then
		return 1
	fi

	extract_metadata
	extract_indices
}

#### Main functions -- call parameter-handling and core functions

# Using the command line, configuration file, and defaults,
# set all the parameters which are needed later.
get_params() {
	init_params
	parse_cmdline $@
	sanity_conffile
	default_conffile
	parse_conffile
	default_params
}

# Fetch command.  Make sure that we're being called
# interactively, then run fetch_check_params and fetch_run
cmd_fetch() {
	if [ ! -t 0 ]; then
		echo -n "`basename $0` fetch should not "
		echo "be run non-interactively."
		echo "Run `basename $0` cron instead."
		exit 1
	fi
	fetch_check_params
	fetch_run || exit 1
}

# Cron command.  Make sure the parameters are sensible; wait
# rand(3600) seconds; then fetch updates.  While fetching updates,
# send output to a temporary file; only print that file if the
# fetching failed.
cmd_cron() {
	fetch_check_params
	sleep `jot -r 1 0 3600`

	TMPFILE=`mktemp /tmp/portsnap.XXXXXX` || exit 1
	if ! fetch_run >> ${TMPFILE}; then
		cat ${TMPFILE}
		rm ${TMPFILE}
		exit 1
	fi

	rm ${TMPFILE}
}

# Extract command.  Make sure the parameters are sensible,
# then extract the ports tree (or part thereof).
cmd_extract() {
	extract_check_params
	extract_run || exit 1
}

# Update command.  Make sure the parameters are sensible,
# then update the ports tree.
cmd_update() {
	update_check_params
	update_run || exit 1
}

# Alfred command.  Run 'fetch' or 'cron' depending on
# whether stdin is a terminal; then run 'update' or
# 'extract' depending on whether ${PORTSDIR} exists.
cmd_alfred() {
	if [ -t 0 ]; then
		cmd_fetch
	else
		cmd_cron
	fi
	if [ -d ${PORTSDIR} ]; then
		cmd_update
	else
		cmd_extract
	fi
}

#### Entry point

# Make sure we find utilities from the base system
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}

# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
export LC_ALL=C

get_params $@
for COMMAND in ${COMMANDS}; do
	cmd_${COMMAND}
done

Man Man