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