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