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