1#! /bin/sh
2#
3# $NetBSD: regpkg,v 1.19 2009/12/02 15:52:14 apb Exp $
4#
5# Copyright (c) 2003,2009 The NetBSD Foundation, Inc.
6# All rights reserved.
7#
8# This code is derived from software contributed to The NetBSD Foundation
9# by Alistair Crooks (agc@NetBSD.org)
10#
11# Redistribution and use in source and binary forms, with or without
12# modification, are permitted provided that the following conditions
13# are met:
14# 1. Redistributions of source code must retain the above copyright
15#    notice, this list of conditions and the following disclaimer.
16# 2. Redistributions in binary form must reproduce the above copyright
17#    notice, this list of conditions and the following disclaimer in the
18#    documentation and/or other materials provided with the distribution.
19#
20# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30# POSSIBILITY OF SUCH DAMAGE.
31#
32
33# Usage: regpkg [options] set pkgname
34#
35# Registers a syspkg in the database directory,
36# and optionally creates a binary package.
37#
38# Options:
39#   -q		Quiet.
40#   -v		Verbose.
41#   -f		Force.
42#   -m		Ignore errors from missing files.
43#   -u		Update.
44#   -c		Use cached information from ${BUILD_INFO_CACHE}.
45#   -d destdir	Sets DESTDIR.
46#   -t binpkgdir Create a binary package (in *.tgz format) in the
47#		specified directory.  Without this option, a binary
48#		package is not created.
49#   -M metalog	Use the specified metalog file to override file
50#		or directory attributes when creating a binary package.
51#   -N etcdir	Use the specified directory for passwd and group files.
52#
53# When -f is set:  If the desired syspkg already exists, it is overwritten.
54# When -u is set:  If the desired syspkg already exists, it might be
55#		overwritten or left alone, depending on whether it's older
56#		or newer than the files that belong to the syspkg.
57# When neither -u nor -f are set:  It's an error for the desired syspkg
58#		to already exist.
59
60prog="${0##*/}"
61toppid=$$
62rundir="$(dirname "$0")" # ${0%/*} isn't good enough when there's no "/"
63. "${rundir}/sets.subr"
64
65bomb()
66{
67	#echo "${prog}: bomb: start, toppid=${toppid} \$\$=$$"
68	kill ${toppid}		# in case we were invoked from a subshell
69	#echo "${prog}: bomb: killed ${toppid}"
70	exit 1
71}
72
73# A literal newline
74nl='
75'
76# A literal tab
77tab='	'
78
79# Prefixes for error messages, warnings, and important informational
80# messages.
81ERROR="${prog}: ERROR: "
82WARNING="${prog}: WARNING: "
83NOTE="${prog}: NOTE: "
84ERRWARN="${ERROR}"	# may be changed by "-f" (force) command line flag
85ERRWARNNOTE="${ERROR}"	# may be changed by "-u" (update) command line flag
86
87#
88# All temporary files will go in ${SCRATCH}, which will be deleted on
89# exit.
90#
91SCRATCH="$(${MKTEMP} -d "/var/tmp/${0##*/}.XXXXXX")"
92if [ $? -ne 0 -o \! -d "${SCRATCH}" ]; then
93	echo >&2 "${prog}: Could not create scratch directory."
94	bomb
95fi
96
97#
98# cleanup() always deletes the SCRATCH directory, and might also
99# delete other files or directories.
100#
101es=0
102cleanup_must_delete_binpkgfile=false
103cleanup_must_delete_dbsubdir=false
104cleanup()
105{
106	trap - 0
107	#echo "${prog}: cleanup start"
108	if ${cleanup_must_delete_binpkgfile:-false} && [ -e "${binpkgfile}" ]
109	then
110		echo >&2 "${prog}: deleting partially-created ${binpkgfile}"
111		rm -f "${binpkgfile}"
112	fi
113	if ${cleanup_must_delete_dbsubdir:-false} \
114	   && [ -e "${SYSPKG_DB_SUBDIR}" ]
115	then
116		echo >&2 "${prog}: deleting partially-created ${SYSPKG_DB_SUBDIR}"
117		rm -rf "${SYSPKG_DB_SUBDIR}"
118	fi
119	rm -rf "${SCRATCH}"
120	#echo "${prog}: cleanup done, exit ${es}"
121	exit ${es}
122}
123trap 'es=128; cleanup' 1 2 3 13 15	# HUP INT QUIT PIPE TERM
124trap 'es=$?; cleanup' 0 		# EXIT
125
126#
127# Parse command line args.
128#
129verbose=false
130verbosity=0
131quiet=false
132force=false
133update=false
134allowmissing=false
135DESTDIR="${DESTDIR}"
136binpkgdir=""
137metalog=""
138etcdir=""
139SYSPKG_DB_TOPDIR=""
140pkgset=""
141pkg=""
142parse_args()
143{
144	while [ $# -gt 2 ]; do
145		case "$1" in
146		-q)	quiet=true; verbose=false ;;
147		-v)	verbose=true; quiet=false
148			verbosity=$(( ${verbosity} + 1 ))
149			;;
150		-f)	force=true ;;
151		-u)	update=true ;;
152		-m)	allowmissing=true ;;
153		-c)	# The -c option is ignored.  The BUILD_INFO_CACHE
154			# environment variable is used instead.
155			;;
156		-d)	DESTDIR="$2"; shift ;;
157		-d*)	DESTDIR="${1#-?}" ;;
158		-t)	binpkgdir="$2"; shift ;;
159		-t*)	binpkgdir="${1#-?}" ;;
160		-M)	metalog="$2"; shift ;;
161		-M*)	metalog="${1#-?}" ;;
162		-N)	etcdir="$2"; shift ;;
163		-N*)	etcdir="${1#-?}" ;;
164		*)	break ;;
165		esac
166		shift
167	done
168	if ${force}; then
169		ERRWARN="${WARNING}"
170	else
171		ERRWARN="${ERROR}"
172	fi
173	if ${update}; then
174		ERRWARNNOTE="${NOTE}"
175	else
176		ERRWARNNOTE="${ERRWARN}"
177	fi
178	DESTDIR="${DESTDIR%/}" # delete trailing "/" if any
179	if [ \! -n "${etcdir}" ]; then
180		etcdir="${DESTDIR}/etc"
181	fi
182	if [ -n "${binpkgdir}" -a \! -d "${binpkgdir}" ]; then
183		echo >&2 "${ERROR}binary pkg directory ${binpkgdir} does not exist"
184		bomb
185	fi
186	#
187	# SYSPKG_DB_TOPDIR is the top level directory for registering
188	# syspkgs.  It defaults to ${DESTDIR}/var/db/syspkg, but can be
189	# overridden by environment variables SYSPKG_DBDIR or PKG_DBDIR.
190	#
191	# Note that this corresponds to the default value of PKG_DBDIR
192	# set in .../distrib/syspkg/mk/bsd.syspkg.mk.
193	# 
194	SYSPKG_DB_TOPDIR="${SYSPKG_DBDIR:-${PKG_DBDIR:-${DESTDIR}/var/db/syspkg}}"
195
196	if [ $# -ne 2 ]; then
197		echo "Usage: regpkg [options] set pkgname"
198		bomb
199	fi
200
201	pkgset="$1"
202	pkg="$2"
203}
204
205#
206# make_PLIST() creates a skeleton PLIST from the pkgset description.
207#
208# The result is stored in the file ${PLIST}.
209#
210PLIST="${SCRATCH}/PLIST"
211make_PLIST()
212{
213	if ${verbose}; then
214		echo "Making PLIST for \"${pkg}\" package (part of ${pkgset} set)"
215	fi
216	prefix="${DESTDIR:-/}"
217	realprefix=/
218	${HOST_SH} "${rundir}/makeplist" -p "${prefix}" -I "${realprefix}" \
219		"${pkgset}" "${pkg}" \
220		>"${PLIST}" 2>"${SCRATCH}/makeplist-errors"
221	if ${EGREP} -v '^DEBUG:' "${SCRATCH}/makeplist-errors"; then
222		# "find" invoked from makeplist sometimes reports
223		# errors about missing files or directories, and
224		# makeplist ignores the errors.  Catch them here.
225		echo >&2 "${ERROR}makeplist reported errors for ${pkg}:"
226		cat >&2 "${SCRATCH}/makeplist-errors"
227		echo >&2 "${ERROR}see above for errors from makeplist"
228		if ${allowmissing}; then
229			echo >&2 "${prog}: ${NOTE}: ignoring above errors, due to '-m' option."
230		else
231			${force} || bomb
232		fi
233	fi
234}
235
236#
237# init_allfiles() converts the PLIST (which contains relative filenames)
238# into a list of absolute filenames.  Directories are excluded from the
239# result.
240# 
241# The result is stored in the variable ${allfiles}.
242#
243allfiles=''
244init_allfiles()
245{
246	[ -f "${PLIST}" ] || make_PLIST
247	allfiles="$(${AWK} '
248		BEGIN { destdir = "'"${DESTDIR%/}"'" }
249		/^@cwd/ { prefix = $2; next }
250		/^@dirrm/ { next }
251		{ printf("%s%s%s\n", destdir, prefix, $0) }' "${PLIST}")"
252}
253
254#
255# init_newestfile() finds the newest file (most recent mtime).
256#
257# The result is stored in the variable ${newestfile}.
258#
259newestfile=''
260init_newestfile()
261{
262	[ -s "${allfiles}" ] || init_allfiles
263	# We assume no shell special characters in ${allfiles},
264	# and spaces only between file names, not inside file names.
265	# This should be safe, because it has no no user-specified parts.
266	newestfile="$(${LS} -1dt ${allfiles} | ${SED} '1q')"
267}
268
269#
270# Various ways of getting parts of the syspkg version number:
271#
272# get_osvers() - get the OS version number from osrelease.sh or $(uname -r),
273#		return it in ${osvers}, and set ${method}.
274# get_tinyvers() - get the tiny version number from the "versions" file,
275#		and return it in ${tinyvers}.  Does not set ${method}.
276# get_newest_rcsid_date() - get the newest RCS date,
277#		and return it in ${newest}.  Does not set ${method}.
278# get_newest_mtime_date() - get the newest file modification date,
279#		and return it in ${newest}.  Does not set ${method}.
280# get_newest_date() - get date from rcsid or mtime, return it in ${newest},
281#		and set ${method}.
282#
283get_osvers()
284{
285	if [ -f ../../sys/conf/osrelease.sh ]; then
286		osvers="$(${HOST_SH} ../../sys/conf/osrelease.sh)"
287		method=osreleases
288	else
289		osvers="$(${UNAME} -r)"
290		method=uname
291	fi
292	#echo "${osvers}"
293}
294get_tinyvers()
295{
296	tinyvers="$(${AWK} '$1 ~ '/"${pkg}"/' { print $2 }' \
297			"${rundir}/versions")"
298	case "${tinyvers}" in
299	"")	tinyvers=0
300		;;
301	esac
302	#echo "${tinyvers}"
303}
304get_newest_rcsid_date()
305{
306	[ -s "${allfiles}" ] || init_allfiles
307
308	# Old RCS identifiers might have 2-digit years, so we match both
309	# YY/MM/DD and YYYY/MM/DD.  We also try to deal with the Y10K
310	# problem by allowing >4 digit years.
311	newest=0
312	case "${allfiles}" in
313	"")	;;
314	*)	newest="$(${IDENT} ${allfiles} 2>/dev/null | ${AWK} '
315			BEGIN { last = 0 }
316			$2 == "crt0.c,v" { next }
317			NF == 8 && \
318			$4 ~ /^[0-9][0-9]\/[0-9][0-9]\/[0-9][0-9]$/ \
319				{ t = "19" $4; gsub("/", "", t);
320				  if (t > last) last = t; }
321			NF == 8 && \
322			$4 ~ /^[0-9][0-9][0-9][0-9][0-9]*\/[0-9][0-9]\/[0-9][0-9]$/ \
323				{ t = $4; gsub("/", "", t);
324				  if (t > last) last = t; }
325			END { print last }')"
326		method=ident
327		;;
328	esac
329	#echo "${newest}"
330}
331get_newest_mtime_date()
332{
333	[ -s "${newestfile}" ] || init_newestfile
334
335	# We could simplify the awk program to take advantage of the
336	# fact thet it should have exactly one line of input.
337	newest="$(${ENV_CMD} TZ=UTC LOCALE=C ${LS} -lT "${newestfile}" \
338		| ${AWK} '
339		BEGIN { newest = 0 }
340		{
341			t = $9 "";
342			if ($6 == "Jan") t = t "01";
343			if ($6 == "Feb") t = t "02";
344			if ($6 == "Mar") t = t "03";
345			if ($6 == "Apr") t = t "04";
346			if ($6 == "May") t = t "05";
347			if ($6 == "Jun") t = t "06";
348			if ($6 == "Jul") t = t "07";
349			if ($6 == "Aug") t = t "08";
350			if ($6 == "Sep") t = t "09";
351			if ($6 == "Oct") t = t "10";
352			if ($6 == "Nov") t = t "11";
353			if ($6 == "Dec") t = t "12";
354			if ($7 < 10) t = t "0";
355			t = t $7;
356			#these next two lines add the 24h clock onto the date
357			#gsub(":", "", $8);
358			#t = sprintf("%s.%4.4s", t, $8);
359			if (t > newest) newest = t;
360		}
361		END { print newest }')"
362	#echo "${newest}"
363}
364get_newest_date()
365{
366	get_newest_rcsid_date
367	case "${newest}" in
368	""|0)	get_newest_mtime_date
369		method=ls
370		;;
371	*)	method=rcsid
372		;;
373	esac
374	#echo "${newest}"
375}
376
377#
378# choose_version_number() chooses the syspkg version number,
379# by concatenating several components (OS version, syspkg "tiny"
380# version and date).  We end up with something like
381# osvers="3.99.15", tinyvers="0", newest="20060104",
382# and t="3.99.15.0.20060104".
383#
384# The result is stored in the variables ${t} and ${method}.
385#
386method=''
387t=''
388choose_version_number()
389{
390	get_osvers; m1="${method}"
391	get_tinyvers # does not set ${method}
392	get_newest_date; m2="${method}"
393	t="${osvers}.${tinyvers}.${newest}"
394	method="${m1}.${m2}"
395
396	# print version number that we're using
397	if ${verbose}; then
398		echo "${pkg} - ${t} version using ${method} method"
399	fi
400}
401
402#
403# init_db_opts() sets the dbfile, dbtype and db_opts variables,
404# used for accessing the pkgdb.byfile.db database.
405#
406init_db_opts()
407{
408	dbfile="${SYSPKG_DB_TOPDIR}/pkgdb.byfile.db"
409	dbtype="btree"
410	db_opts=''
411	: ${TARGET_ENDIANNESS:="$(arch_to_endian "${MACHINE_ARCH}")"}
412	case "${TARGET_ENDIANNESS}" in
413	4321)	db_opts="${db_opts} -E B" # big-endian
414		;;
415	1234)	db_opts="${db_opts} -E L" # little-endian
416		;;
417	*)
418		echo >&2 "${WARNING}Unknown or unsupported target endianness"
419		echo >&2 "${NOTE}Using host endianness"
420		;;
421	esac
422	if ${update} || ${force}; then
423		# overwriting an existing entry is not an error
424		db_opts="${db_opts} -R"
425	fi
426	if [ ${verbosity} -lt 2 ]; then
427		# don't print all the keys added to the database
428		db_opts="${db_opts} -q"
429	fi
430}
431
432#
433# print_dir_exec_lines outputs an "@exec install" line for each
434# directory in ${PLIST}
435#
436print_dir_exec_lines()
437{
438	local dir uname gname mode
439	local dot_slash_dir
440	local no_dot_dir
441	local word line
442	${AWK} '/^@dirrm/ { print $2 }' <"${PLIST}" | \
443	${SORT} | \
444	while read dir; do
445		# Sanitise the name. ${dir} could be an absolute or
446		# relative name, with or without a leading "./".
447		# ${dot_slash_dir} always has a leading "./" (except when
448		# it's exactly equal to "."). ${no_dot_dir} never has a
449		# leading "." or "/" (except when it's exactly equal to
450		# ".").
451		case "${dir}" in
452		.|./|/)	dot_slash_dir=.  ;;
453		./*)	dot_slash_dir="${dir}" ;;
454		/*)	dot_slash_dir=".${dir}" ;;
455		*)	dot_slash_dir="./${dir}" ;;
456		esac
457		no_dot_dir="${dot_slash_dir#./}"
458		# Get the directory's owner, group, and mode
459		# from the live file system, or let it be overridden
460		# by the metalog.
461		eval "$(${STAT} -f 'uname=%Su gname=%Sg mode=%#OLp' \
462				"${DESTDIR}/${dot_slash_dir}")"
463		if [ -n "${metalog}" ]; then
464			line="$(echo "${dot_slash_dir}" | \
465				${AWK} -f "${rundir}/join.awk" \
466					/dev/stdin "${metalog}")"
467			for word in ${line}; do
468				case "${word}" in
469				uname=*|gname=*|mode=*)	eval "${word}" ;;
470				esac
471			done
472		fi
473		# XXX: Work around yet another pkg_add bug: @cwd lines
474		# do not actually cause the working directory to change,
475		# so file names in @exec lines need to be qualified by
476		# %D, which (in our case, since we know there's an
477		# "@cwd /" line) will be the dir name passed to
478		# "pkg_add -p PREFIX".
479		case "${no_dot_dir}" in
480		.) d="%D" ;;
481		*) d="%D/${no_dot_dir}" ;;
482		esac
483		cat <<EOF
484@exec install -d -o ${uname} -g ${gname} -m ${mode} ${d}
485EOF
486	done
487}
488
489#
490# register_syspkg() registers the syspkg in ${SYSPKG_DB_TOPDIR}.
491# This involves creating the subdirectory ${SYSPKG_DB_SUBDIR}
492# and populating it with several files.
493#
494register_syspkg()
495{
496	cleanup_must_delete_dbsubdir=true
497	[ -n "${SYSPKG_DB_SUBDIR}" ] || bomb
498	mkdir -p "${SYSPKG_DB_SUBDIR}"
499
500	#
501	# Guess what versions of other packages to depend on.
502	#
503	# If we are using the OS version as part of the pkg
504	# version, then depend on any version ">=${osvers}".  For
505	# example, etc-sys-etc-1.6ZI.0.20040206 might depend on
506	# base-sys-root>=1.6ZI.
507	#
508	# Failing that, depend on any version "-[0-9]*".
509	#
510	# XXX: We could extend the format of the "deps" file to carry
511	# this sort of information, so we wouldn't have to guess.
512	#
513	case "${t}" in
514	${osvers}.*)	depversion=">=${osvers}" ;;
515	*)		depversion="-[0-9]*" ;;
516	esac
517
518	#
519	# Add the dependencies.
520	#
521	# We always add a "@pkgdep" line for each prerequisite package.
522	#
523	# If the prerequisite pkg is already registered (as it should be
524	# if our caller is doing things in the right order), then we put
525	# its exact version number in a "@blddep" line.
526	#
527	${AWK} '$1 ~ '/"${pkg}"/' { print $2 }' "${rundir}/deps" | ${SORT} | \
528	while read depname; do
529		# ${pkgdepglob} is a shell glob pattern that should match
530		# any version of a pkg.  ${pkgdep} uses the special syntax
531		# for pkg dependencies, and is not usable as a shell
532		# glob pattern.
533		pkgdepglob="${depname}-[0-9]*"
534		pkgdep="${depname}${depversion}"
535		echo "@pkgdep ${pkgdep}"
536		blddep="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pkgdepglob} \
537			|| bomb)"
538		case "${blddep}" in
539		*\*)	# pkgdepglob did not match anything
540			echo >&2 "${WARNING}${pkg} depends on '${pkgdep}' but there is no matching syspkg in ${SYSPKG_DB_TOPDIR}"
541			;;
542		*\ *)	# pkgdepglob matched more than once.
543			echo >&2 "${ERRWARN}${pkg} depends on '${pkgdep}' but there are multiple matching syspkgs in ${SYSPKG_DB_TOPDIR}"
544			${force} || bomb
545			# If ${force} is set, then assume that the last
546			# match is the most recent.
547			# XXX: This might be wrong, because of
548			# differences between lexical sorting and
549			# numeric sorting.
550			lastmatch="${blddep##* }"
551			echo "@blddep ${lastmatch}"
552			;;
553		*)	# exactly one match.
554			# XXX: We ignore the possibility that the
555			# version we found via ${pkgdepglob} might not
556			# satisfy ${pkgdep}.  We could conceivably use
557			# "pkg_admin pmatch" to check, but that's not a
558			# host tool so we can't assume that it will be
559			# available.
560			echo "@blddep ${blddep}"
561			;;
562		esac
563	done >>"${PLIST}"
564
565	# create the comment (should be one line)
566	comment="$(${AWK} '$1 ~ '/"${pkg}"/' \
567			{ print substr($0, length($1) + 2) }' \
568			"${rundir}/comments")"
569	case "${comment}" in
570	"")	echo >&2 "${WARNING}no comment for \"${pkg}\" (using placeholder)"
571		comment="System package for ${pkg}"
572		;;
573	*"${nl}"*)
574		echo >&2 "${ERRWARN}multi-line comment for \"${pkg}\""
575		${force} || bomb
576		;;
577	esac
578	echo "${comment}" > "${SYSPKG_DB_SUBDIR}/+COMMENT"
579
580	# create the description (could be multiple lines)
581	descr="$(${AWK} '$1 ~ '/"${pkg}"/' {
582			print substr($0, length($1) + 2) }' \
583			"${rundir}/descrs")"
584	case "${descr}" in
585	"")	echo >&2 "${WARNING}no description for \"${pkg}\" (re-using comment)" 2>&1
586		descr="${comment}"
587		;;
588	esac
589	echo "${descr}" > "${SYSPKG_DB_SUBDIR}/+DESC"
590	${PRINTF} "\nHomepage:\nhttp://www.NetBSD.org/\n" >> "${SYSPKG_DB_SUBDIR}/+DESC"
591
592	# create the build information
593	if [ x"${BUILD_INFO_CACHE}" = x ]; then
594		{
595		# These variables describe the build
596		# environment, not the target.
597		echo "OPSYS=$(${UNAME} -s)"
598		echo "OS_VERSION=$(${UNAME} -r)"
599		${MAKE} -B -f- all <<EOF
600.include <bsd.own.mk>
601all:
602	@echo OBJECT_FMT=${OBJECT_FMT}
603	@echo MACHINE_ARCH=${MACHINE_ARCH}
604	@echo MACHINE_GNU_ARCH=${MACHINE_GNU_ARCH}
605EOF
606		} > "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
607	else
608		cp "${BUILD_INFO_CACHE}" "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
609	fi
610
611	# test for attributes
612	args=""
613	attrs="$(${AWK} '$1 ~ '/"${pkg}"/' { \
614			print substr($0, length($1) + 2) }' \
615		"${rundir}/attrs")"
616	for a in "${attrs}"; do
617		case "${attrs}" in
618		"")	;;
619		preserve)
620			echo "${pkg}-${t}" >"${SYSPKG_DB_SUBDIR}/+PRESERVE"
621			args="${args} -n ${SYSPKG_DB_SUBDIR}/+PRESERVE"
622			;;
623		esac
624	done
625
626	#
627	# Create ${SYSPKGSIR}/+CONTENTS from ${PLIST}, by adding an
628	# "@name" line and a lot of "@comment MD5:" lines.
629	#
630	{
631		rcsid='$NetBSD: regpkg,v 1.19 2009/12/02 15:52:14 apb Exp $'
632		utcdate="$(${ENV_CMD} TZ=UTC LOCALE=C \
633			${DATE} '+%Y-%m-%d %H:%M')"
634		user="${USER:-root}"
635		host="$(${HOSTNAME_CMD})"
636		echo "@name ${pkg}-${t}"
637		echo "@comment Packaged at ${utcdate} UTC by ${user}@${host}"
638		echo "@comment Packaged using ${prog} ${rcsid}"
639		# XXX: "option extract-in-place" might help to get
640		#	pkg_add to create directories.
641		# XXX: no, it doesn't work.  Yet another pkg_add bug.
642		## echo "@option extract-in-place"
643		# Move the @pkgdep and @blddep lines up, so that
644		# they are easy to see when people do "less
645		# ${DESTDIR}/var/db/syspkg/*/+CONTENTS".
646		${EGREP} '^(@pkgdep|@blddep)' "${PLIST}" || true
647		# Now do the remainder of the file.
648		while read line; do
649			case "${line}" in
650			@pkgdep*|@blddep*)
651				# already handled by grep above
652				;;
653			@cwd*)
654				# There should be exactly one @cwd line.
655				# Just after it, add an "@exec mkdir"
656				# line for every directory.  This is to
657				# work around a pkg-add bug (see
658				# <http://mail-index.NetBSD.org/tech-pkg/2003/12/11/0018.html>)
659				echo "${line}"
660				print_dir_exec_lines
661				;;
662			@*)	
663				# just pass through all other @foo lines
664				echo "${line}"
665				;;
666			*)	
667				# This should be a file name.  Pass it
668				# through, and append "@comment MD5:".
669				# XXX why not SHA256 ?
670				echo "${line}"
671				file="${DESTDIR}${line}"
672				if [ -f "${file}" -a -r "${file}" ];
673				then
674					md5sum="$(${CKSUM} -n -m "${file}" \
675						   | ${AWK} '{print $1}'
676						)"
677					echo "@comment MD5:${md5sum}"
678				fi
679				;;
680			esac
681		done <"${PLIST}"
682	} >"${SYSPKG_DB_SUBDIR}/+CONTENTS"
683
684	#
685	#  Update ${SYSPKG_DB_TOPDIR}/pkgdb.byfile.db.
686	#
687	{
688		init_db_opts # sets dbfile, dbtype, and db_opts
689
690		# Transform ${PLIST} into a form to be used as keys in
691		# ${dbfile}.  The results look like absolute paths,
692		# but they are really relative to ${DESTDIR}.
693		#
694		# "@dirrm ."		-> "/"
695		# "@dirrm foo/bar"	-> "/foo/bar"
696		# "@dirrm ./foo/bar"	-> "/foo/bar"
697		# "foo/bar/baz"		-> "/foo/bar/baz"
698		# "./foo/bar/baz"	-> "/foo/bar/baz"
699		#
700		dblist="${SCRATCH}/dblist"
701		${AWK} '/^@dirrm \.\//	{gsub("^.", "", $2); print $2; next}
702			/^@dirrm \.$/	{print "/"; next}
703			/^@dirrm/	{print "/" $2; next}
704			/^@/		{next}
705			/^\.\//		{gsub("^.", "", $0); print $0; next}
706			/./		{print "/" $0; next}' \
707			<"${PLIST}" >"${dblist}"
708		# Add all the path names to the database.
709		${AWK} '{print $1 "\t" "'"${pkg}-${t}"'"}' <"${dblist}" \
710		| ${DB} -w ${db_opts} -F "${tab}" -f - "${dbtype}" "${dbfile}"
711	}
712
713	if ${verbose}; then
714		echo "Registered ${pkg}-${t} in ${SYSPKG_DB_TOPDIR}"
715	elif ! ${quiet}; then
716		echo "Registered ${pkg}-${t}"
717	fi
718
719	cleanup_must_delete_dbsubdir=false
720}
721
722#
723# create_syspkg_tgz() creates the *.tgz file for the package.
724#
725# The output file is ${binpkgdir}/${pkg}-${t}.tgz.
726#
727create_syspkg_tgz()
728{
729	#
730	# pkg_create does not understand metalog files, so we have to
731	# use pax directly.
732	#
733	# We create two specfiles: specfile_overhead describes the
734	# special files that are part of the package system's metadata
735	# (+CONTENTS, +COMMENT, +DESCR, and more); and specfile_payload
736	# describes the files and directories that we actually want as
737	# part of the package's payload.
738	#
739	# We then use the specfiles to create a compressed tarball that
740	# contains both the overhead files and the payload files.
741	#
742	# There's no trivial way to get a single pax run to do
743	# everything we want, so we run pax twice, with a different
744	# working directory and a different specfile each time.
745	#
746	# We could conceivably make clever use of pax's "-s" option to
747	# get what we want from a single pax run with a single (more
748	# complicated) specfile, but the extra trouble doesn't seem
749	# warranted.
750	#
751	cleanup_must_delete_binpkgfile=true
752	specfile_overhead="${SCRATCH}/spec_overhead"
753	specfile_payload="${SCRATCH}/spec_payload"
754	tarball_uncompressed="${SCRATCH}/tarball_uncompressed"
755
756	# Create a specfile for all the overhead files (+CONTENTS and
757	# friends).
758	{
759		plusnames_first="${SCRATCH}/plusnames_first"
760		plusnames_rest="${SCRATCH}/plusnames_rest"
761
762		# Ensure that the first few files are in the same order
763		# that "pkg_create" would have used, just in case anything
764		# depends on that.  Other files in alphabetical order.
765		SHOULD_BE_FIRST="+CONTENTS +COMMENT +DESC"
766		(
767			cd "${SYSPKG_DB_SUBDIR}" || bomb
768			for file in ${SHOULD_BE_FIRST}; do
769				[ -e "./${file}" ] && echo "${file}"
770			done >"${plusnames_first}"
771			${LS} -1 | ${FGREP} -v -f "${plusnames_first}" \
772				>"${plusnames_rest}" \
773				|| true
774		)
775
776		# Convert the file list to specfile format, and override the
777		# uid/gid/mode.
778		{
779			echo ". optional type=dir"
780			${AWK} '{print "./" $0 " type=file uid=0 gid=0 mode=0444"
781				}' "${plusnames_first}" "${plusnames_rest}"
782		} >"${specfile_overhead}"
783	}
784
785	# Create a specfile for the payload of the package.
786	{
787		spec1="${SCRATCH}/spec1"
788		spec2="${SCRATCH}/spec2"
789
790		# Transform ${PLIST} into simple specfile format:
791		#
792		# "@dirrm ."		-> ". type=dir"
793		# "@dirrm foo/bar"	-> "./foo/bar type=dir"
794		# "@dirrm ./foo/bar"	-> "./foo/bar type=dir"
795		# "foo/bar/baz"		-> "./foo/bar/baz"
796		# "./foo/bar/baz"	-> "./foo/bar/baz"
797		#
798		# Ignores @cwd lines.  This should be safe, given how
799		# makeplist works.
800		${AWK} '/^@dirrm \.\//	{print $2 " type=dir"; next}
801			/^@dirrm \.$/	{print ". type=dir"; next}
802			/^@dirrm/	{print "./" $2 " type=dir"; next}
803			/^@/		{next}
804			/^\.\//		{print $0; next}
805			/./		{print "./" $0; next}' \
806			<"${PLIST}" >"${spec1}"
807
808		# If metalog was specified, attributes from metalog override
809		# attributes in the file system.  We also fake up an
810		# entry for the ./etc/mtree/set.${pkgset} file.
811		{
812			if [ -n "${metalog}" ]; then
813				${AWK} -f "${rundir}/join.awk" \
814					"${spec1}" "${metalog}"
815				${AWK} -f "${rundir}/join.awk" \
816					"${spec1}" /dev/stdin <<EOF
817./etc/mtree/set.${pkgset} type=file mode=0444 uname=root gname=wheel
818EOF
819			else
820				cat "${spec1}"
821			fi
822		} >"${spec2}"
823
824		#
825		# If a file or directory to was mentioned explicitly
826		# in ${PLIST} but not mentioned in ${metalog}, then the
827		# file or directory will not be mentioned in ${spec2}.
828		# This is an error, and means that the metalog was
829		# not built correctly.
830		#
831		if [ -n "${metalog}" ]; then
832			names1="${SCRATCH}/names1"
833			names2="${SCRATCH}/names2"
834			${AWK} '{print $1}' <"${spec1}" | ${SORT} >"${names1}"
835			${AWK} '{print $1}' <"${spec2}" | ${SORT} >"${names2}"
836			if ${FGREP} -v -f "${names2}" "${spec1}" >/dev/null
837			then
838				cat >&2 <<EOM
839${ERRWARN}The metalog file (${metalog}) does not
840	contain entries for the following files or directories
841	which should be part of the ${pkg} syspkg:
842EOM
843				${FGREP} -v -f "${names2}" "${spec1}" >&2
844				${force} || bomb
845			fi
846			if ${FGREP} -v -f "${names1}" "${spec2}" >/dev/null
847			then
848				cat >&2 <<EOM
849${ERRWARN}The following lines are in the metalog file
850	(${metalog}), and the corresponding files or directories
851	should be in the ${pkg} syspkg, but something is wrong:
852EOM
853				${FGREP} -v -f "${names1}" "${spec2}" >&2
854				bomb
855			fi
856		fi
857
858		# Add lines (tagged "optional") for any implicit directories.
859		#
860		# For example, if we have a file ./foo/bar/baz, then we add
861		# "./foo/bar optional type=dir", "./foo optional type=dir",
862		# and ". optional type=dir", unless those directories were
863		# already mentioned explicitly.
864		#
865		${AWK} -f "${rundir}/getdirs.awk" "${spec2}" \
866		| ${SORT} -u >"${specfile_payload}"
867	}
868
869	# Use two pax invocations followed by gzip to create
870	# the tgz file.
871	#
872	# Remove any leading "./" from path names, because that
873	# could confuse tools that work with binary packages.
874	(
875		cd "${SYSPKG_DB_SUBDIR}" && \
876		${PAX} -O -w -d -N"${etcdir}" -M '-s,^\./,,' \
877			-f "${tarball_uncompressed}" \
878			<"${specfile_overhead}" \
879		|| bomb
880	)
881	(
882		cd "${DESTDIR:-/}" && \
883		${PAX} -O -w -d -N"${etcdir}" -M '-s,^\./,,' \
884			-a -f "${tarball_uncompressed}" \
885			<"${specfile_payload}" \
886		|| bomb
887	)
888	${GZIP_CMD} -9n <"${tarball_uncompressed}" >"${binpkgfile}" || bomb
889
890	# (Extra space is to make message line up with "Registered" message.)
891	if ${verbose}; then
892		echo "  Packaged ${binpkgfile}"
893	elif ! ${quiet}; then
894		echo "  Packaged ${binpkgfile##*/}"
895	fi
896
897	cleanup_must_delete_binpkgfile=false
898
899}
900
901#
902# do_register_syspkg() registers the syspkg if appropriate.
903#
904# If SYSPKG_DB_SUBDIR already exists, that might be an error, depending
905# on ${force} and ${update} flags.
906#
907do_register_syspkg()
908{
909	# Check that necessary variables are defined
910	[ -n "${SYSPKG_DB_TOPDIR}" ] || bomb
911	[ -n "${SYSPKG_DB_SUBDIR}" ] || bomb
912
913	# Create SYSPKG_DB_TOPDIR if necessary
914	[ -d "${SYSPKG_DB_TOPDIR}" ] || mkdir -p "${SYSPKG_DB_TOPDIR}" || bomb
915
916	# A function to delete db entries referring to any version of ${pkg}
917	delete_old_db_entries()
918	{
919		init_db_opts # sets dbfile, dbtype, and db_opts
920		dblist="${SCRATCH}/dblist"
921		${DB} ${db_opts} -O "${tab}" "${dbtype}" "${dbfile}" \
922		| ${AWK} -F "${tab}" '$2 ~ /^'"${pkg}"'-[0-9]/ { print $1 }' \
923			>"${dblist}"
924		${DB} -d ${db_opts} -f "${dblist}" "${dbtype}" "${dbfile}"
925	}
926
927	# A function to delete any old version of ${pkg}
928	delete_old_pkg()
929	{
930		pattern="${pkg}-[0-9]*"
931		matches="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pattern} \
932			|| bomb)"
933		echo >&2 "${NOTE}deleting old pkg (${matches})"
934		cleanup_must_delete_dbsubdir=true
935		delete_old_db_entries
936		( cd "${SYSPKG_DB_TOPDIR}" && rm -rf ${matches} )
937	}
938
939	# Check whether another version of ${pkg} is already registered.
940	pattern="${pkg}-[0-9]*"
941	matches="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pattern} || bomb)"
942	case "${matches}" in
943	*\*)		;;	# wildcard did not match anything
944	"${pkg}-${t}")	;;	# exact match
945	*)	echo >&2 "${ERRWARNNOTE}another version of ${pkg} is already registered"
946		${verbose} && echo >&2 "	in ${SYSPKG_DB_TOPDIR}"
947		${verbose} && echo >&2 "	(while registering ${pkg}-${t})"
948		${force} || ${update} || bomb
949		delete_old_pkg
950		;;
951	esac
952
953	# Check whether the desired version of ${pkg} is already registered,
954	# and create it if appropriate.
955	if [ -d "${SYSPKG_DB_SUBDIR}" ]; then
956		echo >&2 "${ERRWARNNOTE}${pkg}-${t} is already registered"
957		${verbose} && echo >&2 "	in ${SYSPKG_DB_TOPDIR}"
958		if ${force}; then
959			delete_old_pkg
960			register_syspkg
961		elif ${update}; then
962			#
963			# If all files in SYSPKG_DB_SUBDIR are newer
964			# than all files in the pkg, then do nothing.
965			# Else delete and re-register the pkg.
966			#
967			[ -n "${newestfile}" ] || init_newestfile
968			if [ -n "${newestfile}" ]; then
969				case "$(${FIND} "${SYSPKG_DB_SUBDIR}" -type f \
970					! -newer "${newestfile}" -print)" \
971				in
972				"")	;;
973				*)
974					echo >&2 "${NOTE}some files are newer but pkg version is unchanged"
975					delete_old_pkg
976					register_syspkg
977					;;
978				esac
979
980			else
981				# No files in the pkg?  (This could happen
982				# if a pkg contains only directories.)
983				# Do nothing (keep the already-registered pkg).
984				:
985			fi
986		else
987			bomb
988		fi
989	else
990		register_syspkg
991	fi
992}
993
994#
995# do_create_syspkg_tgz() creates the the binary pkg (*.tgz) if
996# appropriate.
997#
998# If binpkgfile already exists, that might be an error, depending on
999# ${force} and ${update} flags.
1000#
1001do_create_syspkg_tgz()
1002{
1003	[ -n "${binpkgfile}" ] || bomb
1004
1005	delete_and_recreate()
1006	{
1007		echo >&2 "${ERRWARNNOTE}deleting and re-creating ${pkg}-${t}.tgz"
1008		rm -f "${binpkgfile}"
1009		create_syspkg_tgz
1010	}
1011
1012	# Check whether another version of ${pkg} already exists.
1013	pattern="${pkg}-[0-9]*"
1014	matches="$(cd "${binpkgdir}" && echo ${pattern} || bomb)"
1015	case "${matches}" in
1016	*\*)	;;	# wildcard did not match anything
1017	"${pkg}-${t}.tgz") ;;	# exact match
1018	*)	echo >&2 "${ERRWARNNOTE}another version of ${pkg} binary pkg already exists"
1019		${verbose} && echo >&2 "	in ${binpkgdir}"
1020		${verbose} && echo >&2 "	(while creating ${pkg}-${t}.tgz)"
1021		# If neither force nor update, this is a fatal error.
1022		# If force but not update, then leave old .tgz in place.
1023		# If update, then delete the old .tgz.
1024		${force} || ${update} || bomb
1025		if ${update}; then
1026			echo >&2 "${NOTE}deleting old binary pkg (${matches})"
1027			( cd "${binpkgdir}" && rm -f ${matches} || bomb )
1028		fi
1029		;;
1030	esac
1031
1032	# Check whether the desired version of ${pkg} already exists,
1033	# and create it if appropriate.
1034	if [ -e "${binpkgfile}" ]; then
1035		echo >&2 "${ERRWARNNOTE}${pkg}-${t}.tgz already exists"
1036		${verbose} && echo >&2 "	in ${binpkgdir}"
1037		if ${force}; then
1038			delete_and_recreate
1039		elif ${update}; then
1040			#
1041			# If all files in SYSPKG_DB_SUBDIR are older
1042			# than ${binpkgfile}, then do nothing.
1043			# Else delete and re-create the tgz.
1044			#
1045			case "$(${FIND} "${SYSPKG_DB_SUBDIR}" -type f \
1046				-newer "${binpkgfile}" -print)" \
1047			in
1048			"")	;;
1049			*)	delete_and_recreate ;;
1050			esac
1051		else
1052			bomb
1053		fi
1054	else
1055		create_syspkg_tgz
1056	fi
1057}
1058
1059####################
1060# begin main program
1061
1062parse_args ${1+"$@"}
1063make_PLIST
1064choose_version_number
1065SYSPKG_DB_SUBDIR="${SYSPKG_DB_TOPDIR}/${pkg}-${t}"
1066do_register_syspkg
1067if [ -n "${binpkgdir}" ]; then
1068	binpkgfile="${binpkgdir}/${pkg}-${t}.tgz"
1069	do_create_syspkg_tgz
1070fi
1071
1072exit 0
1073