xref: /netbsd-src/distrib/sets/regpkg (revision 6deb2c22d20de1d75d538e8a5c57b573926fd157)
1#! /bin/sh
2#
3# $NetBSD: regpkg,v 1.17 2009/10/17 23:43:22 agc 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		# XXX: what's the point of reporting _PKGTOOLS_VER
607		# when we roll everything by hand without using
608		# the pkg tools?
609		echo "_PKGTOOLS_VER=$(${PKG_CREATE} -V)"
610		} > "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
611	else
612		cp "${BUILD_INFO_CACHE}" "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
613	fi
614
615	# test for attributes
616	args=""
617	attrs="$(${AWK} '$1 ~ '/"${pkg}"/' { \
618			print substr($0, length($1) + 2) }' \
619		"${rundir}/attrs")"
620	for a in "${attrs}"; do
621		case "${attrs}" in
622		"")	;;
623		preserve)
624			echo "${pkg}-${t}" >"${SYSPKG_DB_SUBDIR}/+PRESERVE"
625			args="${args} -n ${SYSPKG_DB_SUBDIR}/+PRESERVE"
626			;;
627		esac
628	done
629
630	#
631	# Create ${SYSPKGSIR}/+CONTENTS from ${PLIST}, by adding an
632	# "@name" line and a lot of "@comment MD5:" lines.
633	#
634	{
635		rcsid='$NetBSD: regpkg,v 1.17 2009/10/17 23:43:22 agc Exp $'
636		utcdate="$(${ENV_CMD} TZ=UTC LOCALE=C \
637			${DATE} '+%Y-%m-%d %H:%M')"
638		user="${USER:-root}"
639		host="$(${HOSTNAME})"
640		echo "@name ${pkg}-${t}"
641		echo "@comment Packaged at ${utcdate} UTC by ${user}@${host}"
642		echo "@comment Packaged using ${prog} ${rcsid}"
643		# XXX: "option extract-in-place" might help to get
644		#	pkg_add to create directories.
645		# XXX: no, it doesn't work.  Yet another pkg_add bug.
646		## echo "@option extract-in-place"
647		# Move the @pkgdep and @blddep lines up, so that
648		# they are easy to see when people do "less
649		# ${DESTDIR}/var/db/syspkg/*/+CONTENTS".
650		${EGREP} '^(@pkgdep|@blddep)' "${PLIST}" || true
651		# Now do the remainder of the file.
652		while read line; do
653			case "${line}" in
654			@pkgdep*|@blddep*)
655				# already handled by grep above
656				;;
657			@cwd*)
658				# There should be exactly one @cwd line.
659				# Just after it, add an "@exec mkdir"
660				# line for every directory.  This is to
661				# work around a pkg-add bug (see
662				# <http://mail-index.NetBSD.org/tech-pkg/2003/12/11/0018.html>)
663				echo "${line}"
664				print_dir_exec_lines
665				;;
666			@*)
667				# just pass through all other @foo lines
668				echo "${line}"
669				;;
670			*)
671				# This should be a file name.  Pass it
672				# through, and append "@comment MD5:".
673				# XXX why not SHA256 ?
674				echo "${line}"
675				file="${DESTDIR}${line}"
676				if [ -f "${file}" -a -r "${file}" ];
677				then
678					md5sum="$(${CKSUM} -n -m "${file}" \
679						   | ${AWK} '{print $1}'
680						)"
681					echo "@comment MD5:${md5sum}"
682				fi
683				;;
684			esac
685		done <"${PLIST}"
686	} >"${SYSPKG_DB_SUBDIR}/+CONTENTS"
687
688	#
689	#  Update ${SYSPKG_DB_TOPDIR}/pkgdb.byfile.db.
690	#
691	{
692		init_db_opts # sets dbfile, dbtype, and db_opts
693
694		# Transform ${PLIST} into a form to be used as keys in
695		# ${dbfile}.  The results look like absolute paths,
696		# but they are really relative to ${DESTDIR}.
697		#
698		# "@dirrm ."		-> "/"
699		# "@dirrm foo/bar"	-> "/foo/bar"
700		# "@dirrm ./foo/bar"	-> "/foo/bar"
701		# "foo/bar/baz"		-> "/foo/bar/baz"
702		# "./foo/bar/baz"	-> "/foo/bar/baz"
703		#
704		dblist="${SCRATCH}/dblist"
705		${AWK} '/^@dirrm \.\//	{gsub("^.", "", $2); print $2; next}
706			/^@dirrm \.$/	{print "/"; next}
707			/^@dirrm/	{print "/" $2; next}
708			/^@/		{next}
709			/^\.\//		{gsub("^.", "", $0); print $0; next}
710			/./		{print "/" $0; next}' \
711			<"${PLIST}" >"${dblist}"
712		# Add all the path names to the database.
713		${AWK} '{print $1 "\t" "'"${pkg}-${t}"'"}' <"${dblist}" \
714		| ${DB} -w ${db_opts} -F "${tab}" -f - "${dbtype}" "${dbfile}"
715	}
716
717	if ${verbose}; then
718		echo "Registered ${pkg}-${t} in ${SYSPKG_DB_TOPDIR}"
719	elif ! ${quiet}; then
720		echo "Registered ${pkg}-${t}"
721	fi
722
723	cleanup_must_delete_dbsubdir=false
724}
725
726#
727# create_syspkg_tgz() creates the *.tgz file for the package.
728#
729# The output file is ${binpkgdir}/${pkg}-${t}.tgz.
730#
731create_syspkg_tgz()
732{
733	#
734	# pkg_create does not understand metalog files, so we have to
735	# use pax directly.
736	#
737	# We create two specfiles: specfile_overhead describes the
738	# special files that are part of the package system's metadata
739	# (+CONTENTS, +COMMENT, +DESCR, and more); and specfile_payload
740	# describes the files and directories that we actually want as
741	# part of the package's payload.
742	#
743	# We then use the specfiles to create a compressed tarball that
744	# contains both the overhead files and the payload files.
745	#
746	# There's no trivial way to get a single pax run to do
747	# everything we want, so we run pax twice, with a different
748	# working directory and a different specfile each time.
749	#
750	# We could conceivably make clever use of pax's "-s" option to
751	# get what we want from a single pax run with a single (more
752	# complicated) specfile, but the extra trouble doesn't seem
753	# warranted.
754	#
755	cleanup_must_delete_binpkgfile=true
756	specfile_overhead="${SCRATCH}/spec_overhead"
757	specfile_payload="${SCRATCH}/spec_payload"
758	tarball_uncompressed="${SCRATCH}/tarball_uncompressed"
759
760	# Create a specfile for all the overhead files (+CONTENTS and
761	# friends).
762	{
763		plusnames_first="${SCRATCH}/plusnames_first"
764		plusnames_rest="${SCRATCH}/plusnames_rest"
765
766		# Ensure that the first few files are in the same order
767		# that "pkg_create" would have used, just in case anything
768		# depends on that.  Other files in alphabetical order.
769		SHOULD_BE_FIRST="+CONTENTS +COMMENT +DESC"
770		(
771			cd "${SYSPKG_DB_SUBDIR}" || bomb
772			for file in ${SHOULD_BE_FIRST}; do
773				[ -e "./${file}" ] && echo "${file}"
774			done >"${plusnames_first}"
775			${LS} -1 | ${FGREP} -v -f "${plusnames_first}" \
776				>"${plusnames_rest}" \
777				|| true
778		)
779
780		# Convert the file list to specfile format, and override the
781		# uid/gid/mode.
782		{
783			echo ". optional type=dir"
784			${AWK} '{print "./" $0 " type=file uid=0 gid=0 mode=0444"
785				}' "${plusnames_first}" "${plusnames_rest}"
786		} >"${specfile_overhead}"
787	}
788
789	# Create a specfile for the payload of the package.
790	{
791		spec1="${SCRATCH}/spec1"
792		spec2="${SCRATCH}/spec2"
793
794		# Transform ${PLIST} into simple specfile format:
795		#
796		# "@dirrm ."		-> ". type=dir"
797		# "@dirrm foo/bar"	-> "./foo/bar type=dir"
798		# "@dirrm ./foo/bar"	-> "./foo/bar type=dir"
799		# "foo/bar/baz"		-> "./foo/bar/baz"
800		# "./foo/bar/baz"	-> "./foo/bar/baz"
801		#
802		# Ignores @cwd lines.  This should be safe, given how
803		# makeplist works.
804		${AWK} '/^@dirrm \.\//	{print $2 " type=dir"; next}
805			/^@dirrm \.$/	{print ". type=dir"; next}
806			/^@dirrm/	{print "./" $2 " type=dir"; next}
807			/^@/		{next}
808			/^\.\//		{print $0; next}
809			/./		{print "./" $0; next}' \
810			<"${PLIST}" >"${spec1}"
811
812		# If metalog was specified, attributes from metalog override
813		# attributes in the file system.  We also fake up an
814		# entry for the ./etc/mtree/set.${pkgset} file.
815		{
816			if [ -n "${metalog}" ]; then
817				${AWK} -f "${rundir}/join.awk" \
818					"${spec1}" "${metalog}"
819				${AWK} -f "${rundir}/join.awk" \
820					"${spec1}" /dev/stdin <<EOF
821./etc/mtree/set.${pkgset} type=file mode=0444 uname=root gname=wheel
822EOF
823			else
824				cat "${spec1}"
825			fi
826		} >"${spec2}"
827
828		#
829		# If a file or directory to was mentioned explicitly
830		# in ${PLIST} but not mentioned in ${metalog}, then the
831		# file or directory will not be mentioned in ${spec2}.
832		# This is an error, and means that the metalog was
833		# not built correctly.
834		#
835		if [ -n "${metalog}" ]; then
836			names1="${SCRATCH}/names1"
837			names2="${SCRATCH}/names2"
838			${AWK} '{print $1}' <"${spec1}" | ${SORT} >"${names1}"
839			${AWK} '{print $1}' <"${spec2}" | ${SORT} >"${names2}"
840			if ${FGREP} -v -f "${names2}" "${spec1}" >/dev/null
841			then
842				cat >&2 <<EOM
843${ERRWARN}The metalog file (${metalog}) does not
844	contain entries for the following files or directories
845	which should be part of the ${pkg} syspkg:
846EOM
847				${FGREP} -v -f "${names2}" "${spec1}" >&2
848				${force} || bomb
849			fi
850			if ${FGREP} -v -f "${names1}" "${spec2}" >/dev/null
851			then
852				cat >&2 <<EOM
853${ERRWARN}The following lines are in the metalog file
854	(${metalog}), and the corresponding files or directories
855	should be in the ${pkg} syspkg, but something is wrong:
856EOM
857				${FGREP} -v -f "${names1}" "${spec2}" >&2
858				bomb
859			fi
860		fi
861
862		# Add lines (tagged "optional") for any implicit directories.
863		#
864		# For example, if we have a file ./foo/bar/baz, then we add
865		# "./foo/bar optional type=dir", "./foo optional type=dir",
866		# and ". optional type=dir", unless those directories were
867		# already mentioned explicitly.
868		#
869		${AWK} -f "${rundir}/getdirs.awk" "${spec2}" \
870		| ${SORT} -u >"${specfile_payload}"
871	}
872
873	# Use two pax invocations followed by gzip to create
874	# the tgz file.
875	#
876	# Remove any leading "./" from path names, because that
877	# could confuse tools that work with binary packages.
878	(
879		cd "${SYSPKG_DB_SUBDIR}" && \
880		${PAX} -O -w -d -N"${etcdir}" -M '-s,^\./,,' \
881			-f "${tarball_uncompressed}" \
882			<"${specfile_overhead}" \
883		|| bomb
884	)
885	(
886		cd "${DESTDIR:-/}" && \
887		${PAX} -O -w -d -N"${etcdir}" -M '-s,^\./,,' \
888			-a -f "${tarball_uncompressed}" \
889			<"${specfile_payload}" \
890		|| bomb
891	)
892	${GZIP_CMD} -9n <"${tarball_uncompressed}" >"${binpkgfile}" || bomb
893
894	# (Extra space is to make message line up with "Registered" message.)
895	if ${verbose}; then
896		echo "  Packaged ${binpkgfile}"
897	elif ! ${quiet}; then
898		echo "  Packaged ${binpkgfile##*/}"
899	fi
900
901	cleanup_must_delete_binpkgfile=false
902
903}
904
905#
906# do_register_syspkg() registers the syspkg if appropriate.
907#
908# If SYSPKG_DB_SUBDIR already exists, that might be an error, depending
909# on ${force} and ${update} flags.
910#
911do_register_syspkg()
912{
913	# Check that necessary variables are defined
914	[ -n "${SYSPKG_DB_TOPDIR}" ] || bomb
915	[ -n "${SYSPKG_DB_SUBDIR}" ] || bomb
916
917	# Create SYSPKG_DB_TOPDIR if necessary
918	[ -d "${SYSPKG_DB_TOPDIR}" ] || mkdir -p "${SYSPKG_DB_TOPDIR}" || bomb
919
920	# A function to delete db entries referring to any version of ${pkg}
921	delete_old_db_entries()
922	{
923		init_db_opts # sets dbfile, dbtype, and db_opts
924		dblist="${SCRATCH}/dblist"
925		${DB} ${db_opts} -O "${tab}" "${dbtype}" "${dbfile}" \
926		| ${AWK} -F "${tab}" '$2 ~ /^'"${pkg}"'-[0-9]/ { print $1 }' \
927			>"${dblist}"
928		${DB} -d ${db_opts} -f "${dblist}" "${dbtype}" "${dbfile}"
929	}
930
931	# A function to delete any old version of ${pkg}
932	delete_old_pkg()
933	{
934		pattern="${pkg}-[0-9]*"
935		matches="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pattern} \
936			|| bomb)"
937		echo >&2 "${NOTE}deleting old pkg (${matches})"
938		cleanup_must_delete_dbsubdir=true
939		delete_old_db_entries
940		( cd "${SYSPKG_DB_TOPDIR}" && rm -rf ${matches} )
941	}
942
943	# Check whether another version of ${pkg} is already registered.
944	pattern="${pkg}-[0-9]*"
945	matches="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pattern} || bomb)"
946	case "${matches}" in
947	*\*)		;;	# wildcard did not match anything
948	"${pkg}-${t}")	;;	# exact match
949	*)	echo >&2 "${ERRWARNNOTE}another version of ${pkg} is already registered"
950		${verbose} && echo >&2 "	in ${SYSPKG_DB_TOPDIR}"
951		${verbose} && echo >&2 "	(while registering ${pkg}-${t})"
952		${force} || ${update} || bomb
953		delete_old_pkg
954		;;
955	esac
956
957	# Check whether the desired version of ${pkg} is already registered,
958	# and create it if appropriate.
959	if [ -d "${SYSPKG_DB_SUBDIR}" ]; then
960		echo >&2 "${ERRWARNNOTE}${pkg}-${t} is already registered"
961		${verbose} && echo >&2 "	in ${SYSPKG_DB_TOPDIR}"
962		if ${force}; then
963			delete_old_pkg
964			register_syspkg
965		elif ${update}; then
966			#
967			# If all files in SYSPKG_DB_SUBDIR are newer
968			# than all files in the pkg, then do nothing.
969			# Else delete and re-register the pkg.
970			#
971			[ -n "${newestfile}" ] || init_newestfile
972			if [ -n "${newestfile}" ]; then
973				case "$(${FIND} "${SYSPKG_DB_SUBDIR}" -type f \
974					! -newer "${newestfile}" -print)" \
975				in
976				"")	;;
977				*)
978					echo >&2 "${NOTE}some files are newer but pkg version is unchanged"
979					delete_old_pkg
980					register_syspkg
981					;;
982				esac
983
984			else
985				# No files in the pkg?  (This could happen
986				# if a pkg contains only directories.)
987				# Do nothing (keep the already-registered pkg).
988			fi
989		else
990			bomb
991		fi
992	else
993		register_syspkg
994	fi
995}
996
997#
998# do_create_syspkg_tgz() creates the the binary pkg (*.tgz) if
999# appropriate.
1000#
1001# If binpkgfile already exists, that might be an error, depending on
1002# ${force} and ${update} flags.
1003#
1004do_create_syspkg_tgz()
1005{
1006	[ -n "${binpkgfile}" ] || bomb
1007
1008	delete_and_recreate()
1009	{
1010		echo >&2 "${ERRWARNNOTE}deleting and re-creating ${pkg}-${t}.tgz"
1011		rm -f "${binpkgfile}"
1012		create_syspkg_tgz
1013	}
1014
1015	# Check whether another version of ${pkg} already exists.
1016	pattern="${pkg}-[0-9]*"
1017	matches="$(cd "${binpkgdir}" && echo ${pattern} || bomb)"
1018	case "${matches}" in
1019	*\*)	;;	# wildcard did not match anything
1020	"${pkg}-${t}.tgz") ;;	# exact match
1021	*)	echo >&2 "${ERRWARNNOTE}another version of ${pkg} binary pkg already exists"
1022		${verbose} && echo >&2 "	in ${binpkgdir}"
1023		${verbose} && echo >&2 "	(while creating ${pkg}-${t}.tgz)"
1024		# If neither force nor update, this is a fatal error.
1025		# If force but not update, then leave old .tgz in place.
1026		# If update, then delete the old .tgz.
1027		${force} || ${update} || bomb
1028		if ${update}; then
1029			echo >&2 "${NOTE}deleting old binary pkg (${matches})"
1030			( cd "${binpkgdir}" && rm -f ${matches} || bomb )
1031		fi
1032		;;
1033	esac
1034
1035	# Check whether the desired version of ${pkg} already exists,
1036	# and create it if appropriate.
1037	if [ -e "${binpkgfile}" ]; then
1038		echo >&2 "${ERRWARNNOTE}${pkg}-${t}.tgz already exists"
1039		${verbose} && echo >&2 "	in ${binpkgdir}"
1040		if ${force}; then
1041			delete_and_recreate
1042		elif ${update}; then
1043			#
1044			# If all files in SYSPKG_DB_SUBDIR are older
1045			# than ${binpkgfile}, then do nothing.
1046			# Else delete and re-create the tgz.
1047			#
1048			case "$(${FIND} "${SYSPKG_DB_SUBDIR}" -type f \
1049				-newer "${binpkgfile}" -print)" \
1050			in
1051			"")	;;
1052			*)	delete_and_recreate ;;
1053			esac
1054		else
1055			bomb
1056		fi
1057	else
1058		create_syspkg_tgz
1059	fi
1060}
1061
1062####################
1063# begin main program
1064
1065parse_args ${1+"$@"}
1066make_PLIST
1067choose_version_number
1068SYSPKG_DB_SUBDIR="${SYSPKG_DB_TOPDIR}/${pkg}-${t}"
1069do_register_syspkg
1070if [ -n "${binpkgdir}" ]; then
1071	binpkgfile="${binpkgdir}/${pkg}-${t}.tgz"
1072	do_create_syspkg_tgz
1073fi
1074
1075exit 0
1076