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