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