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