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