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