]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/freebsd-update/freebsd-update.sh
MFC r343270: freebsd-update: Update /etc/passwd after password db changes
[FreeBSD/FreeBSD.git] / usr.sbin / freebsd-update / freebsd-update.sh
1 #!/bin/sh
2
3 #-
4 # SPDX-License-Identifier: BSD-2-Clause-FreeBSD
5 #
6 # Copyright 2004-2007 Colin Percival
7 # All rights reserved
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted providing that the following conditions 
11 # are met:
12 # 1. Redistributions of source code must retain the above copyright
13 #    notice, this list of conditions and the following disclaimer.
14 # 2. Redistributions in binary form must reproduce the above copyright
15 #    notice, this list of conditions and the following disclaimer in the
16 #    documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 # ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
27 # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 # POSSIBILITY OF SUCH DAMAGE.
29
30 # $FreeBSD$
31
32 #### Usage function -- called from command-line handling code.
33
34 # Usage instructions.  Options not listed:
35 # --debug       -- don't filter output from utilities
36 # --no-stats    -- don't show progress statistics while fetching files
37 usage () {
38         cat <<EOF
39 usage: `basename $0` [options] command ... [path]
40
41 Options:
42   -b basedir   -- Operate on a system mounted at basedir
43                   (default: /)
44   -d workdir   -- Store working files in workdir
45                   (default: /var/db/freebsd-update/)
46   -f conffile  -- Read configuration options from conffile
47                   (default: /etc/freebsd-update.conf)
48   -F           -- Force a fetch operation to proceed
49   -k KEY       -- Trust an RSA key with SHA256 hash of KEY
50   -r release   -- Target for upgrade (e.g., 11.1-RELEASE)
51   -s server    -- Server from which to fetch updates
52                   (default: update.FreeBSD.org)
53   -t address   -- Mail output of cron command, if any, to address
54                   (default: root)
55   --not-running-from-cron
56                -- Run without a tty, for use by automated tools
57   --currently-running release
58                -- Update as if currently running this release
59 Commands:
60   fetch        -- Fetch updates from server
61   cron         -- Sleep rand(3600) seconds, fetch updates, and send an
62                   email if updates were found
63   upgrade      -- Fetch upgrades to FreeBSD version specified via -r option
64   install      -- Install downloaded updates or upgrades
65   rollback     -- Uninstall most recently installed updates
66   IDS          -- Compare the system against an index of "known good" files.
67 EOF
68         exit 0
69 }
70
71 #### Configuration processing functions
72
73 #-
74 # Configuration options are set in the following order of priority:
75 # 1. Command line options
76 # 2. Configuration file options
77 # 3. Default options
78 # In addition, certain options (e.g., IgnorePaths) can be specified multiple
79 # times and (as long as these are all in the same place, e.g., inside the
80 # configuration file) they will accumulate.  Finally, because the path to the
81 # configuration file can be specified at the command line, the entire command
82 # line must be processed before we start reading the configuration file.
83 #
84 # Sound like a mess?  It is.  Here's how we handle this:
85 # 1. Initialize CONFFILE and all the options to "".
86 # 2. Process the command line.  Throw an error if a non-accumulating option
87 #    is specified twice.
88 # 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
89 # 4. For all the configuration options X, set X_saved to X.
90 # 5. Initialize all the options to "".
91 # 6. Read CONFFILE line by line, parsing options.
92 # 7. For each configuration option X, set X to X_saved iff X_saved is not "".
93 # 8. Repeat steps 4-7, except setting options to their default values at (6).
94
95 CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
96     KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
97     BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
98     IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
99
100 # Set all the configuration options to "".
101 nullconfig () {
102         for X in ${CONFIGOPTIONS}; do
103                 eval ${X}=""
104         done
105 }
106
107 # For each configuration option X, set X_saved to X.
108 saveconfig () {
109         for X in ${CONFIGOPTIONS}; do
110                 eval ${X}_saved=\$${X}
111         done
112 }
113
114 # For each configuration option X, set X to X_saved if X_saved is not "".
115 mergeconfig () {
116         for X in ${CONFIGOPTIONS}; do
117                 eval _=\$${X}_saved
118                 if ! [ -z "${_}" ]; then
119                         eval ${X}=\$${X}_saved
120                 fi
121         done
122 }
123
124 # Set the trusted keyprint.
125 config_KeyPrint () {
126         if [ -z ${KEYPRINT} ]; then
127                 KEYPRINT=$1
128         else
129                 return 1
130         fi
131 }
132
133 # Set the working directory.
134 config_WorkDir () {
135         if [ -z ${WORKDIR} ]; then
136                 WORKDIR=$1
137         else
138                 return 1
139         fi
140 }
141
142 # Set the name of the server (pool) from which to fetch updates
143 config_ServerName () {
144         if [ -z ${SERVERNAME} ]; then
145                 SERVERNAME=$1
146         else
147                 return 1
148         fi
149 }
150
151 # Set the address to which 'cron' output will be mailed.
152 config_MailTo () {
153         if [ -z ${MAILTO} ]; then
154                 MAILTO=$1
155         else
156                 return 1
157         fi
158 }
159
160 # Set whether FreeBSD Update is allowed to add files (or directories, or
161 # symlinks) which did not previously exist.
162 config_AllowAdd () {
163         if [ -z ${ALLOWADD} ]; then
164                 case $1 in
165                 [Yy][Ee][Ss])
166                         ALLOWADD=yes
167                         ;;
168                 [Nn][Oo])
169                         ALLOWADD=no
170                         ;;
171                 *)
172                         return 1
173                         ;;
174                 esac
175         else
176                 return 1
177         fi
178 }
179
180 # Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
181 config_AllowDelete () {
182         if [ -z ${ALLOWDELETE} ]; then
183                 case $1 in
184                 [Yy][Ee][Ss])
185                         ALLOWDELETE=yes
186                         ;;
187                 [Nn][Oo])
188                         ALLOWDELETE=no
189                         ;;
190                 *)
191                         return 1
192                         ;;
193                 esac
194         else
195                 return 1
196         fi
197 }
198
199 # Set whether FreeBSD Update should keep existing inode ownership,
200 # permissions, and flags, in the event that they have been modified locally
201 # after the release.
202 config_KeepModifiedMetadata () {
203         if [ -z ${KEEPMODIFIEDMETADATA} ]; then
204                 case $1 in
205                 [Yy][Ee][Ss])
206                         KEEPMODIFIEDMETADATA=yes
207                         ;;
208                 [Nn][Oo])
209                         KEEPMODIFIEDMETADATA=no
210                         ;;
211                 *)
212                         return 1
213                         ;;
214                 esac
215         else
216                 return 1
217         fi
218 }
219
220 # Add to the list of components which should be kept updated.
221 config_Components () {
222         for C in $@; do
223                 if [ "$C" = "src" ]; then
224                         if [ -e /usr/src/COPYRIGHT ]; then
225                                 COMPONENTS="${COMPONENTS} ${C}"
226                         else
227                                 echo "src component not installed, skipped"
228                         fi
229                 else
230                         COMPONENTS="${COMPONENTS} ${C}"
231                 fi
232         done
233 }
234
235 # Add to the list of paths under which updates will be ignored.
236 config_IgnorePaths () {
237         for C in $@; do
238                 IGNOREPATHS="${IGNOREPATHS} ${C}"
239         done
240 }
241
242 # Add to the list of paths which IDS should ignore.
243 config_IDSIgnorePaths () {
244         for C in $@; do
245                 IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}"
246         done
247 }
248
249 # Add to the list of paths within which updates will be performed only if the
250 # file on disk has not been modified locally.
251 config_UpdateIfUnmodified () {
252         for C in $@; do
253                 UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
254         done
255 }
256
257 # Add to the list of paths within which updates to text files will be merged
258 # instead of overwritten.
259 config_MergeChanges () {
260         for C in $@; do
261                 MERGECHANGES="${MERGECHANGES} ${C}"
262         done
263 }
264
265 # Work on a FreeBSD installation mounted under $1
266 config_BaseDir () {
267         if [ -z ${BASEDIR} ]; then
268                 BASEDIR=$1
269         else
270                 return 1
271         fi
272 }
273
274 # When fetching upgrades, should we assume the user wants exactly the
275 # components listed in COMPONENTS, rather than trying to guess based on
276 # what's currently installed?
277 config_StrictComponents () {
278         if [ -z ${STRICTCOMPONENTS} ]; then
279                 case $1 in
280                 [Yy][Ee][Ss])
281                         STRICTCOMPONENTS=yes
282                         ;;
283                 [Nn][Oo])
284                         STRICTCOMPONENTS=no
285                         ;;
286                 *)
287                         return 1
288                         ;;
289                 esac
290         else
291                 return 1
292         fi
293 }
294
295 # Upgrade to FreeBSD $1
296 config_TargetRelease () {
297         if [ -z ${TARGETRELEASE} ]; then
298                 TARGETRELEASE=$1
299         else
300                 return 1
301         fi
302         if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
303                 TARGETRELEASE="${TARGETRELEASE}-RELEASE"
304         fi
305 }
306
307 # Define what happens to output of utilities
308 config_VerboseLevel () {
309         if [ -z ${VERBOSELEVEL} ]; then
310                 case $1 in
311                 [Dd][Ee][Bb][Uu][Gg])
312                         VERBOSELEVEL=debug
313                         ;;
314                 [Nn][Oo][Ss][Tt][Aa][Tt][Ss])
315                         VERBOSELEVEL=nostats
316                         ;;
317                 [Ss][Tt][Aa][Tt][Ss])
318                         VERBOSELEVEL=stats
319                         ;;
320                 *)
321                         return 1
322                         ;;
323                 esac
324         else
325                 return 1
326         fi
327 }
328
329 config_BackupKernel () {
330         if [ -z ${BACKUPKERNEL} ]; then
331                 case $1 in
332                 [Yy][Ee][Ss])
333                         BACKUPKERNEL=yes
334                         ;;
335                 [Nn][Oo])
336                         BACKUPKERNEL=no
337                         ;;
338                 *)
339                         return 1
340                         ;;
341                 esac
342         else
343                 return 1
344         fi
345 }
346
347 config_BackupKernelDir () {
348         if [ -z ${BACKUPKERNELDIR} ]; then
349                 if [ -z "$1" ]; then
350                         echo "BackupKernelDir set to empty dir"
351                         return 1
352                 fi
353
354                 # We check for some paths which would be extremely odd
355                 # to use, but which could cause a lot of problems if
356                 # used.
357                 case $1 in
358                 /|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
359                         echo "BackupKernelDir set to invalid path $1"
360                         return 1
361                         ;;
362                 /*)
363                         BACKUPKERNELDIR=$1
364                         ;;
365                 *)
366                         echo "BackupKernelDir ($1) is not an absolute path"
367                         return 1
368                         ;;
369                 esac
370         else
371                 return 1
372         fi
373 }
374
375 config_BackupKernelSymbolFiles () {
376         if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
377                 case $1 in
378                 [Yy][Ee][Ss])
379                         BACKUPKERNELSYMBOLFILES=yes
380                         ;;
381                 [Nn][Oo])
382                         BACKUPKERNELSYMBOLFILES=no
383                         ;;
384                 *)
385                         return 1
386                         ;;
387                 esac
388         else
389                 return 1
390         fi
391 }
392
393 # Handle one line of configuration
394 configline () {
395         if [ $# -eq 0 ]; then
396                 return
397         fi
398
399         OPT=$1
400         shift
401         config_${OPT} $@
402 }
403
404 #### Parameter handling functions.
405
406 # Initialize parameters to null, just in case they're
407 # set in the environment.
408 init_params () {
409         # Configration settings
410         nullconfig
411
412         # No configuration file set yet
413         CONFFILE=""
414
415         # No commands specified yet
416         COMMANDS=""
417
418         # Force fetch to proceed
419         FORCEFETCH=0
420
421         # Run without a TTY
422         NOTTYOK=0
423
424         # Fetched first in a chain of commands
425         ISFETCHED=0
426 }
427
428 # Parse the command line
429 parse_cmdline () {
430         while [ $# -gt 0 ]; do
431                 case "$1" in
432                 # Location of configuration file
433                 -f)
434                         if [ $# -eq 1 ]; then usage; fi
435                         if [ ! -z "${CONFFILE}" ]; then usage; fi
436                         shift; CONFFILE="$1"
437                         ;;
438                 -F)
439                         FORCEFETCH=1
440                         ;;
441                 --not-running-from-cron)
442                         NOTTYOK=1
443                         ;;
444                 --currently-running)
445                         shift; export UNAME_r="$1"
446                         ;;
447
448                 # Configuration file equivalents
449                 -b)
450                         if [ $# -eq 1 ]; then usage; fi; shift
451                         config_BaseDir $1 || usage
452                         ;;
453                 -d)
454                         if [ $# -eq 1 ]; then usage; fi; shift
455                         config_WorkDir $1 || usage
456                         ;;
457                 -k)
458                         if [ $# -eq 1 ]; then usage; fi; shift
459                         config_KeyPrint $1 || usage
460                         ;;
461                 -s)
462                         if [ $# -eq 1 ]; then usage; fi; shift
463                         config_ServerName $1 || usage
464                         ;;
465                 -r)
466                         if [ $# -eq 1 ]; then usage; fi; shift
467                         config_TargetRelease $1 || usage
468                         ;;
469                 -t)
470                         if [ $# -eq 1 ]; then usage; fi; shift
471                         config_MailTo $1 || usage
472                         ;;
473                 -v)
474                         if [ $# -eq 1 ]; then usage; fi; shift
475                         config_VerboseLevel $1 || usage
476                         ;;
477
478                 # Aliases for "-v debug" and "-v nostats"
479                 --debug)
480                         config_VerboseLevel debug || usage
481                         ;;
482                 --no-stats)
483                         config_VerboseLevel nostats || usage
484                         ;;
485
486                 # Commands
487                 cron | fetch | upgrade | install | rollback | IDS)
488                         COMMANDS="${COMMANDS} $1"
489                         ;;
490
491                 # Anything else is an error
492                 *)
493                         usage
494                         ;;
495                 esac
496                 shift
497         done
498
499         # Make sure we have at least one command
500         if [ -z "${COMMANDS}" ]; then
501                 usage
502         fi
503 }
504
505 # Parse the configuration file
506 parse_conffile () {
507         # If a configuration file was specified on the command line, check
508         # that it exists and is readable.
509         if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
510                 echo -n "File does not exist "
511                 echo -n "or is not readable: "
512                 echo ${CONFFILE}
513                 exit 1
514         fi
515
516         # If a configuration file was not specified on the command line,
517         # use the default configuration file path.  If that default does
518         # not exist, give up looking for any configuration.
519         if [ -z "${CONFFILE}" ]; then
520                 CONFFILE="/etc/freebsd-update.conf"
521                 if [ ! -r "${CONFFILE}" ]; then
522                         return
523                 fi
524         fi
525
526         # Save the configuration options specified on the command line, and
527         # clear all the options in preparation for reading the config file.
528         saveconfig
529         nullconfig
530
531         # Read the configuration file.  Anything after the first '#' is
532         # ignored, and any blank lines are ignored.
533         L=0
534         while read LINE; do
535                 L=$(($L + 1))
536                 LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
537                 if ! configline ${LINEX}; then
538                         echo "Error processing configuration file, line $L:"
539                         echo "==> ${LINE}"
540                         exit 1
541                 fi
542         done < ${CONFFILE}
543
544         # Merge the settings read from the configuration file with those
545         # provided at the command line.
546         mergeconfig
547 }
548
549 # Provide some default parameters
550 default_params () {
551         # Save any parameters already configured, and clear the slate
552         saveconfig
553         nullconfig
554
555         # Default configurations
556         config_WorkDir /var/db/freebsd-update
557         config_MailTo root
558         config_AllowAdd yes
559         config_AllowDelete yes
560         config_KeepModifiedMetadata yes
561         config_BaseDir /
562         config_VerboseLevel stats
563         config_StrictComponents no
564         config_BackupKernel yes
565         config_BackupKernelDir /boot/kernel.old
566         config_BackupKernelSymbolFiles no
567
568         # Merge these defaults into the earlier-configured settings
569         mergeconfig
570 }
571
572 # Set utility output filtering options, based on ${VERBOSELEVEL}
573 fetch_setup_verboselevel () {
574         case ${VERBOSELEVEL} in
575         debug)
576                 QUIETREDIR="/dev/stderr"
577                 QUIETFLAG=" "
578                 STATSREDIR="/dev/stderr"
579                 DDSTATS=".."
580                 XARGST="-t"
581                 NDEBUG=" "
582                 ;;
583         nostats)
584                 QUIETREDIR=""
585                 QUIETFLAG=""
586                 STATSREDIR="/dev/null"
587                 DDSTATS=".."
588                 XARGST=""
589                 NDEBUG=""
590                 ;;
591         stats)
592                 QUIETREDIR="/dev/null"
593                 QUIETFLAG="-q"
594                 STATSREDIR="/dev/stdout"
595                 DDSTATS=""
596                 XARGST=""
597                 NDEBUG="-n"
598                 ;;
599         esac
600 }
601
602 # Perform sanity checks and set some final parameters
603 # in preparation for fetching files.  Figure out which
604 # set of updates should be downloaded: If the user is
605 # running *-p[0-9]+, strip off the last part; if the
606 # user is running -SECURITY, call it -RELEASE.  Chdir
607 # into the working directory.
608 fetchupgrade_check_params () {
609         export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
610
611         _SERVERNAME_z=\
612 "SERVERNAME must be given via command line or configuration file."
613         _KEYPRINT_z="Key must be given via -k option or configuration file."
614         _KEYPRINT_bad="Invalid key fingerprint: "
615         _WORKDIR_bad="Directory does not exist or is not writable: "
616         _WORKDIR_bad2="Directory is not on a persistent filesystem: "
617
618         if [ -z "${SERVERNAME}" ]; then
619                 echo -n "`basename $0`: "
620                 echo "${_SERVERNAME_z}"
621                 exit 1
622         fi
623         if [ -z "${KEYPRINT}" ]; then
624                 echo -n "`basename $0`: "
625                 echo "${_KEYPRINT_z}"
626                 exit 1
627         fi
628         if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
629                 echo -n "`basename $0`: "
630                 echo -n "${_KEYPRINT_bad}"
631                 echo ${KEYPRINT}
632                 exit 1
633         fi
634         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
635                 echo -n "`basename $0`: "
636                 echo -n "${_WORKDIR_bad}"
637                 echo ${WORKDIR}
638                 exit 1
639         fi
640         case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*)
641                 echo -n "`basename $0`: "
642                 echo -n "${_WORKDIR_bad2}"
643                 echo ${WORKDIR}
644                 exit 1
645                 ;;
646         esac
647         chmod 700 ${WORKDIR}
648         cd ${WORKDIR} || exit 1
649
650         # Generate release number.  The s/SECURITY/RELEASE/ bit exists
651         # to provide an upgrade path for FreeBSD Update 1.x users, since
652         # the kernels provided by FreeBSD Update 1.x are always labelled
653         # as X.Y-SECURITY.
654         RELNUM=`uname -r |
655             sed -E 's,-p[0-9]+,,' |
656             sed -E 's,-SECURITY,-RELEASE,'`
657         ARCH=`uname -m`
658         FETCHDIR=${RELNUM}/${ARCH}
659         PATCHDIR=${RELNUM}/${ARCH}/bp
660
661         # Figure out what directory contains the running kernel
662         BOOTFILE=`sysctl -n kern.bootfile`
663         KERNELDIR=${BOOTFILE%/kernel}
664         if ! [ -d ${KERNELDIR} ]; then
665                 echo "Cannot identify running kernel"
666                 exit 1
667         fi
668
669         # Figure out what kernel configuration is running.  We start with
670         # the output of `uname -i`, and then make the following adjustments:
671         # 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
672         # file says "ident SMP-GENERIC", I don't know...
673         # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
674         # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
675         # we're running an SMP kernel.  This mis-identification is a bug
676         # which was fixed in 6.2-STABLE.
677         KERNCONF=`uname -i`
678         if [ ${KERNCONF} = "SMP-GENERIC" ]; then
679                 KERNCONF=SMP
680         fi
681         if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
682                 if sysctl kern.version | grep -qE '/SMP$'; then
683                         KERNCONF=SMP
684                 fi
685         fi
686
687         # Define some paths
688         BSPATCH=/usr/bin/bspatch
689         SHA256=/sbin/sha256
690         PHTTPGET=/usr/libexec/phttpget
691
692         # Set up variables relating to VERBOSELEVEL
693         fetch_setup_verboselevel
694
695         # Construct a unique name from ${BASEDIR}
696         BDHASH=`echo ${BASEDIR} | sha256 -q`
697 }
698
699 # Perform sanity checks etc. before fetching updates.
700 fetch_check_params () {
701         fetchupgrade_check_params
702
703         if ! [ -z "${TARGETRELEASE}" ]; then
704                 echo -n "`basename $0`: "
705                 echo -n "-r option is meaningless with 'fetch' command.  "
706                 echo "(Did you mean 'upgrade' instead?)"
707                 exit 1
708         fi
709
710         # Check that we have updates ready to install
711         if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
712                 echo "You have a partially completed upgrade pending"
713                 echo "Run '$0 install' first."
714                 echo "Run '$0 fetch -F' to proceed anyway."
715                 exit 1
716         fi
717 }
718
719 # Perform sanity checks etc. before fetching upgrades.
720 upgrade_check_params () {
721         fetchupgrade_check_params
722
723         # Unless set otherwise, we're upgrading to the same kernel config.
724         NKERNCONF=${KERNCONF}
725
726         # We need TARGETRELEASE set
727         _TARGETRELEASE_z="Release target must be specified via -r option."
728         if [ -z "${TARGETRELEASE}" ]; then
729                 echo -n "`basename $0`: "
730                 echo "${_TARGETRELEASE_z}"
731                 exit 1
732         fi
733
734         # The target release should be != the current release.
735         if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
736                 echo -n "`basename $0`: "
737                 echo "Cannot upgrade from ${RELNUM} to itself"
738                 exit 1
739         fi
740
741         # Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
742         if [ "${ALLOWADD}" = "no" ]; then
743                 echo -n "`basename $0`: "
744                 echo -n "WARNING: \"AllowAdd no\" is a bad idea "
745                 echo "when upgrading between releases."
746                 echo
747         fi
748         if [ "${ALLOWDELETE}" = "no" ]; then
749                 echo -n "`basename $0`: "
750                 echo -n "WARNING: \"AllowDelete no\" is a bad idea "
751                 echo "when upgrading between releases."
752                 echo
753         fi
754
755         # Set EDITOR to /usr/bin/vi if it isn't already set
756         : ${EDITOR:='/usr/bin/vi'}
757 }
758
759 # Perform sanity checks and set some final parameters in
760 # preparation for installing updates.
761 install_check_params () {
762         # Check that we are root.  All sorts of things won't work otherwise.
763         if [ `id -u` != 0 ]; then
764                 echo "You must be root to run this."
765                 exit 1
766         fi
767
768         # Check that securelevel <= 0.  Otherwise we can't update schg files.
769         if [ `sysctl -n kern.securelevel` -gt 0 ]; then
770                 echo "Updates cannot be installed when the system securelevel"
771                 echo "is greater than zero."
772                 exit 1
773         fi
774
775         # Check that we have a working directory
776         _WORKDIR_bad="Directory does not exist or is not writable: "
777         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
778                 echo -n "`basename $0`: "
779                 echo -n "${_WORKDIR_bad}"
780                 echo ${WORKDIR}
781                 exit 1
782         fi
783         cd ${WORKDIR} || exit 1
784
785         # Construct a unique name from ${BASEDIR}
786         BDHASH=`echo ${BASEDIR} | sha256 -q`
787
788         # Check that we have updates ready to install
789         if ! [ -L ${BDHASH}-install ]; then
790                 echo "No updates are available to install."
791                 if [ $ISFETCHED -eq 0 ]; then
792                         echo "Run '$0 fetch' first."
793                 fi
794                 exit 0
795         fi
796         if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
797             ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
798                 echo "Update manifest is corrupt -- this should never happen."
799                 echo "Re-run '$0 fetch'."
800                 exit 1
801         fi
802
803         # Figure out what directory contains the running kernel
804         BOOTFILE=`sysctl -n kern.bootfile`
805         KERNELDIR=${BOOTFILE%/kernel}
806         if ! [ -d ${KERNELDIR} ]; then
807                 echo "Cannot identify running kernel"
808                 exit 1
809         fi
810 }
811
812 # Perform sanity checks and set some final parameters in
813 # preparation for UNinstalling updates.
814 rollback_check_params () {
815         # Check that we are root.  All sorts of things won't work otherwise.
816         if [ `id -u` != 0 ]; then
817                 echo "You must be root to run this."
818                 exit 1
819         fi
820
821         # Check that we have a working directory
822         _WORKDIR_bad="Directory does not exist or is not writable: "
823         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
824                 echo -n "`basename $0`: "
825                 echo -n "${_WORKDIR_bad}"
826                 echo ${WORKDIR}
827                 exit 1
828         fi
829         cd ${WORKDIR} || exit 1
830
831         # Construct a unique name from ${BASEDIR}
832         BDHASH=`echo ${BASEDIR} | sha256 -q`
833
834         # Check that we have updates ready to rollback
835         if ! [ -L ${BDHASH}-rollback ]; then
836                 echo "No rollback directory found."
837                 exit 1
838         fi
839         if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
840             ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
841                 echo "Update manifest is corrupt -- this should never happen."
842                 exit 1
843         fi
844 }
845
846 # Perform sanity checks and set some final parameters
847 # in preparation for comparing the system against the
848 # published index.  Figure out which index we should
849 # compare against: If the user is running *-p[0-9]+,
850 # strip off the last part; if the user is running
851 # -SECURITY, call it -RELEASE.  Chdir into the working
852 # directory.
853 IDS_check_params () {
854         export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
855
856         _SERVERNAME_z=\
857 "SERVERNAME must be given via command line or configuration file."
858         _KEYPRINT_z="Key must be given via -k option or configuration file."
859         _KEYPRINT_bad="Invalid key fingerprint: "
860         _WORKDIR_bad="Directory does not exist or is not writable: "
861
862         if [ -z "${SERVERNAME}" ]; then
863                 echo -n "`basename $0`: "
864                 echo "${_SERVERNAME_z}"
865                 exit 1
866         fi
867         if [ -z "${KEYPRINT}" ]; then
868                 echo -n "`basename $0`: "
869                 echo "${_KEYPRINT_z}"
870                 exit 1
871         fi
872         if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
873                 echo -n "`basename $0`: "
874                 echo -n "${_KEYPRINT_bad}"
875                 echo ${KEYPRINT}
876                 exit 1
877         fi
878         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
879                 echo -n "`basename $0`: "
880                 echo -n "${_WORKDIR_bad}"
881                 echo ${WORKDIR}
882                 exit 1
883         fi
884         cd ${WORKDIR} || exit 1
885
886         # Generate release number.  The s/SECURITY/RELEASE/ bit exists
887         # to provide an upgrade path for FreeBSD Update 1.x users, since
888         # the kernels provided by FreeBSD Update 1.x are always labelled
889         # as X.Y-SECURITY.
890         RELNUM=`uname -r |
891             sed -E 's,-p[0-9]+,,' |
892             sed -E 's,-SECURITY,-RELEASE,'`
893         ARCH=`uname -m`
894         FETCHDIR=${RELNUM}/${ARCH}
895         PATCHDIR=${RELNUM}/${ARCH}/bp
896
897         # Figure out what directory contains the running kernel
898         BOOTFILE=`sysctl -n kern.bootfile`
899         KERNELDIR=${BOOTFILE%/kernel}
900         if ! [ -d ${KERNELDIR} ]; then
901                 echo "Cannot identify running kernel"
902                 exit 1
903         fi
904
905         # Figure out what kernel configuration is running.  We start with
906         # the output of `uname -i`, and then make the following adjustments:
907         # 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
908         # file says "ident SMP-GENERIC", I don't know...
909         # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
910         # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
911         # we're running an SMP kernel.  This mis-identification is a bug
912         # which was fixed in 6.2-STABLE.
913         KERNCONF=`uname -i`
914         if [ ${KERNCONF} = "SMP-GENERIC" ]; then
915                 KERNCONF=SMP
916         fi
917         if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
918                 if sysctl kern.version | grep -qE '/SMP$'; then
919                         KERNCONF=SMP
920                 fi
921         fi
922
923         # Define some paths
924         SHA256=/sbin/sha256
925         PHTTPGET=/usr/libexec/phttpget
926
927         # Set up variables relating to VERBOSELEVEL
928         fetch_setup_verboselevel
929 }
930
931 #### Core functionality -- the actual work gets done here
932
933 # Use an SRV query to pick a server.  If the SRV query doesn't provide
934 # a useful answer, use the server name specified by the user.
935 # Put another way... look up _http._tcp.${SERVERNAME} and pick a server
936 # from that; or if no servers are returned, use ${SERVERNAME}.
937 # This allows a user to specify "portsnap.freebsd.org" (in which case
938 # portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
939 # (in which case portsnap will use that particular server, since there
940 # won't be an SRV entry for that name).
941 #
942 # We ignore the Port field, since we are always going to use port 80.
943
944 # Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
945 # no mirrors are available for any reason.
946 fetch_pick_server_init () {
947         : > serverlist_tried
948
949 # Check that host(1) exists (i.e., that the system wasn't built with the
950 # WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
951         if ! which -s host; then
952                 : > serverlist_full
953                 return 1
954         fi
955
956         echo -n "Looking up ${SERVERNAME} mirrors... "
957
958 # Issue the SRV query and pull out the Priority, Weight, and Target fields.
959 # BIND 9 prints "$name has SRV record ..." while BIND 8 prints
960 # "$name server selection ..."; we allow either format.
961         MLIST="_http._tcp.${SERVERNAME}"
962         host -t srv "${MLIST}" |
963             sed -nE "s/${MLIST} (has SRV record|server selection) //Ip" |
964             cut -f 1,2,4 -d ' ' |
965             sed -e 's/\.$//' |
966             sort > serverlist_full
967
968 # If no records, give up -- we'll just use the server name we were given.
969         if [ `wc -l < serverlist_full` -eq 0 ]; then
970                 echo "none found."
971                 return 1
972         fi
973
974 # Report how many mirrors we found.
975         echo `wc -l < serverlist_full` "mirrors found."
976
977 # Generate a random seed for use in picking mirrors.  If HTTP_PROXY
978 # is set, this will be used to generate the seed; otherwise, the seed
979 # will be random.
980         if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
981                 RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
982                     tr -d 'a-f' |
983                     cut -c 1-9`
984         else
985                 RANDVALUE=`jot -r 1 0 999999999`
986         fi
987 }
988
989 # Pick a mirror.  Returns 1 if we have run out of mirrors to try.
990 fetch_pick_server () {
991 # Generate a list of not-yet-tried mirrors
992         sort serverlist_tried |
993             comm -23 serverlist_full - > serverlist
994
995 # Have we run out of mirrors?
996         if [ `wc -l < serverlist` -eq 0 ]; then
997                 cat <<- EOF
998                         No mirrors remaining, giving up.
999
1000                         This may be because upgrading from this platform (${ARCH})
1001                         or release (${RELNUM}) is unsupported by `basename $0`. Only
1002                         platforms with Tier 1 support can be upgraded by `basename $0`.
1003                         See https://www.freebsd.org/platforms/index.html for more info.
1004
1005                         If unsupported, FreeBSD must be upgraded by source.
1006                 EOF
1007                 return 1
1008         fi
1009
1010 # Find the highest priority level (lowest numeric value).
1011         SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
1012
1013 # Add up the weights of the response lines at that priority level.
1014         SRV_WSUM=0;
1015         while read X; do
1016                 case "$X" in
1017                 ${SRV_PRIORITY}\ *)
1018                         SRV_W=`echo $X | cut -f 2 -d ' '`
1019                         SRV_WSUM=$(($SRV_WSUM + $SRV_W))
1020                         ;;
1021                 esac
1022         done < serverlist
1023
1024 # If all the weights are 0, pretend that they are all 1 instead.
1025         if [ ${SRV_WSUM} -eq 0 ]; then
1026                 SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
1027                 SRV_W_ADD=1
1028         else
1029                 SRV_W_ADD=0
1030         fi
1031
1032 # Pick a value between 0 and the sum of the weights - 1
1033         SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
1034
1035 # Read through the list of mirrors and set SERVERNAME.  Write the line
1036 # corresponding to the mirror we selected into serverlist_tried so that
1037 # we won't try it again.
1038         while read X; do
1039                 case "$X" in
1040                 ${SRV_PRIORITY}\ *)
1041                         SRV_W=`echo $X | cut -f 2 -d ' '`
1042                         SRV_W=$(($SRV_W + $SRV_W_ADD))
1043                         if [ $SRV_RND -lt $SRV_W ]; then
1044                                 SERVERNAME=`echo $X | cut -f 3 -d ' '`
1045                                 echo "$X" >> serverlist_tried
1046                                 break
1047                         else
1048                                 SRV_RND=$(($SRV_RND - $SRV_W))
1049                         fi
1050                         ;;
1051                 esac
1052         done < serverlist
1053 }
1054
1055 # Take a list of ${oldhash}|${newhash} and output a list of needed patches,
1056 # i.e., those for which we have ${oldhash} and don't have ${newhash}.
1057 fetch_make_patchlist () {
1058         grep -vE "^([0-9a-f]{64})\|\1$" |
1059             tr '|' ' ' |
1060                 while read X Y; do
1061                         if [ -f "files/${Y}.gz" ] ||
1062                             [ ! -f "files/${X}.gz" ]; then
1063                                 continue
1064                         fi
1065                         echo "${X}|${Y}"
1066                 done | sort -u
1067 }
1068
1069 # Print user-friendly progress statistics
1070 fetch_progress () {
1071         LNC=0
1072         while read x; do
1073                 LNC=$(($LNC + 1))
1074                 if [ $(($LNC % 10)) = 0 ]; then
1075                         echo -n $LNC
1076                 elif [ $(($LNC % 2)) = 0 ]; then
1077                         echo -n .
1078                 fi
1079         done
1080         echo -n " "
1081 }
1082
1083 # Function for asking the user if everything is ok
1084 continuep () {
1085         while read -p "Does this look reasonable (y/n)? " CONTINUE; do
1086                 case "${CONTINUE}" in
1087                 y*)
1088                         return 0
1089                         ;;
1090                 n*)
1091                         return 1
1092                         ;;
1093                 esac
1094         done
1095 }
1096
1097 # Initialize the working directory
1098 workdir_init () {
1099         mkdir -p files
1100         touch tINDEX.present
1101 }
1102
1103 # Check that we have a public key with an appropriate hash, or
1104 # fetch the key if it doesn't exist.  Returns 1 if the key has
1105 # not yet been fetched.
1106 fetch_key () {
1107         if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1108                 return 0
1109         fi
1110
1111         echo -n "Fetching public key from ${SERVERNAME}... "
1112         rm -f pub.ssl
1113         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
1114             2>${QUIETREDIR} || true
1115         if ! [ -r pub.ssl ]; then
1116                 echo "failed."
1117                 return 1
1118         fi
1119         if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1120                 echo "key has incorrect hash."
1121                 rm -f pub.ssl
1122                 return 1
1123         fi
1124         echo "done."
1125 }
1126
1127 # Fetch metadata signature, aka "tag".
1128 fetch_tag () {
1129         echo -n "Fetching metadata signature "
1130         echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
1131         rm -f latest.ssl
1132         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl  \
1133             2>${QUIETREDIR} || true
1134         if ! [ -r latest.ssl ]; then
1135                 echo "failed."
1136                 return 1
1137         fi
1138
1139         openssl rsautl -pubin -inkey pub.ssl -verify            \
1140             < latest.ssl > tag.new 2>${QUIETREDIR} || true
1141         rm latest.ssl
1142
1143         if ! [ `wc -l < tag.new` = 1 ] ||
1144             ! grep -qE  \
1145     "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1146                 tag.new; then
1147                 echo "invalid signature."
1148                 return 1
1149         fi
1150
1151         echo "done."
1152
1153         RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
1154         TINDEXHASH=`cut -f 5 -d '|' < tag.new`
1155         EOLTIME=`cut -f 6 -d '|' < tag.new`
1156 }
1157
1158 # Sanity-check the patch number in a tag, to make sure that we're not
1159 # going to "update" backwards and to prevent replay attacks.
1160 fetch_tagsanity () {
1161         # Check that we're not going to move from -pX to -pY with Y < X.
1162         RELPX=`uname -r | sed -E 's,.*-,,'`
1163         if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
1164                 RELPX=`echo ${RELPX} | cut -c 2-`
1165         else
1166                 RELPX=0
1167         fi
1168         if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
1169                 echo
1170                 echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1171                 echo " appear older than what"
1172                 echo "we are currently running (`uname -r`)!"
1173                 echo "Cowardly refusing to proceed any further."
1174                 return 1
1175         fi
1176
1177         # If "tag" exists and corresponds to ${RELNUM}, make sure that
1178         # it contains a patch number <= RELPATCHNUM, in order to protect
1179         # against rollback (replay) attacks.
1180         if [ -f tag ] &&
1181             grep -qE    \
1182     "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1183                 tag; then
1184                 LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
1185
1186                 if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
1187                         echo
1188                         echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1189                         echo " are older than the"
1190                         echo -n "most recently seen updates"
1191                         echo " (${RELNUM}-p${LASTRELPATCHNUM})."
1192                         echo "Cowardly refusing to proceed any further."
1193                         return 1
1194                 fi
1195         fi
1196 }
1197
1198 # Fetch metadata index file
1199 fetch_metadata_index () {
1200         echo ${NDEBUG} "Fetching metadata index... "
1201         rm -f ${TINDEXHASH}
1202         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
1203             2>${QUIETREDIR}
1204         if ! [ -f ${TINDEXHASH} ]; then
1205                 echo "failed."
1206                 return 1
1207         fi
1208         if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
1209                 echo "update metadata index corrupt."
1210                 return 1
1211         fi
1212         echo "done."
1213 }
1214
1215 # Print an error message about signed metadata being bogus.
1216 fetch_metadata_bogus () {
1217         echo
1218         echo "The update metadata$1 is correctly signed, but"
1219         echo "failed an integrity check."
1220         echo "Cowardly refusing to proceed any further."
1221         return 1
1222 }
1223
1224 # Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
1225 # with the lines not named in $@ from tINDEX.present (if that file exists).
1226 fetch_metadata_index_merge () {
1227         for METAFILE in $@; do
1228                 if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l`   \
1229                     -ne 1 ]; then
1230                         fetch_metadata_bogus " index"
1231                         return 1
1232                 fi
1233
1234                 grep -E "${METAFILE}\|" ${TINDEXHASH}
1235         done |
1236             sort > tINDEX.wanted
1237
1238         if [ -f tINDEX.present ]; then
1239                 join -t '|' -v 2 tINDEX.wanted tINDEX.present |
1240                     sort -m - tINDEX.wanted > tINDEX.new
1241                 rm tINDEX.wanted
1242         else
1243                 mv tINDEX.wanted tINDEX.new
1244         fi
1245 }
1246
1247 # Sanity check all the lines of tINDEX.new.  Even if more metadata lines
1248 # are added by future versions of the server, this won't cause problems,
1249 # since the only lines which appear in tINDEX.new are the ones which we
1250 # specifically grepped out of ${TINDEXHASH}.
1251 fetch_metadata_index_sanity () {
1252         if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
1253                 fetch_metadata_bogus " index"
1254                 return 1
1255         fi
1256 }
1257
1258 # Sanity check the metadata file $1.
1259 fetch_metadata_sanity () {
1260         # Some aliases to save space later: ${P} is a character which can
1261         # appear in a path; ${M} is the four numeric metadata fields; and
1262         # ${H} is a sha256 hash.
1263         P="[-+./:=,%@_[~[:alnum:]]"
1264         M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
1265         H="[0-9a-f]{64}"
1266
1267         # Check that the first four fields make sense.
1268         if gunzip -c < files/$1.gz |
1269             grep -qvE "^[a-z]+\|[0-9a-z-]+\|${P}+\|[fdL-]\|"; then
1270                 fetch_metadata_bogus ""
1271                 return 1
1272         fi
1273
1274         # Remove the first three fields.
1275         gunzip -c < files/$1.gz |
1276             cut -f 4- -d '|' > sanitycheck.tmp
1277
1278         # Sanity check entries with type 'f'
1279         if grep -E '^f' sanitycheck.tmp |
1280             grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
1281                 fetch_metadata_bogus ""
1282                 return 1
1283         fi
1284
1285         # Sanity check entries with type 'd'
1286         if grep -E '^d' sanitycheck.tmp |
1287             grep -qvE "^d\|${M}\|\|\$"; then
1288                 fetch_metadata_bogus ""
1289                 return 1
1290         fi
1291
1292         # Sanity check entries with type 'L'
1293         if grep -E '^L' sanitycheck.tmp |
1294             grep -qvE "^L\|${M}\|${P}*\|\$"; then
1295                 fetch_metadata_bogus ""
1296                 return 1
1297         fi
1298
1299         # Sanity check entries with type '-'
1300         if grep -E '^-' sanitycheck.tmp |
1301             grep -qvE "^-\|\|\|\|\|\|"; then
1302                 fetch_metadata_bogus ""
1303                 return 1
1304         fi
1305
1306         # Clean up
1307         rm sanitycheck.tmp
1308 }
1309
1310 # Fetch the metadata index and metadata files listed in $@,
1311 # taking advantage of metadata patches where possible.
1312 fetch_metadata () {
1313         fetch_metadata_index || return 1
1314         fetch_metadata_index_merge $@ || return 1
1315         fetch_metadata_index_sanity || return 1
1316
1317         # Generate a list of wanted metadata patches
1318         join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
1319             fetch_make_patchlist > patchlist
1320
1321         if [ -s patchlist ]; then
1322                 # Attempt to fetch metadata patches
1323                 echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1324                 echo ${NDEBUG} "metadata patches.${DDSTATS}"
1325                 tr '|' '-' < patchlist |
1326                     lam -s "${FETCHDIR}/tp/" - -s ".gz" |
1327                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1328                         2>${STATSREDIR} | fetch_progress
1329                 echo "done."
1330
1331                 # Attempt to apply metadata patches
1332                 echo -n "Applying metadata patches... "
1333                 tr '|' ' ' < patchlist |
1334                     while read X Y; do
1335                         if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
1336                         gunzip -c < ${X}-${Y}.gz > diff
1337                         gunzip -c < files/${X}.gz > diff-OLD
1338
1339                         # Figure out which lines are being added and removed
1340                         grep -E '^-' diff |
1341                             cut -c 2- |
1342                             while read PREFIX; do
1343                                 look "${PREFIX}" diff-OLD
1344                             done |
1345                             sort > diff-rm
1346                         grep -E '^\+' diff |
1347                             cut -c 2- > diff-add
1348
1349                         # Generate the new file
1350                         comm -23 diff-OLD diff-rm |
1351                             sort - diff-add > diff-NEW
1352
1353                         if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
1354                                 mv diff-NEW files/${Y}
1355                                 gzip -n files/${Y}
1356                         else
1357                                 mv diff-NEW ${Y}.bad
1358                         fi
1359                         rm -f ${X}-${Y}.gz diff
1360                         rm -f diff-OLD diff-NEW diff-add diff-rm
1361                 done 2>${QUIETREDIR}
1362                 echo "done."
1363         fi
1364
1365         # Update metadata without patches
1366         cut -f 2 -d '|' < tINDEX.new |
1367             while read Y; do
1368                 if [ ! -f "files/${Y}.gz" ]; then
1369                         echo ${Y};
1370                 fi
1371             done |
1372             sort -u > filelist
1373
1374         if [ -s filelist ]; then
1375                 echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1376                 echo ${NDEBUG} "metadata files... "
1377                 lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
1378                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1379                     2>${QUIETREDIR}
1380
1381                 while read Y; do
1382                         if ! [ -f ${Y}.gz ]; then
1383                                 echo "failed."
1384                                 return 1
1385                         fi
1386                         if [ `gunzip -c < ${Y}.gz |
1387                             ${SHA256} -q` = ${Y} ]; then
1388                                 mv ${Y}.gz files/${Y}.gz
1389                         else
1390                                 echo "metadata is corrupt."
1391                                 return 1
1392                         fi
1393                 done < filelist
1394                 echo "done."
1395         fi
1396
1397 # Sanity-check the metadata files.
1398         cut -f 2 -d '|' tINDEX.new > filelist
1399         while read X; do
1400                 fetch_metadata_sanity ${X} || return 1
1401         done < filelist
1402
1403 # Remove files which are no longer needed
1404         cut -f 2 -d '|' tINDEX.present |
1405             sort > oldfiles
1406         cut -f 2 -d '|' tINDEX.new |
1407             sort |
1408             comm -13 - oldfiles |
1409             lam -s "files/" - -s ".gz" |
1410             xargs rm -f
1411         rm patchlist filelist oldfiles
1412         rm ${TINDEXHASH}
1413
1414 # We're done!
1415         mv tINDEX.new tINDEX.present
1416         mv tag.new tag
1417
1418         return 0
1419 }
1420
1421 # Extract a subset of a downloaded metadata file containing only the parts
1422 # which are listed in COMPONENTS.
1423 fetch_filter_metadata_components () {
1424         METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
1425         gunzip -c < files/${METAHASH}.gz > $1.all
1426
1427         # Fish out the lines belonging to components we care about.
1428         for C in ${COMPONENTS}; do
1429                 look "`echo ${C} | tr '/' '|'`|" $1.all
1430         done > $1
1431
1432         # Remove temporary file.
1433         rm $1.all
1434 }
1435
1436 # Generate a filtered version of the metadata file $1 from the downloaded
1437 # file, by fishing out the lines corresponding to components we're trying
1438 # to keep updated, and then removing lines corresponding to paths we want
1439 # to ignore.
1440 fetch_filter_metadata () {
1441         # Fish out the lines belonging to components we care about.
1442         fetch_filter_metadata_components $1
1443
1444         # Canonicalize directory names by removing any trailing / in
1445         # order to avoid listing directories multiple times if they
1446         # belong to multiple components.  Turning "/" into "" doesn't
1447         # matter, since we add a leading "/" when we use paths later.
1448         cut -f 3- -d '|' $1 |
1449             sed -e 's,/|d|,|d|,' |
1450             sed -e 's,/|-|,|-|,' |
1451             sort -u > $1.tmp
1452
1453         # Figure out which lines to ignore and remove them.
1454         for X in ${IGNOREPATHS}; do
1455                 grep -E "^${X}" $1.tmp
1456         done |
1457             sort -u |
1458             comm -13 - $1.tmp > $1
1459
1460         # Remove temporary files.
1461         rm $1.tmp
1462 }
1463
1464 # Filter the metadata file $1 by adding lines with "/boot/$2"
1465 # replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
1466 # trailing "/kernel"); and if "/boot/$2" does not exist, remove
1467 # the original lines which start with that.
1468 # Put another way: Deal with the fact that the FOO kernel is sometimes
1469 # installed in /boot/FOO/ and is sometimes installed elsewhere.
1470 fetch_filter_kernel_names () {
1471         grep ^/boot/$2 $1 |
1472             sed -e "s,/boot/$2,${KERNELDIR},g" |
1473             sort - $1 > $1.tmp
1474         mv $1.tmp $1
1475
1476         if ! [ -d /boot/$2 ]; then
1477                 grep -v ^/boot/$2 $1 > $1.tmp
1478                 mv $1.tmp $1
1479         fi
1480 }
1481
1482 # For all paths appearing in $1 or $3, inspect the system
1483 # and generate $2 describing what is currently installed.
1484 fetch_inspect_system () {
1485         # No errors yet...
1486         rm -f .err
1487
1488         # Tell the user why his disk is suddenly making lots of noise
1489         echo -n "Inspecting system... "
1490
1491         # Generate list of files to inspect
1492         cat $1 $3 |
1493             cut -f 1 -d '|' |
1494             sort -u > filelist
1495
1496         # Examine each file and output lines of the form
1497         # /path/to/file|type|device-inum|user|group|perm|flags|value
1498         # sorted by device and inode number.
1499         while read F; do
1500                 # If the symlink/file/directory does not exist, record this.
1501                 if ! [ -e ${BASEDIR}/${F} ]; then
1502                         echo "${F}|-||||||"
1503                         continue
1504                 fi
1505                 if ! [ -r ${BASEDIR}/${F} ]; then
1506                         echo "Cannot read file: ${BASEDIR}/${F}"        \
1507                             >/dev/stderr
1508                         touch .err
1509                         return 1
1510                 fi
1511
1512                 # Otherwise, output an index line.
1513                 if [ -L ${BASEDIR}/${F} ]; then
1514                         echo -n "${F}|L|"
1515                         stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1516                         readlink ${BASEDIR}/${F};
1517                 elif [ -f ${BASEDIR}/${F} ]; then
1518                         echo -n "${F}|f|"
1519                         stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1520                         sha256 -q ${BASEDIR}/${F};
1521                 elif [ -d ${BASEDIR}/${F} ]; then
1522                         echo -n "${F}|d|"
1523                         stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1524                 else
1525                         echo "Unknown file type: ${BASEDIR}/${F}"       \
1526                             >/dev/stderr
1527                         touch .err
1528                         return 1
1529                 fi
1530         done < filelist |
1531             sort -k 3,3 -t '|' > $2.tmp
1532         rm filelist
1533
1534         # Check if an error occurred during system inspection
1535         if [ -f .err ]; then
1536                 return 1
1537         fi
1538
1539         # Convert to the form
1540         # /path/to/file|type|user|group|perm|flags|value|hlink
1541         # by resolving identical device and inode numbers into hard links.
1542         cut -f 1,3 -d '|' $2.tmp |
1543             sort -k 1,1 -t '|' |
1544             sort -s -u -k 2,2 -t '|' |
1545             join -1 2 -2 3 -t '|' - $2.tmp |
1546             awk -F \| -v OFS=\|         \
1547                 '{
1548                     if (($2 == $3) || ($4 == "-"))
1549                         print $3,$4,$5,$6,$7,$8,$9,""
1550                     else
1551                         print $3,$4,$5,$6,$7,$8,$9,$2
1552                 }' |
1553             sort > $2
1554         rm $2.tmp
1555
1556         # We're finished looking around
1557         echo "done."
1558 }
1559
1560 # For any paths matching ${MERGECHANGES}, compare $1 and $2 and find any
1561 # files which differ; generate $3 containing these paths and the old hashes.
1562 fetch_filter_mergechanges () {
1563         # Pull out the paths and hashes of the files matching ${MERGECHANGES}.
1564         for F in $1 $2; do
1565                 for X in ${MERGECHANGES}; do
1566                         grep -E "^${X}" ${F}
1567                 done |
1568                     cut -f 1,2,7 -d '|' |
1569                     sort > ${F}-values
1570         done
1571
1572         # Any line in $2-values which doesn't appear in $1-values and is a
1573         # file means that we should list the path in $3.
1574         comm -13 $1-values $2-values |
1575             fgrep '|f|' |
1576             cut -f 1 -d '|' > $2-paths
1577
1578         # For each path, pull out one (and only one!) entry from $1-values.
1579         # Note that we cannot distinguish which "old" version the user made
1580         # changes to; but hopefully any changes which occur due to security
1581         # updates will exist in both the "new" version and the version which
1582         # the user has installed, so the merging will still work.
1583         while read X; do
1584                 look "${X}|" $1-values |
1585                     head -1
1586         done < $2-paths > $3
1587
1588         # Clean up
1589         rm $1-values $2-values $2-paths
1590 }
1591
1592 # For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
1593 # which correspond to lines in $2 with hashes not matching $1 or $3, unless
1594 # the paths are listed in $4.  For entries in $2 marked "not present"
1595 # (aka. type -), remove lines from $[123] unless there is a corresponding
1596 # entry in $1.
1597 fetch_filter_unmodified_notpresent () {
1598         # Figure out which lines of $1 and $3 correspond to bits which
1599         # should only be updated if they haven't changed, and fish out
1600         # the (path, type, value) tuples.
1601         # NOTE: We don't consider a file to be "modified" if it matches
1602         # the hash from $3.
1603         for X in ${UPDATEIFUNMODIFIED}; do
1604                 grep -E "^${X}" $1
1605                 grep -E "^${X}" $3
1606         done |
1607             cut -f 1,2,7 -d '|' |
1608             sort > $1-values
1609
1610         # Do the same for $2.
1611         for X in ${UPDATEIFUNMODIFIED}; do
1612                 grep -E "^${X}" $2
1613         done |
1614             cut -f 1,2,7 -d '|' |
1615             sort > $2-values
1616
1617         # Any entry in $2-values which is not in $1-values corresponds to
1618         # a path which we need to remove from $1, $2, and $3, unless it
1619         # that path appears in $4.
1620         comm -13 $1-values $2-values |
1621             sort -t '|' -k 1,1 > mlines.tmp
1622         cut -f 1 -d '|' $4 |
1623             sort |
1624             join -v 2 -t '|' - mlines.tmp |
1625             sort > mlines
1626         rm $1-values $2-values mlines.tmp
1627
1628         # Any lines in $2 which are not in $1 AND are "not present" lines
1629         # also belong in mlines.
1630         comm -13 $1 $2 |
1631             cut -f 1,2,7 -d '|' |
1632             fgrep '|-|' >> mlines
1633
1634         # Remove lines from $1, $2, and $3
1635         for X in $1 $2 $3; do
1636                 sort -t '|' -k 1,1 ${X} > ${X}.tmp
1637                 cut -f 1 -d '|' < mlines |
1638                     sort |
1639                     join -v 2 -t '|' - ${X}.tmp |
1640                     sort > ${X}
1641                 rm ${X}.tmp
1642         done
1643
1644         # Store a list of the modified files, for future reference
1645         fgrep -v '|-|' mlines |
1646             cut -f 1 -d '|' > modifiedfiles
1647         rm mlines
1648 }
1649
1650 # For each entry in $1 of type -, remove any corresponding
1651 # entry from $2 if ${ALLOWADD} != "yes".  Remove all entries
1652 # of type - from $1.
1653 fetch_filter_allowadd () {
1654         cut -f 1,2 -d '|' < $1 |
1655             fgrep '|-' |
1656             cut -f 1 -d '|' > filesnotpresent
1657
1658         if [ ${ALLOWADD} != "yes" ]; then
1659                 sort < $2 |
1660                     join -v 1 -t '|' - filesnotpresent |
1661                     sort > $2.tmp
1662                 mv $2.tmp $2
1663         fi
1664
1665         sort < $1 |
1666             join -v 1 -t '|' - filesnotpresent |
1667             sort > $1.tmp
1668         mv $1.tmp $1
1669         rm filesnotpresent
1670 }
1671
1672 # If ${ALLOWDELETE} != "yes", then remove any entries from $1
1673 # which don't correspond to entries in $2.
1674 fetch_filter_allowdelete () {
1675         # Produce a lists ${PATH}|${TYPE}
1676         for X in $1 $2; do
1677                 cut -f 1-2 -d '|' < ${X} |
1678                     sort -u > ${X}.nodes
1679         done
1680
1681         # Figure out which lines need to be removed from $1.
1682         if [ ${ALLOWDELETE} != "yes" ]; then
1683                 comm -23 $1.nodes $2.nodes > $1.badnodes
1684         else
1685                 : > $1.badnodes
1686         fi
1687
1688         # Remove the relevant lines from $1
1689         while read X; do
1690                 look "${X}|" $1
1691         done < $1.badnodes |
1692             comm -13 - $1 > $1.tmp
1693         mv $1.tmp $1
1694
1695         rm $1.badnodes $1.nodes $2.nodes
1696 }
1697
1698 # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
1699 # with metadata not matching any entry in $1, replace the corresponding
1700 # line of $3 with one having the same metadata as the entry in $2.
1701 fetch_filter_modified_metadata () {
1702         # Fish out the metadata from $1 and $2
1703         for X in $1 $2; do
1704                 cut -f 1-6 -d '|' < ${X} > ${X}.metadata
1705         done
1706
1707         # Find the metadata we need to keep
1708         if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
1709                 comm -13 $1.metadata $2.metadata > keepmeta
1710         else
1711                 : > keepmeta
1712         fi
1713
1714         # Extract the lines which we need to remove from $3, and
1715         # construct the lines which we need to add to $3.
1716         : > $3.remove
1717         : > $3.add
1718         while read LINE; do
1719                 NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
1720                 look "${NODE}|" $3 >> $3.remove
1721                 look "${NODE}|" $3 |
1722                     cut -f 7- -d '|' |
1723                     lam -s "${LINE}|" - >> $3.add
1724         done < keepmeta
1725
1726         # Remove the specified lines and add the new lines.
1727         sort $3.remove |
1728             comm -13 - $3 |
1729             sort -u - $3.add > $3.tmp
1730         mv $3.tmp $3
1731
1732         rm keepmeta $1.metadata $2.metadata $3.add $3.remove
1733 }
1734
1735 # Remove lines from $1 and $2 which are identical;
1736 # no need to update a file if it isn't changing.
1737 fetch_filter_uptodate () {
1738         comm -23 $1 $2 > $1.tmp
1739         comm -13 $1 $2 > $2.tmp
1740
1741         mv $1.tmp $1
1742         mv $2.tmp $2
1743 }
1744
1745 # Fetch any "clean" old versions of files we need for merging changes.
1746 fetch_files_premerge () {
1747         # We only need to do anything if $1 is non-empty.
1748         if [ -s $1 ]; then
1749                 # Tell the user what we're doing
1750                 echo -n "Fetching files from ${OLDRELNUM} for merging... "
1751
1752                 # List of files wanted
1753                 fgrep '|f|' < $1 |
1754                     cut -f 3 -d '|' |
1755                     sort -u > files.wanted
1756
1757                 # Only fetch the files we don't already have
1758                 while read Y; do
1759                         if [ ! -f "files/${Y}.gz" ]; then
1760                                 echo ${Y};
1761                         fi
1762                 done < files.wanted > filelist
1763
1764                 # Actually fetch them
1765                 lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist |
1766                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1767                     2>${QUIETREDIR}
1768
1769                 # Make sure we got them all, and move them into /files/
1770                 while read Y; do
1771                         if ! [ -f ${Y}.gz ]; then
1772                                 echo "failed."
1773                                 return 1
1774                         fi
1775                         if [ `gunzip -c < ${Y}.gz |
1776                             ${SHA256} -q` = ${Y} ]; then
1777                                 mv ${Y}.gz files/${Y}.gz
1778                         else
1779                                 echo "${Y} has incorrect hash."
1780                                 return 1
1781                         fi
1782                 done < filelist
1783                 echo "done."
1784
1785                 # Clean up
1786                 rm filelist files.wanted
1787         fi
1788 }
1789
1790 # Prepare to fetch files: Generate a list of the files we need,
1791 # copy the unmodified files we have into /files/, and generate
1792 # a list of patches to download.
1793 fetch_files_prepare () {
1794         # Tell the user why his disk is suddenly making lots of noise
1795         echo -n "Preparing to download files... "
1796
1797         # Reduce indices to ${PATH}|${HASH} pairs
1798         for X in $1 $2 $3; do
1799                 cut -f 1,2,7 -d '|' < ${X} |
1800                     fgrep '|f|' |
1801                     cut -f 1,3 -d '|' |
1802                     sort > ${X}.hashes
1803         done
1804
1805         # List of files wanted
1806         cut -f 2 -d '|' < $3.hashes |
1807             sort -u |
1808             while read HASH; do
1809                 if ! [ -f files/${HASH}.gz ]; then
1810                         echo ${HASH}
1811                 fi
1812         done > files.wanted
1813
1814         # Generate a list of unmodified files
1815         comm -12 $1.hashes $2.hashes |
1816             sort -k 1,1 -t '|' > unmodified.files
1817
1818         # Copy all files into /files/.  We only need the unmodified files
1819         # for use in patching; but we'll want all of them if the user asks
1820         # to rollback the updates later.
1821         while read LINE; do
1822                 F=`echo "${LINE}" | cut -f 1 -d '|'`
1823                 HASH=`echo "${LINE}" | cut -f 2 -d '|'`
1824
1825                 # Skip files we already have.
1826                 if [ -f files/${HASH}.gz ]; then
1827                         continue
1828                 fi
1829
1830                 # Make sure the file hasn't changed.
1831                 cp "${BASEDIR}/${F}" tmpfile
1832                 if [ `sha256 -q tmpfile` != ${HASH} ]; then
1833                         echo
1834                         echo "File changed while FreeBSD Update running: ${F}"
1835                         return 1
1836                 fi
1837
1838                 # Place the file into storage.
1839                 gzip -c < tmpfile > files/${HASH}.gz
1840                 rm tmpfile
1841         done < $2.hashes
1842
1843         # Produce a list of patches to download
1844         sort -k 1,1 -t '|' $3.hashes |
1845             join -t '|' -o 2.2,1.2 - unmodified.files |
1846             fetch_make_patchlist > patchlist
1847
1848         # Garbage collect
1849         rm unmodified.files $1.hashes $2.hashes $3.hashes
1850
1851         # We don't need the list of possible old files any more.
1852         rm $1
1853
1854         # We're finished making noise
1855         echo "done."
1856 }
1857
1858 # Fetch files.
1859 fetch_files () {
1860         # Attempt to fetch patches
1861         if [ -s patchlist ]; then
1862                 echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1863                 echo ${NDEBUG} "patches.${DDSTATS}"
1864                 tr '|' '-' < patchlist |
1865                     lam -s "${PATCHDIR}/" - |
1866                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1867                         2>${STATSREDIR} | fetch_progress
1868                 echo "done."
1869
1870                 # Attempt to apply patches
1871                 echo -n "Applying patches... "
1872                 tr '|' ' ' < patchlist |
1873                     while read X Y; do
1874                         if [ ! -f "${X}-${Y}" ]; then continue; fi
1875                         gunzip -c < files/${X}.gz > OLD
1876
1877                         bspatch OLD NEW ${X}-${Y}
1878
1879                         if [ `${SHA256} -q NEW` = ${Y} ]; then
1880                                 mv NEW files/${Y}
1881                                 gzip -n files/${Y}
1882                         fi
1883                         rm -f diff OLD NEW ${X}-${Y}
1884                 done 2>${QUIETREDIR}
1885                 echo "done."
1886         fi
1887
1888         # Download files which couldn't be generate via patching
1889         while read Y; do
1890                 if [ ! -f "files/${Y}.gz" ]; then
1891                         echo ${Y};
1892                 fi
1893         done < files.wanted > filelist
1894
1895         if [ -s filelist ]; then
1896                 echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1897                 echo ${NDEBUG} "files... "
1898                 lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
1899                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1900                         2>${STATSREDIR} | fetch_progress
1901
1902                 while read Y; do
1903                         if ! [ -f ${Y}.gz ]; then
1904                                 echo "failed."
1905                                 return 1
1906                         fi
1907                         if [ `gunzip -c < ${Y}.gz |
1908                             ${SHA256} -q` = ${Y} ]; then
1909                                 mv ${Y}.gz files/${Y}.gz
1910                         else
1911                                 echo "${Y} has incorrect hash."
1912                                 return 1
1913                         fi
1914                 done < filelist
1915                 echo "done."
1916         fi
1917
1918         # Clean up
1919         rm files.wanted filelist patchlist
1920 }
1921
1922 # Create and populate install manifest directory; and report what updates
1923 # are available.
1924 fetch_create_manifest () {
1925         # If we have an existing install manifest, nuke it.
1926         if [ -L "${BDHASH}-install" ]; then
1927                 rm -r ${BDHASH}-install/
1928                 rm ${BDHASH}-install
1929         fi
1930
1931         # Report to the user if any updates were avoided due to local changes
1932         if [ -s modifiedfiles ]; then
1933                 cat - modifiedfiles <<- EOF | ${PAGER}
1934                         The following files are affected by updates. No changes have
1935                         been downloaded, however, because the files have been modified
1936                         locally:
1937                 EOF
1938         fi
1939         rm modifiedfiles
1940
1941         # If no files will be updated, tell the user and exit
1942         if ! [ -s INDEX-PRESENT ] &&
1943             ! [ -s INDEX-NEW ]; then
1944                 rm INDEX-PRESENT INDEX-NEW
1945                 echo
1946                 echo -n "No updates needed to update system to "
1947                 echo "${RELNUM}-p${RELPATCHNUM}."
1948                 return
1949         fi
1950
1951         # Divide files into (a) removed files, (b) added files, and
1952         # (c) updated files.
1953         cut -f 1 -d '|' < INDEX-PRESENT |
1954             sort > INDEX-PRESENT.flist
1955         cut -f 1 -d '|' < INDEX-NEW |
1956             sort > INDEX-NEW.flist
1957         comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
1958         comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
1959         comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
1960         rm INDEX-PRESENT.flist INDEX-NEW.flist
1961
1962         # Report removed files, if any
1963         if [ -s files.removed ]; then
1964                 cat - files.removed <<- EOF | ${PAGER}
1965                         The following files will be removed as part of updating to
1966                         ${RELNUM}-p${RELPATCHNUM}:
1967                 EOF
1968         fi
1969         rm files.removed
1970
1971         # Report added files, if any
1972         if [ -s files.added ]; then
1973                 cat - files.added <<- EOF | ${PAGER}
1974                         The following files will be added as part of updating to
1975                         ${RELNUM}-p${RELPATCHNUM}:
1976                 EOF
1977         fi
1978         rm files.added
1979
1980         # Report updated files, if any
1981         if [ -s files.updated ]; then
1982                 cat - files.updated <<- EOF | ${PAGER}
1983                         The following files will be updated as part of updating to
1984                         ${RELNUM}-p${RELPATCHNUM}:
1985                 EOF
1986         fi
1987         rm files.updated
1988
1989         # Create a directory for the install manifest.
1990         MDIR=`mktemp -d install.XXXXXX` || return 1
1991
1992         # Populate it
1993         mv INDEX-PRESENT ${MDIR}/INDEX-OLD
1994         mv INDEX-NEW ${MDIR}/INDEX-NEW
1995
1996         # Link it into place
1997         ln -s ${MDIR} ${BDHASH}-install
1998 }
1999
2000 # Warn about any upcoming EoL
2001 fetch_warn_eol () {
2002         # What's the current time?
2003         NOWTIME=`date "+%s"`
2004
2005         # When did we last warn about the EoL date?
2006         if [ -f lasteolwarn ]; then
2007                 LASTWARN=`cat lasteolwarn`
2008         else
2009                 LASTWARN=`expr ${NOWTIME} - 63072000`
2010         fi
2011
2012         # If the EoL time is past, warn.
2013         if [ ${EOLTIME} -lt ${NOWTIME} ]; then
2014                 echo
2015                 cat <<-EOF
2016                 WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
2017                 Any security issues discovered after `date -r ${EOLTIME}`
2018                 will not have been corrected.
2019                 EOF
2020                 return 1
2021         fi
2022
2023         # Figure out how long it has been since we last warned about the
2024         # upcoming EoL, and how much longer we have left.
2025         SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
2026         TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
2027
2028         # Don't warn if the EoL is more than 3 months away
2029         if [ ${TIMELEFT} -gt 7884000 ]; then
2030                 return 0
2031         fi
2032
2033         # Don't warn if the time remaining is more than 3 times the time
2034         # since the last warning.
2035         if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
2036                 return 0
2037         fi
2038
2039         # Figure out what time units to use.
2040         if [ ${TIMELEFT} -lt 604800 ]; then
2041                 UNIT="day"
2042                 SIZE=86400
2043         elif [ ${TIMELEFT} -lt 2678400 ]; then
2044                 UNIT="week"
2045                 SIZE=604800
2046         else
2047                 UNIT="month"
2048                 SIZE=2678400
2049         fi
2050
2051         # Compute the right number of units
2052         NUM=`expr ${TIMELEFT} / ${SIZE}`
2053         if [ ${NUM} != 1 ]; then
2054                 UNIT="${UNIT}s"
2055         fi
2056
2057         # Print the warning
2058         echo
2059         cat <<-EOF
2060                 WARNING: `uname -sr` is approaching its End-of-Life date.
2061                 It is strongly recommended that you upgrade to a newer
2062                 release within the next ${NUM} ${UNIT}.
2063         EOF
2064
2065         # Update the stored time of last warning
2066         echo ${NOWTIME} > lasteolwarn
2067 }
2068
2069 # Do the actual work involved in "fetch" / "cron".
2070 fetch_run () {
2071         workdir_init || return 1
2072
2073         # Prepare the mirror list.
2074         fetch_pick_server_init && fetch_pick_server
2075
2076         # Try to fetch the public key until we run out of servers.
2077         while ! fetch_key; do
2078                 fetch_pick_server || return 1
2079         done
2080
2081         # Try to fetch the metadata index signature ("tag") until we run
2082         # out of available servers; and sanity check the downloaded tag.
2083         while ! fetch_tag; do
2084                 fetch_pick_server || return 1
2085         done
2086         fetch_tagsanity || return 1
2087
2088         # Fetch the latest INDEX-NEW and INDEX-OLD files.
2089         fetch_metadata INDEX-NEW INDEX-OLD || return 1
2090
2091         # Generate filtered INDEX-NEW and INDEX-OLD files containing only
2092         # the lines which (a) belong to components we care about, and (b)
2093         # don't correspond to paths we're explicitly ignoring.
2094         fetch_filter_metadata INDEX-NEW || return 1
2095         fetch_filter_metadata INDEX-OLD || return 1
2096
2097         # Translate /boot/${KERNCONF} into ${KERNELDIR}
2098         fetch_filter_kernel_names INDEX-NEW ${KERNCONF}
2099         fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2100
2101         # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2102         # system and generate an INDEX-PRESENT file.
2103         fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2104
2105         # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2106         # correspond to lines in INDEX-PRESENT with hashes not appearing
2107         # in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2108         # INDEX-PRESENT has type - and there isn't a corresponding entry in
2109         # INDEX-OLD with type -.
2110         fetch_filter_unmodified_notpresent      \
2111             INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null
2112
2113         # For each entry in INDEX-PRESENT of type -, remove any corresponding
2114         # entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2115         # of type - from INDEX-PRESENT.
2116         fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2117
2118         # If ${ALLOWDELETE} != "yes", then remove any entries from
2119         # INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2120         fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2121
2122         # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2123         # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2124         # replace the corresponding line of INDEX-NEW with one having the
2125         # same metadata as the entry in INDEX-PRESENT.
2126         fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2127
2128         # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2129         # no need to update a file if it isn't changing.
2130         fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2131
2132         # Prepare to fetch files: Generate a list of the files we need,
2133         # copy the unmodified files we have into /files/, and generate
2134         # a list of patches to download.
2135         fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2136
2137         # Fetch files.
2138         fetch_files || return 1
2139
2140         # Create and populate install manifest directory; and report what
2141         # updates are available.
2142         fetch_create_manifest || return 1
2143
2144         # Warn about any upcoming EoL
2145         fetch_warn_eol || return 1
2146 }
2147
2148 # If StrictComponents is not "yes", generate a new components list
2149 # with only the components which appear to be installed.
2150 upgrade_guess_components () {
2151         if [ "${STRICTCOMPONENTS}" = "no" ]; then
2152                 # Generate filtered INDEX-ALL with only the components listed
2153                 # in COMPONENTS.
2154                 fetch_filter_metadata_components $1 || return 1
2155
2156                 # Tell the user why his disk is suddenly making lots of noise
2157                 echo -n "Inspecting system... "
2158
2159                 # Look at the files on disk, and assume that a component is
2160                 # supposed to be present if it is more than half-present.
2161                 cut -f 1-3 -d '|' < INDEX-ALL |
2162                     tr '|' ' ' |
2163                     while read C S F; do
2164                         if [ -e ${BASEDIR}/${F} ]; then
2165                                 echo "+ ${C}|${S}"
2166                         fi
2167                         echo "= ${C}|${S}"
2168                     done |
2169                     sort |
2170                     uniq -c |
2171                     sed -E 's,^ +,,' > compfreq
2172                 grep ' = ' compfreq |
2173                     cut -f 1,3 -d ' ' |
2174                     sort -k 2,2 -t ' ' > compfreq.total
2175                 grep ' + ' compfreq |
2176                     cut -f 1,3 -d ' ' |
2177                     sort -k 2,2 -t ' ' > compfreq.present
2178                 join -t ' ' -1 2 -2 2 compfreq.present compfreq.total |
2179                     while read S P T; do
2180                         if [ ${T} -ne 0 -a ${P} -gt `expr ${T} / 2` ]; then
2181                                 echo ${S}
2182                         fi
2183                     done > comp.present
2184                 cut -f 2 -d ' ' < compfreq.total > comp.total
2185                 rm INDEX-ALL compfreq compfreq.total compfreq.present
2186
2187                 # We're done making noise.
2188                 echo "done."
2189
2190                 # Sometimes the kernel isn't installed where INDEX-ALL
2191                 # thinks that it should be: In particular, it is often in
2192                 # /boot/kernel instead of /boot/GENERIC or /boot/SMP.  To
2193                 # deal with this, if "kernel|X" is listed in comp.total
2194                 # (i.e., is a component which would be upgraded if it is
2195                 # found to be present) we will add it to comp.present.
2196                 # If "kernel|<anything>" is in comp.total but "kernel|X" is
2197                 # not, we print a warning -- the user is running a kernel
2198                 # which isn't part of the release.
2199                 KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'`
2200                 grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present
2201
2202                 if grep -qE "^kernel\|" comp.total &&
2203                     ! grep -qE "^kernel\|${KCOMP}\$" comp.total; then
2204                         cat <<-EOF
2205
2206 WARNING: This system is running a "${KCOMP}" kernel, which is not a
2207 kernel configuration distributed as part of FreeBSD ${RELNUM}.
2208 This kernel will not be updated: you MUST update the kernel manually
2209 before running "$0 install".
2210                         EOF
2211                 fi
2212
2213                 # Re-sort the list of installed components and generate
2214                 # the list of non-installed components.
2215                 sort -u < comp.present > comp.present.tmp
2216                 mv comp.present.tmp comp.present
2217                 comm -13 comp.present comp.total > comp.absent
2218
2219                 # Ask the user to confirm that what we have is correct.  To
2220                 # reduce user confusion, translate "X|Y" back to "X/Y" (as
2221                 # subcomponents must be listed in the configuration file).
2222                 echo
2223                 echo -n "The following components of FreeBSD "
2224                 echo "seem to be installed:"
2225                 tr '|' '/' < comp.present |
2226                     fmt -72
2227                 echo
2228                 echo -n "The following components of FreeBSD "
2229                 echo "do not seem to be installed:"
2230                 tr '|' '/' < comp.absent |
2231                     fmt -72
2232                 echo
2233                 continuep || return 1
2234                 echo
2235
2236                 # Suck the generated list of components into ${COMPONENTS}.
2237                 # Note that comp.present.tmp is used due to issues with
2238                 # pipelines and setting variables.
2239                 COMPONENTS=""
2240                 tr '|' '/' < comp.present > comp.present.tmp
2241                 while read C; do
2242                         COMPONENTS="${COMPONENTS} ${C}"
2243                 done < comp.present.tmp
2244
2245                 # Delete temporary files
2246                 rm comp.present comp.present.tmp comp.absent comp.total
2247         fi
2248 }
2249
2250 # If StrictComponents is not "yes", COMPONENTS contains an entry
2251 # corresponding to the currently running kernel, and said kernel
2252 # does not exist in the new release, add "kernel/generic" to the
2253 # list of components.
2254 upgrade_guess_new_kernel () {
2255         if [ "${STRICTCOMPONENTS}" = "no" ]; then
2256                 # Grab the unfiltered metadata file.
2257                 METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
2258                 gunzip -c < files/${METAHASH}.gz > $1.all
2259
2260                 # If "kernel/${KCOMP}" is in ${COMPONENTS} and that component
2261                 # isn't in $1.all, we need to add kernel/generic.
2262                 for C in ${COMPONENTS}; do
2263                         if [ ${C} = "kernel/${KCOMP}" ] &&
2264                             ! grep -qE "^kernel\|${KCOMP}\|" $1.all; then
2265                                 COMPONENTS="${COMPONENTS} kernel/generic"
2266                                 NKERNCONF="GENERIC"
2267                                 cat <<-EOF
2268
2269 WARNING: This system is running a "${KCOMP}" kernel, which is not a
2270 kernel configuration distributed as part of FreeBSD ${RELNUM}.
2271 As part of upgrading to FreeBSD ${RELNUM}, this kernel will be
2272 replaced with a "generic" kernel.
2273                                 EOF
2274                                 continuep || return 1
2275                         fi
2276                 done
2277
2278                 # Don't need this any more...
2279                 rm $1.all
2280         fi
2281 }
2282
2283 # Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2284 # INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2285 upgrade_oldall_to_oldnew () {
2286         # For each ${F}|... which appears in INDEX-ALL but does not appear
2287         # in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD.
2288         cut -f 1 -d '|' < $1 |
2289             sort -u > $1.paths
2290         cut -f 1 -d '|' < $2 |
2291             sort -u |
2292             comm -13 $1.paths - |
2293             lam - -s "|-||||||" |
2294             sort - $1 > $1.tmp
2295         mv $1.tmp $1
2296
2297         # Remove lines from INDEX-OLD which also appear in INDEX-ALL
2298         comm -23 $1 $2 > $1.tmp
2299         mv $1.tmp $1
2300
2301         # Remove lines from INDEX-ALL which have a file name not appearing
2302         # anywhere in INDEX-OLD (since these must be files which haven't
2303         # changed -- if they were new, there would be an entry of type "-").
2304         cut -f 1 -d '|' < $1 |
2305             sort -u > $1.paths
2306         sort -k 1,1 -t '|' < $2 |
2307             join -t '|' - $1.paths |
2308             sort > $2.tmp
2309         rm $1.paths
2310         mv $2.tmp $2
2311
2312         # Rename INDEX-ALL to INDEX-NEW.
2313         mv $2 $3
2314 }
2315
2316 # Helper for upgrade_merge: Return zero true iff the two files differ only
2317 # in the contents of their RCS tags.
2318 samef () {
2319         X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}`
2320         Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}`
2321
2322         if [ $X = $Y ]; then
2323                 return 0;
2324         else
2325                 return 1;
2326         fi
2327 }
2328
2329 # From the list of "old" files in $1, merge changes in $2 with those in $3,
2330 # and update $3 to reflect the hashes of merged files.
2331 upgrade_merge () {
2332         # We only need to do anything if $1 is non-empty.
2333         if [ -s $1 ]; then
2334                 cut -f 1 -d '|' $1 |
2335                     sort > $1-paths
2336
2337                 # Create staging area for merging files
2338                 rm -rf merge/
2339                 while read F; do
2340                         D=`dirname ${F}`
2341                         mkdir -p merge/old/${D}
2342                         mkdir -p merge/${OLDRELNUM}/${D}
2343                         mkdir -p merge/${RELNUM}/${D}
2344                         mkdir -p merge/new/${D}
2345                 done < $1-paths
2346
2347                 # Copy in files
2348                 while read F; do
2349                         # Currently installed file
2350                         V=`look "${F}|" $2 | cut -f 7 -d '|'`
2351                         gunzip < files/${V}.gz > merge/old/${F}
2352
2353                         # Old release
2354                         if look "${F}|" $1 | fgrep -q "|f|"; then
2355                                 V=`look "${F}|" $1 | cut -f 3 -d '|'`
2356                                 gunzip < files/${V}.gz          \
2357                                     > merge/${OLDRELNUM}/${F}
2358                         fi
2359
2360                         # New release
2361                         if look "${F}|" $3 | cut -f 1,2,7 -d '|' |
2362                             fgrep -q "|f|"; then
2363                                 V=`look "${F}|" $3 | cut -f 7 -d '|'`
2364                                 gunzip < files/${V}.gz          \
2365                                     > merge/${RELNUM}/${F}
2366                         fi
2367                 done < $1-paths
2368
2369                 # Attempt to automatically merge changes
2370                 echo -n "Attempting to automatically merge "
2371                 echo -n "changes in files..."
2372                 : > failed.merges
2373                 while read F; do
2374                         # If the file doesn't exist in the new release,
2375                         # the result of "merging changes" is having the file
2376                         # not exist.
2377                         if ! [ -f merge/${RELNUM}/${F} ]; then
2378                                 continue
2379                         fi
2380
2381                         # If the file didn't exist in the old release, we're
2382                         # going to throw away the existing file and hope that
2383                         # the version from the new release is what we want.
2384                         if ! [ -f merge/${OLDRELNUM}/${F} ]; then
2385                                 cp merge/${RELNUM}/${F} merge/new/${F}
2386                                 continue
2387                         fi
2388
2389                         # Some files need special treatment.
2390                         case ${F} in
2391                         /etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
2392                                 # Don't merge these -- we're rebuild them
2393                                 # after updates are installed.
2394                                 cp merge/old/${F} merge/new/${F}
2395                                 ;;
2396                         *)
2397                                 if ! diff3 -E -m -L "current version"   \
2398                                     -L "${OLDRELNUM}" -L "${RELNUM}"    \
2399                                     merge/old/${F}                      \
2400                                     merge/${OLDRELNUM}/${F}             \
2401                                     merge/${RELNUM}/${F}                \
2402                                     > merge/new/${F} 2>/dev/null; then
2403                                         echo ${F} >> failed.merges
2404                                 fi
2405                                 ;;
2406                         esac
2407                 done < $1-paths
2408                 echo " done."
2409
2410                 # Ask the user to handle any files which didn't merge.
2411                 while read F; do
2412                         # If the installed file differs from the version in
2413                         # the old release only due to RCS tag expansion
2414                         # then just use the version in the new release.
2415                         if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
2416                                 cp merge/${RELNUM}/${F} merge/new/${F}
2417                                 continue
2418                         fi
2419
2420                         cat <<-EOF
2421
2422 The following file could not be merged automatically: ${F}
2423 Press Enter to edit this file in ${EDITOR} and resolve the conflicts
2424 manually...
2425                         EOF
2426                         read dummy </dev/tty
2427                         ${EDITOR} `pwd`/merge/new/${F} < /dev/tty
2428                 done < failed.merges
2429                 rm failed.merges
2430
2431                 # Ask the user to confirm that he likes how the result
2432                 # of merging files.
2433                 while read F; do
2434                         # Skip files which haven't changed except possibly
2435                         # in their RCS tags.
2436                         if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
2437                             samef merge/old/${F} merge/new/${F}; then
2438                                 continue
2439                         fi
2440
2441                         # Skip files where the installed file differs from
2442                         # the old file only due to RCS tags.
2443                         if [ -f merge/old/${F} ] &&
2444                             [ -f merge/${OLDRELNUM}/${F} ] &&
2445                             samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
2446                                 continue
2447                         fi
2448
2449                         # Warn about files which are ceasing to exist.
2450                         if ! [ -f merge/new/${F} ]; then
2451                                 cat <<-EOF
2452
2453 The following file will be removed, as it no longer exists in
2454 FreeBSD ${RELNUM}: ${F}
2455                                 EOF
2456                                 continuep < /dev/tty || return 1
2457                                 continue
2458                         fi
2459
2460                         # Print changes for the user's approval.
2461                         cat <<-EOF
2462
2463 The following changes, which occurred between FreeBSD ${OLDRELNUM} and
2464 FreeBSD ${RELNUM} have been merged into ${F}:
2465 EOF
2466                         diff -U 5 -L "current version" -L "new version" \
2467                             merge/old/${F} merge/new/${F} || true
2468                         continuep < /dev/tty || return 1
2469                 done < $1-paths
2470
2471                 # Store merged files.
2472                 while read F; do
2473                         if [ -f merge/new/${F} ]; then
2474                                 V=`${SHA256} -q merge/new/${F}`
2475
2476                                 gzip -c < merge/new/${F} > files/${V}.gz
2477                                 echo "${F}|${V}"
2478                         fi
2479                 done < $1-paths > newhashes
2480
2481                 # Pull lines out from $3 which need to be updated to
2482                 # reflect merged files.
2483                 while read F; do
2484                         look "${F}|" $3
2485                 done < $1-paths > $3-oldlines
2486
2487                 # Update lines to reflect merged files
2488                 join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8          \
2489                     $3-oldlines newhashes > $3-newlines
2490
2491                 # Remove old lines from $3 and add new lines.
2492                 sort $3-oldlines |
2493                     comm -13 - $3 |
2494                     sort - $3-newlines > $3.tmp
2495                 mv $3.tmp $3
2496
2497                 # Clean up
2498                 rm $1-paths newhashes $3-oldlines $3-newlines
2499                 rm -rf merge/
2500         fi
2501
2502         # We're done with merging files.
2503         rm $1
2504 }
2505
2506 # Do the work involved in fetching upgrades to a new release
2507 upgrade_run () {
2508         workdir_init || return 1
2509
2510         # Prepare the mirror list.
2511         fetch_pick_server_init && fetch_pick_server
2512
2513         # Try to fetch the public key until we run out of servers.
2514         while ! fetch_key; do
2515                 fetch_pick_server || return 1
2516         done
2517  
2518         # Try to fetch the metadata index signature ("tag") until we run
2519         # out of available servers; and sanity check the downloaded tag.
2520         while ! fetch_tag; do
2521                 fetch_pick_server || return 1
2522         done
2523         fetch_tagsanity || return 1
2524
2525         # Fetch the INDEX-OLD and INDEX-ALL.
2526         fetch_metadata INDEX-OLD INDEX-ALL || return 1
2527
2528         # If StrictComponents is not "yes", generate a new components list
2529         # with only the components which appear to be installed.
2530         upgrade_guess_components INDEX-ALL || return 1
2531
2532         # Generate filtered INDEX-OLD and INDEX-ALL files containing only
2533         # the components we want and without anything marked as "Ignore".
2534         fetch_filter_metadata INDEX-OLD || return 1
2535         fetch_filter_metadata INDEX-ALL || return 1
2536
2537         # Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
2538         sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
2539         mv INDEX-OLD.tmp INDEX-OLD
2540         rm INDEX-ALL
2541
2542         # Adjust variables for fetching files from the new release.
2543         OLDRELNUM=${RELNUM}
2544         RELNUM=${TARGETRELEASE}
2545         OLDFETCHDIR=${FETCHDIR}
2546         FETCHDIR=${RELNUM}/${ARCH}
2547
2548         # Try to fetch the NEW metadata index signature ("tag") until we run
2549         # out of available servers; and sanity check the downloaded tag.
2550         while ! fetch_tag; do
2551                 fetch_pick_server || return 1
2552         done
2553
2554         # Fetch the new INDEX-ALL.
2555         fetch_metadata INDEX-ALL || return 1
2556
2557         # If StrictComponents is not "yes", COMPONENTS contains an entry
2558         # corresponding to the currently running kernel, and said kernel
2559         # does not exist in the new release, add "kernel/generic" to the
2560         # list of components.
2561         upgrade_guess_new_kernel INDEX-ALL || return 1
2562
2563         # Filter INDEX-ALL to contain only the components we want and without
2564         # anything marked as "Ignore".
2565         fetch_filter_metadata INDEX-ALL || return 1
2566
2567         # Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2568         # INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2569         upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
2570
2571         # Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
2572         fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
2573         fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2574
2575         # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2576         # system and generate an INDEX-PRESENT file.
2577         fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2578
2579         # Based on ${MERGECHANGES}, generate a file tomerge-old with the
2580         # paths and hashes of old versions of files to merge.
2581         fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT tomerge-old
2582
2583         # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2584         # correspond to lines in INDEX-PRESENT with hashes not appearing
2585         # in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2586         # INDEX-PRESENT has type - and there isn't a corresponding entry in
2587         # INDEX-OLD with type -.
2588         fetch_filter_unmodified_notpresent      \
2589             INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
2590
2591         # For each entry in INDEX-PRESENT of type -, remove any corresponding
2592         # entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2593         # of type - from INDEX-PRESENT.
2594         fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2595
2596         # If ${ALLOWDELETE} != "yes", then remove any entries from
2597         # INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2598         fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2599
2600         # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2601         # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2602         # replace the corresponding line of INDEX-NEW with one having the
2603         # same metadata as the entry in INDEX-PRESENT.
2604         fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2605
2606         # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2607         # no need to update a file if it isn't changing.
2608         fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2609
2610         # Fetch "clean" files from the old release for merging changes.
2611         fetch_files_premerge tomerge-old
2612
2613         # Prepare to fetch files: Generate a list of the files we need,
2614         # copy the unmodified files we have into /files/, and generate
2615         # a list of patches to download.
2616         fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2617
2618         # Fetch patches from to-${RELNUM}/${ARCH}/bp/
2619         PATCHDIR=to-${RELNUM}/${ARCH}/bp
2620         fetch_files || return 1
2621
2622         # Merge configuration file changes.
2623         upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
2624
2625         # Create and populate install manifest directory; and report what
2626         # updates are available.
2627         fetch_create_manifest || return 1
2628
2629         # Leave a note behind to tell the "install" command that the kernel
2630         # needs to be installed before the world.
2631         touch ${BDHASH}-install/kernelfirst
2632
2633         # Remind the user that they need to run "freebsd-update install"
2634         # to install the downloaded bits, in case they didn't RTFM.
2635         echo "To install the downloaded upgrades, run \"$0 install\"."
2636 }
2637
2638 # Make sure that all the file hashes mentioned in $@ have corresponding
2639 # gzipped files stored in /files/.
2640 install_verify () {
2641         # Generate a list of hashes
2642         cat $@ |
2643             cut -f 2,7 -d '|' |
2644             grep -E '^f' |
2645             cut -f 2 -d '|' |
2646             sort -u > filelist
2647
2648         # Make sure all the hashes exist
2649         while read HASH; do
2650                 if ! [ -f files/${HASH}.gz ]; then
2651                         echo -n "Update files missing -- "
2652                         echo "this should never happen."
2653                         echo "Re-run '$0 fetch'."
2654                         return 1
2655                 fi
2656         done < filelist
2657
2658         # Clean up
2659         rm filelist
2660 }
2661
2662 # Remove the system immutable flag from files
2663 install_unschg () {
2664         # Generate file list
2665         cat $@ |
2666             cut -f 1 -d '|' > filelist
2667
2668         # Remove flags
2669         while read F; do
2670                 if ! [ -e ${BASEDIR}/${F} ]; then
2671                         continue
2672                 else
2673                         echo ${BASEDIR}/${F}
2674                 fi
2675         done < filelist | xargs chflags noschg || return 1
2676
2677         # Clean up
2678         rm filelist
2679 }
2680
2681 # Decide which directory name to use for kernel backups.
2682 backup_kernel_finddir () {
2683         CNT=0
2684         while true ; do
2685                 # Pathname does not exist, so it is OK use that name
2686                 # for backup directory.
2687                 if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
2688                         return 0
2689                 fi
2690
2691                 # If directory do exist, we only use if it has our
2692                 # marker file.
2693                 if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
2694                         -e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
2695                         return 0
2696                 fi
2697
2698                 # We could not use current directory name, so add counter to
2699                 # the end and try again.
2700                 CNT=$((CNT + 1))
2701                 if [ $CNT -gt 9 ]; then
2702                         echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
2703                         exit 1
2704                 fi
2705                 BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
2706                 BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
2707         done
2708 }
2709
2710 # Backup the current kernel using hardlinks, if not disabled by user.
2711 # Since we delete all files in the directory used for previous backups
2712 # we create a marker file called ".freebsd-update" in the directory so
2713 # we can determine on the next run that the directory was created by
2714 # freebsd-update and we then do not accidentally remove user files in
2715 # the unlikely case that the user has created a directory with a
2716 # conflicting name.
2717 backup_kernel () {
2718         # Only make kernel backup is so configured.
2719         if [ $BACKUPKERNEL != yes ]; then
2720                 return 0
2721         fi
2722
2723         # Decide which directory name to use for kernel backups.
2724         backup_kernel_finddir
2725
2726         # Remove old kernel backup files.  If $BACKUPKERNELDIR was
2727         # "not ours", backup_kernel_finddir would have exited, so
2728         # deleting the directory content is as safe as we can make it.
2729         if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
2730                 rm -fr $BASEDIR/$BACKUPKERNELDIR
2731         fi
2732
2733         # Create directories for backup.
2734         mkdir -p $BASEDIR/$BACKUPKERNELDIR
2735         mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
2736             mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
2737
2738         # Mark the directory as having been created by freebsd-update.
2739         touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
2740         if [ $? -ne 0 ]; then
2741                 echo "Could not create kernel backup directory"
2742                 exit 1
2743         fi
2744
2745         # Disable pathname expansion to be sure *.symbols is not
2746         # expanded.
2747         set -f
2748
2749         # Use find to ignore symbol files, unless disabled by user.
2750         if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
2751                 FINDFILTER=""
2752         else
2753                 FINDFILTER="-a ! -name *.debug -a ! -name *.symbols"
2754         fi
2755
2756         # Backup all the kernel files using hardlinks.
2757         (cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
2758             cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
2759
2760         # Re-enable patchname expansion.
2761         set +f
2762 }
2763
2764 # Install new files
2765 install_from_index () {
2766         # First pass: Do everything apart from setting file flags.  We
2767         # can't set flags yet, because schg inhibits hard linking.
2768         sort -k 1,1 -t '|' $1 |
2769             tr '|' ' ' |
2770             while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2771                 case ${TYPE} in
2772                 d)
2773                         # Create a directory
2774                         install -d -o ${OWNER} -g ${GROUP}              \
2775                             -m ${PERM} ${BASEDIR}/${FPATH}
2776                         ;;
2777                 f)
2778                         if [ -z "${LINK}" ]; then
2779                                 # Create a file, without setting flags.
2780                                 gunzip < files/${HASH}.gz > ${HASH}
2781                                 install -S -o ${OWNER} -g ${GROUP}      \
2782                                     -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
2783                                 rm ${HASH}
2784                         else
2785                                 # Create a hard link.
2786                                 ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
2787                         fi
2788                         ;;
2789                 L)
2790                         # Create a symlink
2791                         ln -sfh ${HASH} ${BASEDIR}/${FPATH}
2792                         ;;
2793                 esac
2794             done
2795
2796         # Perform a second pass, adding file flags.
2797         tr '|' ' ' < $1 |
2798             while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2799                 if [ ${TYPE} = "f" ] &&
2800                     ! [ ${FLAGS} = "0" ]; then
2801                         chflags ${FLAGS} ${BASEDIR}/${FPATH}
2802                 fi
2803             done
2804 }
2805
2806 # Remove files which we want to delete
2807 install_delete () {
2808         # Generate list of new files
2809         cut -f 1 -d '|' < $2 |
2810             sort > newfiles
2811
2812         # Generate subindex of old files we want to nuke
2813         sort -k 1,1 -t '|' $1 |
2814             join -t '|' -v 1 - newfiles |
2815             sort -r -k 1,1 -t '|' |
2816             cut -f 1,2 -d '|' |
2817             tr '|' ' ' > killfiles
2818
2819         # Remove the offending bits
2820         while read FPATH TYPE; do
2821                 case ${TYPE} in
2822                 d)
2823                         rmdir ${BASEDIR}/${FPATH}
2824                         ;;
2825                 f)
2826                         rm ${BASEDIR}/${FPATH}
2827                         ;;
2828                 L)
2829                         rm ${BASEDIR}/${FPATH}
2830                         ;;
2831                 esac
2832         done < killfiles
2833
2834         # Clean up
2835         rm newfiles killfiles
2836 }
2837
2838 # Install new files, delete old files, and update linker.hints
2839 install_files () {
2840         # If we haven't already dealt with the kernel, deal with it.
2841         if ! [ -f $1/kerneldone ]; then
2842                 grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
2843                 grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
2844
2845                 # Backup current kernel before installing a new one
2846                 backup_kernel || return 1
2847
2848                 # Install new files
2849                 install_from_index INDEX-NEW || return 1
2850
2851                 # Remove files which need to be deleted
2852                 install_delete INDEX-OLD INDEX-NEW || return 1
2853
2854                 # Update linker.hints if necessary
2855                 if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2856                         kldxref -R ${BASEDIR}/boot/ 2>/dev/null
2857                 fi
2858
2859                 # We've finished updating the kernel.
2860                 touch $1/kerneldone
2861
2862                 # Do we need to ask for a reboot now?
2863                 if [ -f $1/kernelfirst ] &&
2864                     [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2865                         cat <<-EOF
2866
2867 Kernel updates have been installed.  Please reboot and run
2868 "$0 install" again to finish installing updates.
2869                         EOF
2870                         exit 0
2871                 fi
2872         fi
2873
2874         # If we haven't already dealt with the world, deal with it.
2875         if ! [ -f $1/worlddone ]; then
2876                 # Create any necessary directories first
2877                 grep -vE '^/boot/' $1/INDEX-NEW |
2878                     grep -E '^[^|]+\|d\|' > INDEX-NEW
2879                 install_from_index INDEX-NEW || return 1
2880
2881                 # Install new runtime linker
2882                 grep -vE '^/boot/' $1/INDEX-NEW |
2883                     grep -vE '^[^|]+\|d\|' |
2884                     grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2885                 install_from_index INDEX-NEW || return 1
2886
2887                 # Install new shared libraries next
2888                 grep -vE '^/boot/' $1/INDEX-NEW |
2889                     grep -vE '^[^|]+\|d\|' |
2890                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2891                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2892                 install_from_index INDEX-NEW || return 1
2893
2894                 # Deal with everything else
2895                 grep -vE '^/boot/' $1/INDEX-OLD |
2896                     grep -vE '^[^|]+\|d\|' |
2897                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2898                     grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
2899                 grep -vE '^/boot/' $1/INDEX-NEW |
2900                     grep -vE '^[^|]+\|d\|' |
2901                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2902                     grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2903                 install_from_index INDEX-NEW || return 1
2904                 install_delete INDEX-OLD INDEX-NEW || return 1
2905
2906                 # Rebuild generated pwd files.
2907                 if [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/spwd.db ] ||
2908                     [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/pwd.db ] ||
2909                     [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/passwd ]; then
2910                         pwd_mkdb -d ${BASEDIR}/etc -p ${BASEDIR}/etc/master.passwd
2911                 fi
2912
2913                 # Rebuild /etc/login.conf.db if necessary.
2914                 if [ ${BASEDIR}/etc/login.conf -nt ${BASEDIR}/etc/login.conf.db ]; then
2915                         cap_mkdb ${BASEDIR}/etc/login.conf
2916                 fi
2917
2918                 # Rebuild man page databases, if necessary.
2919                 for D in /usr/share/man /usr/share/openssl/man; do
2920                         if [ ! -d ${BASEDIR}/$D ]; then
2921                                 continue
2922                         fi
2923                         if [ -z "$(find ${BASEDIR}/$D -type f -newer ${BASEDIR}/$D/mandoc.db)" ]; then
2924                                 continue;
2925                         fi
2926                         makewhatis ${BASEDIR}/$D
2927                 done
2928
2929                 # We've finished installing the world and deleting old files
2930                 # which are not shared libraries.
2931                 touch $1/worlddone
2932
2933                 # Do we need to ask the user to portupgrade now?
2934                 grep -vE '^/boot/' $1/INDEX-NEW |
2935                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
2936                     cut -f 1 -d '|' |
2937                     sort > newfiles
2938                 if grep -vE '^/boot/' $1/INDEX-OLD |
2939                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
2940                     cut -f 1 -d '|' |
2941                     sort |
2942                     join -v 1 - newfiles |
2943                     grep -q .; then
2944                         cat <<-EOF
2945
2946 Completing this upgrade requires removing old shared object files.
2947 Please rebuild all installed 3rd party software (e.g., programs
2948 installed from the ports tree) and then run "$0 install"
2949 again to finish installing updates.
2950                         EOF
2951                         rm newfiles
2952                         exit 0
2953                 fi
2954                 rm newfiles
2955         fi
2956
2957         # Remove old shared libraries
2958         grep -vE '^/boot/' $1/INDEX-NEW |
2959             grep -vE '^[^|]+\|d\|' |
2960             grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2961         grep -vE '^/boot/' $1/INDEX-OLD |
2962             grep -vE '^[^|]+\|d\|' |
2963             grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
2964         install_delete INDEX-OLD INDEX-NEW || return 1
2965
2966         # Remove old directories
2967         grep -vE '^/boot/' $1/INDEX-NEW |
2968             grep -E '^[^|]+\|d\|' > INDEX-NEW
2969         grep -vE '^/boot/' $1/INDEX-OLD |
2970             grep -E '^[^|]+\|d\|' > INDEX-OLD
2971         install_delete INDEX-OLD INDEX-NEW || return 1
2972
2973         # Remove temporary files
2974         rm INDEX-OLD INDEX-NEW
2975 }
2976
2977 # Rearrange bits to allow the installed updates to be rolled back
2978 install_setup_rollback () {
2979         # Remove the "reboot after installing kernel", "kernel updated", and
2980         # "finished installing the world" flags if present -- they are
2981         # irrelevant when rolling back updates.
2982         if [ -f ${BDHASH}-install/kernelfirst ]; then
2983                 rm ${BDHASH}-install/kernelfirst
2984                 rm ${BDHASH}-install/kerneldone
2985         fi
2986         if [ -f ${BDHASH}-install/worlddone ]; then
2987                 rm ${BDHASH}-install/worlddone
2988         fi
2989
2990         if [ -L ${BDHASH}-rollback ]; then
2991                 mv ${BDHASH}-rollback ${BDHASH}-install/rollback
2992         fi
2993
2994         mv ${BDHASH}-install ${BDHASH}-rollback
2995 }
2996
2997 # Actually install updates
2998 install_run () {
2999         echo -n "Installing updates..."
3000
3001         # Make sure we have all the files we should have
3002         install_verify ${BDHASH}-install/INDEX-OLD      \
3003             ${BDHASH}-install/INDEX-NEW || return 1
3004
3005         # Remove system immutable flag from files
3006         install_unschg ${BDHASH}-install/INDEX-OLD      \
3007             ${BDHASH}-install/INDEX-NEW || return 1
3008
3009         # Install new files, delete old files, and update linker.hints
3010         install_files ${BDHASH}-install || return 1
3011
3012         # Rearrange bits to allow the installed updates to be rolled back
3013         install_setup_rollback
3014
3015         echo " done."
3016 }
3017
3018 # Rearrange bits to allow the previous set of updates to be rolled back next.
3019 rollback_setup_rollback () {
3020         if [ -L ${BDHASH}-rollback/rollback ]; then
3021                 mv ${BDHASH}-rollback/rollback rollback-tmp
3022                 rm -r ${BDHASH}-rollback/
3023                 rm ${BDHASH}-rollback
3024                 mv rollback-tmp ${BDHASH}-rollback
3025         else
3026                 rm -r ${BDHASH}-rollback/
3027                 rm ${BDHASH}-rollback
3028         fi
3029 }
3030
3031 # Install old files, delete new files, and update linker.hints
3032 rollback_files () {
3033         # Install old shared library files which don't have the same path as
3034         # a new shared library file.
3035         grep -vE '^/boot/' $1/INDEX-NEW |
3036             grep -E '/lib/.*\.so\.[0-9]+\|' |
3037             cut -f 1 -d '|' |
3038             sort > INDEX-NEW.libs.flist
3039         grep -vE '^/boot/' $1/INDEX-OLD |
3040             grep -E '/lib/.*\.so\.[0-9]+\|' |
3041             sort -k 1,1 -t '|' - |
3042             join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
3043         install_from_index INDEX-OLD || return 1
3044
3045         # Deal with files which are neither kernel nor shared library
3046         grep -vE '^/boot/' $1/INDEX-OLD |
3047             grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3048         grep -vE '^/boot/' $1/INDEX-NEW |
3049             grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3050         install_from_index INDEX-OLD || return 1
3051         install_delete INDEX-NEW INDEX-OLD || return 1
3052
3053         # Install any old shared library files which we didn't install above.
3054         grep -vE '^/boot/' $1/INDEX-OLD |
3055             grep -E '/lib/.*\.so\.[0-9]+\|' |
3056             sort -k 1,1 -t '|' - |
3057             join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
3058         install_from_index INDEX-OLD || return 1
3059
3060         # Delete unneeded shared library files
3061         grep -vE '^/boot/' $1/INDEX-OLD |
3062             grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3063         grep -vE '^/boot/' $1/INDEX-NEW |
3064             grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3065         install_delete INDEX-NEW INDEX-OLD || return 1
3066
3067         # Deal with kernel files
3068         grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
3069         grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
3070         install_from_index INDEX-OLD || return 1
3071         install_delete INDEX-NEW INDEX-OLD || return 1
3072         if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
3073                 kldxref -R /boot/ 2>/dev/null
3074         fi
3075
3076         # Remove temporary files
3077         rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
3078 }
3079
3080 # Actually rollback updates
3081 rollback_run () {
3082         echo -n "Uninstalling updates..."
3083
3084         # If there are updates waiting to be installed, remove them; we
3085         # want the user to re-run 'fetch' after rolling back updates.
3086         if [ -L ${BDHASH}-install ]; then
3087                 rm -r ${BDHASH}-install/
3088                 rm ${BDHASH}-install
3089         fi
3090
3091         # Make sure we have all the files we should have
3092         install_verify ${BDHASH}-rollback/INDEX-NEW     \
3093             ${BDHASH}-rollback/INDEX-OLD || return 1
3094
3095         # Remove system immutable flag from files
3096         install_unschg ${BDHASH}-rollback/INDEX-NEW     \
3097             ${BDHASH}-rollback/INDEX-OLD || return 1
3098
3099         # Install old files, delete new files, and update linker.hints
3100         rollback_files ${BDHASH}-rollback || return 1
3101
3102         # Remove the rollback directory and the symlink pointing to it; and
3103         # rearrange bits to allow the previous set of updates to be rolled
3104         # back next.
3105         rollback_setup_rollback
3106
3107         echo " done."
3108 }
3109
3110 # Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences.
3111 IDS_compare () {
3112         # Get all the lines which mismatch in something other than file
3113         # flags.  We ignore file flags because sysinstall doesn't seem to
3114         # set them when it installs FreeBSD; warning about these adds a
3115         # very large amount of noise.
3116         cut -f 1-5,7-8 -d '|' $1 > $1.noflags
3117         sort -k 1,1 -t '|' $1.noflags > $1.sorted
3118         cut -f 1-5,7-8 -d '|' $2 |
3119             comm -13 $1.noflags - |
3120             fgrep -v '|-|||||' |
3121             sort -k 1,1 -t '|' |
3122             join -t '|' $1.sorted - > INDEX-NOTMATCHING
3123
3124         # Ignore files which match IDSIGNOREPATHS.
3125         for X in ${IDSIGNOREPATHS}; do
3126                 grep -E "^${X}" INDEX-NOTMATCHING
3127         done |
3128             sort -u |
3129             comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp
3130         mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
3131
3132         # Go through the lines and print warnings.
3133         local IFS='|'
3134         while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do
3135                 # Warn about different object types.
3136                 if ! [ "${TYPE}" = "${P_TYPE}" ]; then
3137                         echo -n "${FPATH} is a "
3138                         case "${P_TYPE}" in
3139                         f)      echo -n "regular file, "
3140                                 ;;
3141                         d)      echo -n "directory, "
3142                                 ;;
3143                         L)      echo -n "symlink, "
3144                                 ;;
3145                         esac
3146                         echo -n "but should be a "
3147                         case "${TYPE}" in
3148                         f)      echo -n "regular file."
3149                                 ;;
3150                         d)      echo -n "directory."
3151                                 ;;
3152                         L)      echo -n "symlink."
3153                                 ;;
3154                         esac
3155                         echo
3156
3157                         # Skip other tests, since they don't make sense if
3158                         # we're comparing different object types.
3159                         continue
3160                 fi
3161
3162                 # Warn about different owners.
3163                 if ! [ "${OWNER}" = "${P_OWNER}" ]; then
3164                         echo -n "${FPATH} is owned by user id ${P_OWNER}, "
3165                         echo "but should be owned by user id ${OWNER}."
3166                 fi
3167
3168                 # Warn about different groups.
3169                 if ! [ "${GROUP}" = "${P_GROUP}" ]; then
3170                         echo -n "${FPATH} is owned by group id ${P_GROUP}, "
3171                         echo "but should be owned by group id ${GROUP}."
3172                 fi
3173
3174                 # Warn about different permissions.  We do not warn about
3175                 # different permissions on symlinks, since some archivers
3176                 # don't extract symlink permissions correctly and they are
3177                 # ignored anyway.
3178                 if ! [ "${PERM}" = "${P_PERM}" ] &&
3179                     ! [ "${TYPE}" = "L" ]; then
3180                         echo -n "${FPATH} has ${P_PERM} permissions, "
3181                         echo "but should have ${PERM} permissions."
3182                 fi
3183
3184                 # Warn about different file hashes / symlink destinations.
3185                 if ! [ "${HASH}" = "${P_HASH}" ]; then
3186                         if [ "${TYPE}" = "L" ]; then
3187                                 echo -n "${FPATH} is a symlink to ${P_HASH}, "
3188                                 echo "but should be a symlink to ${HASH}."
3189                         fi
3190                         if [ "${TYPE}" = "f" ]; then
3191                                 echo -n "${FPATH} has SHA256 hash ${P_HASH}, "
3192                                 echo "but should have SHA256 hash ${HASH}."
3193                         fi
3194                 fi
3195
3196                 # We don't warn about different hard links, since some
3197                 # some archivers break hard links, and as long as the
3198                 # underlying data is correct they really don't matter.
3199         done < INDEX-NOTMATCHING
3200
3201         # Clean up
3202         rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING
3203 }
3204
3205 # Do the work involved in comparing the system to a "known good" index
3206 IDS_run () {
3207         workdir_init || return 1
3208
3209         # Prepare the mirror list.
3210         fetch_pick_server_init && fetch_pick_server
3211
3212         # Try to fetch the public key until we run out of servers.
3213         while ! fetch_key; do
3214                 fetch_pick_server || return 1
3215         done
3216  
3217         # Try to fetch the metadata index signature ("tag") until we run
3218         # out of available servers; and sanity check the downloaded tag.
3219         while ! fetch_tag; do
3220                 fetch_pick_server || return 1
3221         done
3222         fetch_tagsanity || return 1
3223
3224         # Fetch INDEX-OLD and INDEX-ALL.
3225         fetch_metadata INDEX-OLD INDEX-ALL || return 1
3226
3227         # Generate filtered INDEX-OLD and INDEX-ALL files containing only
3228         # the components we want and without anything marked as "Ignore".
3229         fetch_filter_metadata INDEX-OLD || return 1
3230         fetch_filter_metadata INDEX-ALL || return 1
3231
3232         # Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL.
3233         sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp
3234         mv INDEX-ALL.tmp INDEX-ALL
3235         rm INDEX-OLD
3236
3237         # Translate /boot/${KERNCONF} to ${KERNELDIR}
3238         fetch_filter_kernel_names INDEX-ALL ${KERNCONF}
3239
3240         # Inspect the system and generate an INDEX-PRESENT file.
3241         fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1
3242
3243         # Compare INDEX-ALL and INDEX-PRESENT and print warnings about any
3244         # differences.
3245         IDS_compare INDEX-ALL INDEX-PRESENT
3246 }
3247
3248 #### Main functions -- call parameter-handling and core functions
3249
3250 # Using the command line, configuration file, and defaults,
3251 # set all the parameters which are needed later.
3252 get_params () {
3253         init_params
3254         parse_cmdline $@
3255         parse_conffile
3256         default_params
3257 }
3258
3259 # Fetch command.  Make sure that we're being called
3260 # interactively, then run fetch_check_params and fetch_run
3261 cmd_fetch () {
3262         if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
3263                 echo -n "`basename $0` fetch should not "
3264                 echo "be run non-interactively."
3265                 echo "Run `basename $0` cron instead."
3266                 exit 1
3267         fi
3268         fetch_check_params
3269         fetch_run || exit 1
3270         ISFETCHED=1
3271 }
3272
3273 # Cron command.  Make sure the parameters are sensible; wait
3274 # rand(3600) seconds; then fetch updates.  While fetching updates,
3275 # send output to a temporary file; only print that file if the
3276 # fetching failed.
3277 cmd_cron () {
3278         fetch_check_params
3279         sleep `jot -r 1 0 3600`
3280
3281         TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
3282         if ! fetch_run >> ${TMPFILE} ||
3283             ! grep -q "No updates needed" ${TMPFILE} ||
3284             [ ${VERBOSELEVEL} = "debug" ]; then
3285                 mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
3286         fi
3287
3288         rm ${TMPFILE}
3289 }
3290
3291 # Fetch files for upgrading to a new release.
3292 cmd_upgrade () {
3293         upgrade_check_params
3294         upgrade_run || exit 1
3295 }
3296
3297 # Install downloaded updates.
3298 cmd_install () {
3299         install_check_params
3300         install_run || exit 1
3301 }
3302
3303 # Rollback most recently installed updates.
3304 cmd_rollback () {
3305         rollback_check_params
3306         rollback_run || exit 1
3307 }
3308
3309 # Compare system against a "known good" index.
3310 cmd_IDS () {
3311         IDS_check_params
3312         IDS_run || exit 1
3313 }
3314
3315 #### Entry point
3316
3317 # Make sure we find utilities from the base system
3318 export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
3319
3320 # Set a pager if the user doesn't
3321 if [ -z "$PAGER" ]; then
3322         PAGER=/usr/bin/less
3323 fi
3324
3325 # Set LC_ALL in order to avoid problems with character ranges like [A-Z].
3326 export LC_ALL=C
3327
3328 get_params $@
3329 for COMMAND in ${COMMANDS}; do
3330         cmd_${COMMAND}
3331 done