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