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