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