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