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