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