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