1#! /bin/sh
2#
3# $NetBSD: regpkg,v 1.26 2023/02/11 04:16:57 uki 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                    /^@pkgdir/ { 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} '/^@pkgdir/ { print $2 }' <"${PLIST}" | \
443          ${SORT} | \
444          while read dir; do
445                    # Sanitise the name. ${dir} could be an absolute or
446                    # relative name, with or without a leading "./".
447                    # ${dot_slash_dir} always has a leading "./" (except when
448                    # it's exactly equal to "."). ${no_dot_dir} never has a
449                    # leading "." or "/" (except when it's exactly equal to
450                    # ".").
451                    case "${dir}" in
452                    .|./|/)   dot_slash_dir=.  ;;
453                    ./*)      dot_slash_dir="${dir}" ;;
454                    /*)       dot_slash_dir=".${dir}" ;;
455                    *)        dot_slash_dir="./${dir}" ;;
456                    esac
457                    no_dot_dir="${dot_slash_dir#./}"
458                    # Get the directory's owner, group, and mode
459                    # from the live file system, or let it be overridden
460                    # by the metalog.
461                    eval "$(${STAT} -f 'uname=%Su gname=%Sg mode=%#OLp' \
462                                        "${DESTDIR}/${dot_slash_dir}")"
463                    if [ -n "${metalog}" ]; then
464                              line="$(echo "${dot_slash_dir}" | \
465                                        ${AWK} -f "${rundir}/join.awk" \
466                                                  /dev/stdin "${metalog}")"
467                              for word in ${line}; do
468                                        case "${word}" in
469                                        uname=*|gname=*|mode=*)       eval "${word}" ;;
470                                        esac
471                              done
472                    fi
473                    # XXX: Work around yet another pkg_add bug: @cwd lines
474                    # do not actually cause the working directory to change,
475                    # so file names in @exec lines need to be qualified by
476                    # %D, which (in our case, since we know there's an
477                    # "@cwd /" line) will be the dir name passed to
478                    # "pkg_add -p PREFIX".
479                    case "${no_dot_dir}" in
480                    .) d="%D" ;;
481                    *) d="%D/${no_dot_dir}" ;;
482                    esac
483                    cat <<EOF
484@exec install -d -o ${uname} -g ${gname} -m ${mode} ${d}
485EOF
486          done
487}
488
489#
490# register_syspkg() registers the syspkg in ${SYSPKG_DB_TOPDIR}.
491# This involves creating the subdirectory ${SYSPKG_DB_SUBDIR}
492# and populating it with several files.
493#
494register_syspkg()
495{
496          cleanup_must_delete_dbsubdir=true
497          [ -n "${SYSPKG_DB_SUBDIR}" ] || bomb
498          mkdir -p "${SYSPKG_DB_SUBDIR}"
499
500          #
501          # Guess what versions of other packages to depend on.
502          #
503          # If we are using the OS version as part of the pkg
504          # version, then depend on any version ">=${osvers}".  For
505          # example, etc-sys-etc-1.6ZI.0.20040206 might depend on
506          # base-sys-root>=1.6ZI.
507          #
508          # Failing that, depend on any version "-[0-9]*".
509          #
510          # XXX: We could extend the format of the "deps" file to carry
511          # this sort of information, so we wouldn't have to guess.
512          #
513          case "${t}" in
514          ${osvers}.*)        depversion=">=${osvers}" ;;
515          *)                  depversion="-[0-9]*" ;;
516          esac
517
518          #
519          # Add the dependencies.
520          #
521          # We always add a "@pkgdep" line for each prerequisite package.
522          #
523          # If the prerequisite pkg is already registered (as it should be
524          # if our caller is doing things in the right order), then we put
525          # its exact version number in a "@blddep" line.
526          #
527          ${AWK} '$1 ~ '/"${pkg}"/' { print $2 }' "${rundir}/deps" | ${SORT} | \
528          while read depname; do
529                    # ${pkgdepglob} is a shell glob pattern that should match
530                    # any version of a pkg.  ${pkgdep} uses the special syntax
531                    # for pkg dependencies, and is not usable as a shell
532                    # glob pattern.
533                    pkgdepglob="${depname}-[0-9]*"
534                    pkgdep="${depname}${depversion}"
535                    echo "@pkgdep ${pkgdep}"
536                    blddep="$(cd "${SYSPKG_DB_TOPDIR}" && echo ${pkgdepglob} \
537                              || bomb)"
538                    case "${blddep}" in
539                    *\*)      # pkgdepglob did not match anything
540                              echo >&2 "${WARNING}${pkg} depends on '${pkgdep}' but there is no matching syspkg in ${SYSPKG_DB_TOPDIR}"
541                              ;;
542                    *\ *)     # pkgdepglob matched more than once.
543                              echo >&2 "${ERRWARN}${pkg} depends on '${pkgdep}' but there are multiple matching syspkgs in ${SYSPKG_DB_TOPDIR}"
544                              ${force} || bomb
545                              # If ${force} is set, then assume that the last
546                              # match is the most recent.
547                              # XXX: This might be wrong, because of
548                              # differences between lexical sorting and
549                              # numeric sorting.
550                              lastmatch="${blddep##* }"
551                              echo "@blddep ${lastmatch}"
552                              ;;
553                    *)        # exactly one match.
554                              # XXX: We ignore the possibility that the
555                              # version we found via ${pkgdepglob} might not
556                              # satisfy ${pkgdep}.  We could conceivably use
557                              # "pkg_admin pmatch" to check, but that's not a
558                              # host tool so we can't assume that it will be
559                              # available.
560                              echo "@blddep ${blddep}"
561                              ;;
562                    esac
563          done >>"${PLIST}"
564
565          # create the comment (should be one line)
566          comment="$(${AWK} '$1 ~ '/"${pkg}"/' \
567                              { print substr($0, length($1) + 2) }' \
568                              "${rundir}/comments")"
569          case "${comment}" in
570          "")       echo >&2 "${WARNING}no comment for \"${pkg}\" (using placeholder)"
571                    comment="System package for ${pkg}"
572                    ;;
573          *"${nl}"*)
574                    echo >&2 "${ERRWARN}multi-line comment for \"${pkg}\""
575                    ${force} || bomb
576                    ;;
577          esac
578          echo "${comment}" > "${SYSPKG_DB_SUBDIR}/+COMMENT"
579
580          # create the description (could be multiple lines)
581          descr="$(${AWK} '$1 ~ '/"${pkg}"/' {
582                              print substr($0, length($1) + 2) }' \
583                              "${rundir}/descrs")"
584          case "${descr}" in
585          "")       echo >&2 "${WARNING}no description for \"${pkg}\" (re-using comment)" 2>&1
586                    descr="${comment}"
587                    ;;
588          esac
589          echo "${descr}" > "${SYSPKG_DB_SUBDIR}/+DESC"
590          ${PRINTF} "\nHomepage:\nhttp://www.NetBSD.org/\n" >> "${SYSPKG_DB_SUBDIR}/+DESC"
591
592          # create the build information
593          if [ x"${BUILD_INFO_CACHE}" = x ]; then
594                    {
595                    # These variables describe the build
596                    # environment, not the target.
597                    echo "OPSYS=$(${UNAME} -s)"
598                    echo "OS_VERSION=$(${UNAME} -r)"
599                    ${MAKE} -B -f- all <<EOF
600.include <bsd.own.mk>
601all:
602          @echo OBJECT_FMT=${OBJECT_FMT}
603          @echo MACHINE_ARCH=${MACHINE_ARCH}
604          @echo MACHINE_GNU_ARCH=${MACHINE_GNU_ARCH}
605EOF
606                    } > "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
607          else
608                    cp "${BUILD_INFO_CACHE}" "${SYSPKG_DB_SUBDIR}/+BUILD_INFO"
609          fi
610
611          # test for attributes
612          args=""
613          attrs="$(${AWK} '$1 ~ '/"${pkg}"/' { \
614                              print substr($0, length($1) + 2) }' \
615                    "${rundir}/attrs")"
616          for a in "${attrs}"; do
617                    case "${attrs}" in
618                    "")       ;;
619                    preserve)
620                              echo "${pkg}-${t}" >"${SYSPKG_DB_SUBDIR}/+PRESERVE"
621                              args="${args} -n ${SYSPKG_DB_SUBDIR}/+PRESERVE"
622                              ;;
623                    esac
624          done
625
626          #
627          # Create ${SYSPKGSIR}/+CONTENTS from ${PLIST}, by adding an
628          # "@name" line and a lot of "@comment MD5:" lines.
629          #
630          {
631                    rcsid='$NetBSD: regpkg,v 1.26 2023/02/11 04:16:57 uki Exp $'
632                    utcdate="$(${ENV_CMD} TZ=UTC LOCALE=C \
633                              ${DATE} '+%Y-%m-%d %H:%M')"
634                    user="${USER:-root}"
635                    host="$(${HOSTNAME_CMD})"
636                    echo "@name ${pkg}-${t}"
637                    echo "@comment Packaged at ${utcdate} UTC by ${user}@${host}"
638                    echo "@comment Packaged using ${prog} ${rcsid}"
639                    # XXX: "option extract-in-place" might help to get
640                    #         pkg_add to create directories.
641                    # XXX: no, it doesn't work.  Yet another pkg_add bug.
642                    ## echo "@option extract-in-place"
643                    # Move the @pkgdep and @blddep lines up, so that
644                    # they are easy to see when people do "less
645                    # ${DESTDIR}/var/db/syspkg/*/+CONTENTS".
646                    ${EGREP} '^(@pkgdep|@blddep)' "${PLIST}" || true
647                    # Now do the remainder of the file.
648                    while read line; do
649                              case "${line}" in
650                              @pkgdep*|@blddep*)
651                                        # already handled by grep above
652                                        ;;
653                              @cwd*)
654                                        # There should be exactly one @cwd line.
655                                        # Just after it, add an "@exec mkdir"
656                                        # line for every directory.  This is to
657                                        # work around a pkg-add bug (see
658                                        # <http://mail-index.NetBSD.org/tech-pkg/2003/12/11/0018.html>)
659                                        echo "${line}"
660                                        print_dir_exec_lines
661                                        ;;
662                              @*)
663                                        # just pass through all other @foo lines
664                                        echo "${line}"
665                                        ;;
666                              *)
667                                        # This should be a file name.  Pass it
668                                        # through, and append "@comment SHA256:".
669                                        echo "${line}"
670                                        file="${DESTDIR}/${line}"
671                                        if [ -f "${file}" ] && [ -r "${file}" ];
672                                        then
673                                                  sha256sum="$(${CKSUM} -a sha256 -n "${file}" \
674                                                               | ${AWK} '{print $1}'
675                                                            )"
676                                                  echo "@comment SHA256:${sha256sum}"
677                                        fi
678                                        ;;
679                              esac
680                    done <"${PLIST}"
681          } >"${SYSPKG_DB_SUBDIR}/+CONTENTS"
682
683          #
684          #  Update ${SYSPKG_DB_TOPDIR}/pkgdb.byfile.db.
685          #
686          {
687                    init_db_opts # sets dbfile, dbtype, and db_opts
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                    # "@pkgdir ."                 -> "/"
694                    # "@pkgdir foo/bar" -> "/foo/bar"
695                    # "@pkgdir ./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} '/^@pkgdir \.\//       {gsub("^.", "", $2); print $2; next}
701                              /^@pkgdir \.$/      {print "/"; next}
702                              /^@pkgdir/          {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                    # "@pkgdir ."                 -> ". type=dir"
792                    # "@pkgdir foo/bar" -> "./foo/bar type=dir"
793                    # "@pkgdir ./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} '/^@pkgdir \.\//       {print $2 " type=dir"; next}
800                              /^@pkgdir \.$/      {print ". type=dir"; next}
801                              /^@pkgdir/          {print "./" $2 " type=dir"; next}
802                              /^@/                {next}
803                              /^\.\//             {print $0; next}
804                              /./                 {print "./" $0; next}' \
805                              <"${PLIST}" |
806                    # Escape some characters to match the new mtree(8) format.
807                    # C.f. usr.sbin/mtree/spec.c:vispath()
808                    # XXX escape only '[' for now
809                    ${SED} -e 's,\[,\\133,g' \
810                    >"${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                                        :
989                              fi
990                    else
991                              bomb
992                    fi
993          else
994                    register_syspkg
995          fi
996}
997
998#
999# do_create_syspkg_tgz() creates the binary pkg (*.tgz) if
1000# appropriate.
1001#
1002# If binpkgfile already exists, that might be an error, depending on
1003# ${force} and ${update} flags.
1004#
1005do_create_syspkg_tgz()
1006{
1007          [ -n "${binpkgfile}" ] || bomb
1008
1009          delete_and_recreate()
1010          {
1011                    echo >&2 "${ERRWARNNOTE}deleting and re-creating ${pkg}-${t}.tgz"
1012                    rm -f "${binpkgfile}"
1013                    create_syspkg_tgz
1014          }
1015
1016          # Check whether another version of ${pkg} already exists.
1017          pattern="${pkg}-[0-9]*"
1018          matches="$(cd "${binpkgdir}" && echo ${pattern} || bomb)"
1019          case "${matches}" in
1020          *\*)      ;;        # wildcard did not match anything
1021          "${pkg}-${t}.tgz") ;;         # exact match
1022          *)        echo >&2 "${ERRWARNNOTE}another version of ${pkg} binary pkg already exists"
1023                    ${verbose} && echo >&2 "      in ${binpkgdir}"
1024                    ${verbose} && echo >&2 "      (while creating ${pkg}-${t}.tgz)"
1025                    # If neither force nor update, this is a fatal error.
1026                    # If force but not update, then leave old .tgz in place.
1027                    # If update, then delete the old .tgz.
1028                    ${force} || ${update} || bomb
1029                    if ${update}; then
1030                              echo >&2 "${NOTE}deleting old binary pkg (${matches})"
1031                              ( cd "${binpkgdir}" && rm -f ${matches} || bomb )
1032                    fi
1033                    ;;
1034          esac
1035
1036          # Check whether the desired version of ${pkg} already exists,
1037          # and create it if appropriate.
1038          if [ -e "${binpkgfile}" ]; then
1039                    echo >&2 "${ERRWARNNOTE}${pkg}-${t}.tgz already exists"
1040                    ${verbose} && echo >&2 "      in ${binpkgdir}"
1041                    if ${force}; then
1042                              delete_and_recreate
1043                    elif ${update}; then
1044                              #
1045                              # If all files in SYSPKG_DB_SUBDIR are older
1046                              # than ${binpkgfile}, then do nothing.
1047                              # Else delete and re-create the tgz.
1048                              #
1049                              case "$(${FIND} "${SYSPKG_DB_SUBDIR}" -type f \
1050                                        -newer "${binpkgfile}" -print)" \
1051                              in
1052                              "")       ;;
1053                              *)        delete_and_recreate ;;
1054                              esac
1055                    else
1056                              bomb
1057                    fi
1058          else
1059                    create_syspkg_tgz
1060          fi
1061}
1062
1063####################
1064# begin main program
1065
1066parse_args ${1+"$@"}
1067make_PLIST
1068choose_version_number
1069SYSPKG_DB_SUBDIR="${SYSPKG_DB_TOPDIR}/${pkg}-${t}"
1070do_register_syspkg
1071if [ -n "${binpkgdir}" ]; then
1072          binpkgfile="${binpkgdir}/${pkg}-${t}.tgz"
1073          do_create_syspkg_tgz
1074fi
1075
1076exit 0
1077