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