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