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