]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.sbin/freebsd-update/freebsd-update.sh
ena: Upgrade ena-com to freebsd v2.7.0
[FreeBSD/FreeBSD.git] / usr.sbin / freebsd-update / freebsd-update.sh
1 #!/bin/sh
2
3 #-
4 # SPDX-License-Identifier: BSD-2-Clause
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 #### Usage function -- called from command-line handling code.
31
32 # Usage instructions.  Options not listed:
33 # --debug       -- don't filter output from utilities
34 # --no-stats    -- don't show progress statistics while fetching files
35 usage () {
36         cat <<EOF
37 usage: `basename $0` [options] command ...
38
39 Options:
40   -b basedir   -- Operate on a system mounted at basedir
41                   (default: /)
42   -d workdir   -- Store working files in workdir
43                   (default: /var/db/freebsd-update/)
44   -f conffile  -- Read configuration options from conffile
45                   (default: /etc/freebsd-update.conf)
46   -F           -- Force a fetch operation to proceed in the
47                   case of an unfinished upgrade
48   -j jail      -- Operate on the given jail specified by jid or name
49   -k KEY       -- Trust an RSA key with SHA256 hash of KEY
50   -r release   -- Target for upgrade (e.g., 13.2-RELEASE)
51   -s server    -- Server from which to fetch updates
52                   (default: update.FreeBSD.org)
53   -t address   -- Mail output of cron command, if any, to address
54                   (default: root)
55   --not-running-from-cron
56                -- Run without a tty, for use by automated tools
57   --currently-running release
58                -- Update as if currently running this release
59 Commands:
60   fetch        -- Fetch updates from server
61   cron         -- Sleep rand(3600) seconds, fetch updates, and send an
62                   email if updates were found
63   upgrade      -- Fetch upgrades to FreeBSD version specified via -r option
64   updatesready -- Check if there are fetched updates ready to install
65   install      -- Install downloaded updates or upgrades
66   rollback     -- Uninstall most recently installed updates
67   IDS          -- Compare the system against an index of "known good" files
68   showconfig   -- Show configuration
69 EOF
70         exit 0
71 }
72
73 #### Configuration processing functions
74
75 #-
76 # Configuration options are set in the following order of priority:
77 # 1. Command line options
78 # 2. Configuration file options
79 # 3. Default options
80 # In addition, certain options (e.g., IgnorePaths) can be specified multiple
81 # times and (as long as these are all in the same place, e.g., inside the
82 # configuration file) they will accumulate.  Finally, because the path to the
83 # configuration file can be specified at the command line, the entire command
84 # line must be processed before we start reading the configuration file.
85 #
86 # Sound like a mess?  It is.  Here's how we handle this:
87 # 1. Initialize CONFFILE and all the options to "".
88 # 2. Process the command line.  Throw an error if a non-accumulating option
89 #    is specified twice.
90 # 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
91 # 4. For all the configuration options X, set X_saved to X.
92 # 5. Initialize all the options to "".
93 # 6. Read CONFFILE line by line, parsing options.
94 # 7. For each configuration option X, set X to X_saved iff X_saved is not "".
95 # 8. Repeat steps 4-7, except setting options to their default values at (6).
96
97 CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
98     KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
99     BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
100     IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
101
102 # Set all the configuration options to "".
103 nullconfig () {
104         for X in ${CONFIGOPTIONS}; do
105                 eval ${X}=""
106         done
107 }
108
109 # For each configuration option X, set X_saved to X.
110 saveconfig () {
111         for X in ${CONFIGOPTIONS}; do
112                 eval ${X}_saved=\$${X}
113         done
114 }
115
116 # For each configuration option X, set X to X_saved if X_saved is not "".
117 mergeconfig () {
118         for X in ${CONFIGOPTIONS}; do
119                 eval _=\$${X}_saved
120                 if ! [ -z "${_}" ]; then
121                         eval ${X}=\$${X}_saved
122                 fi
123         done
124 }
125
126 # Set the trusted keyprint.
127 config_KeyPrint () {
128         if [ -z ${KEYPRINT} ]; then
129                 KEYPRINT=$1
130         else
131                 return 1
132         fi
133 }
134
135 # Set the working directory.
136 config_WorkDir () {
137         if [ -z ${WORKDIR} ]; then
138                 WORKDIR=$1
139         else
140                 return 1
141         fi
142 }
143
144 # Set the name of the server (pool) from which to fetch updates
145 config_ServerName () {
146         if [ -z ${SERVERNAME} ]; then
147                 SERVERNAME=$1
148         else
149                 return 1
150         fi
151 }
152
153 # Set the address to which 'cron' output will be mailed.
154 config_MailTo () {
155         if [ -z ${MAILTO} ]; then
156                 MAILTO=$1
157         else
158                 return 1
159         fi
160 }
161
162 # Set whether FreeBSD Update is allowed to add files (or directories, or
163 # symlinks) which did not previously exist.
164 config_AllowAdd () {
165         if [ -z ${ALLOWADD} ]; then
166                 case $1 in
167                 [Yy][Ee][Ss])
168                         ALLOWADD=yes
169                         ;;
170                 [Nn][Oo])
171                         ALLOWADD=no
172                         ;;
173                 *)
174                         return 1
175                         ;;
176                 esac
177         else
178                 return 1
179         fi
180 }
181
182 # Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
183 config_AllowDelete () {
184         if [ -z ${ALLOWDELETE} ]; then
185                 case $1 in
186                 [Yy][Ee][Ss])
187                         ALLOWDELETE=yes
188                         ;;
189                 [Nn][Oo])
190                         ALLOWDELETE=no
191                         ;;
192                 *)
193                         return 1
194                         ;;
195                 esac
196         else
197                 return 1
198         fi
199 }
200
201 # Set whether FreeBSD Update should keep existing inode ownership,
202 # permissions, and flags, in the event that they have been modified locally
203 # after the release.
204 config_KeepModifiedMetadata () {
205         if [ -z ${KEEPMODIFIEDMETADATA} ]; then
206                 case $1 in
207                 [Yy][Ee][Ss])
208                         KEEPMODIFIEDMETADATA=yes
209                         ;;
210                 [Nn][Oo])
211                         KEEPMODIFIEDMETADATA=no
212                         ;;
213                 *)
214                         return 1
215                         ;;
216                 esac
217         else
218                 return 1
219         fi
220 }
221
222 # Add to the list of components which should be kept updated.
223 config_Components () {
224         for C in $@; do
225                 COMPONENTS="${COMPONENTS} ${C}"
226         done
227 }
228
229 # Remove src component from list if it isn't installed
230 finalize_components_config () {
231         COMPONENTS=""
232         for C in $@; do
233                 if [ "$C" = "src" ]; then
234                         if [ -e "${BASEDIR}/usr/src/COPYRIGHT" ]; then
235                                 COMPONENTS="${COMPONENTS} ${C}"
236                         else
237                                 echo "src component not installed, skipped"
238                         fi
239                 else
240                         COMPONENTS="${COMPONENTS} ${C}"
241                 fi
242         done
243 }
244
245 # Add to the list of paths under which updates will be ignored.
246 config_IgnorePaths () {
247         for C in $@; do
248                 IGNOREPATHS="${IGNOREPATHS} ${C}"
249         done
250 }
251
252 # Add to the list of paths which IDS should ignore.
253 config_IDSIgnorePaths () {
254         for C in $@; do
255                 IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}"
256         done
257 }
258
259 # Add to the list of paths within which updates will be performed only if the
260 # file on disk has not been modified locally.
261 config_UpdateIfUnmodified () {
262         for C in $@; do
263                 UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
264         done
265 }
266
267 # Add to the list of paths within which updates to text files will be merged
268 # instead of overwritten.
269 config_MergeChanges () {
270         for C in $@; do
271                 MERGECHANGES="${MERGECHANGES} ${C}"
272         done
273 }
274
275 # Work on a FreeBSD installation mounted under $1
276 config_BaseDir () {
277         if [ -z ${BASEDIR} ]; then
278                 BASEDIR=$1
279         else
280                 return 1
281         fi
282 }
283
284 # When fetching upgrades, should we assume the user wants exactly the
285 # components listed in COMPONENTS, rather than trying to guess based on
286 # what's currently installed?
287 config_StrictComponents () {
288         if [ -z ${STRICTCOMPONENTS} ]; then
289                 case $1 in
290                 [Yy][Ee][Ss])
291                         STRICTCOMPONENTS=yes
292                         ;;
293                 [Nn][Oo])
294                         STRICTCOMPONENTS=no
295                         ;;
296                 *)
297                         return 1
298                         ;;
299                 esac
300         else
301                 return 1
302         fi
303 }
304
305 # Upgrade to FreeBSD $1
306 config_TargetRelease () {
307         if [ -z ${TARGETRELEASE} ]; then
308                 TARGETRELEASE=$1
309         else
310                 return 1
311         fi
312         if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
313                 TARGETRELEASE="${TARGETRELEASE}-RELEASE"
314         fi
315 }
316
317 # Pretend current release is FreeBSD $1
318 config_SourceRelease () {
319         UNAME_r=$1
320         if echo ${UNAME_r} | grep -qE '^[0-9.]+$'; then
321                 UNAME_r="${UNAME_r}-RELEASE"
322         fi
323         export UNAME_r
324 }
325
326 # Get the Jail's path and the version of its installed userland
327 config_TargetJail () {
328         JAIL=$1
329         UNAME_r=$(freebsd-version -j ${JAIL})
330         BASEDIR=$(jls -j ${JAIL} -h path | awk 'NR == 2 {print}')
331         if [ -z ${BASEDIR} ] || [ -z ${UNAME_r} ]; then
332                 echo "The specified jail either doesn't exist or" \
333                       "does not have freebsd-version."
334                 exit 1
335         fi
336         export UNAME_r
337 }
338
339 # Define what happens to output of utilities
340 config_VerboseLevel () {
341         if [ -z ${VERBOSELEVEL} ]; then
342                 case $1 in
343                 [Dd][Ee][Bb][Uu][Gg])
344                         VERBOSELEVEL=debug
345                         ;;
346                 [Nn][Oo][Ss][Tt][Aa][Tt][Ss])
347                         VERBOSELEVEL=nostats
348                         ;;
349                 [Ss][Tt][Aa][Tt][Ss])
350                         VERBOSELEVEL=stats
351                         ;;
352                 *)
353                         return 1
354                         ;;
355                 esac
356         else
357                 return 1
358         fi
359 }
360
361 config_BackupKernel () {
362         if [ -z ${BACKUPKERNEL} ]; then
363                 case $1 in
364                 [Yy][Ee][Ss])
365                         BACKUPKERNEL=yes
366                         ;;
367                 [Nn][Oo])
368                         BACKUPKERNEL=no
369                         ;;
370                 *)
371                         return 1
372                         ;;
373                 esac
374         else
375                 return 1
376         fi
377 }
378
379 config_BackupKernelDir () {
380         if [ -z ${BACKUPKERNELDIR} ]; then
381                 if [ -z "$1" ]; then
382                         echo "BackupKernelDir set to empty dir"
383                         return 1
384                 fi
385
386                 # We check for some paths which would be extremely odd
387                 # to use, but which could cause a lot of problems if
388                 # used.
389                 case $1 in
390                 /|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
391                         echo "BackupKernelDir set to invalid path $1"
392                         return 1
393                         ;;
394                 /*)
395                         BACKUPKERNELDIR=$1
396                         ;;
397                 *)
398                         echo "BackupKernelDir ($1) is not an absolute path"
399                         return 1
400                         ;;
401                 esac
402         else
403                 return 1
404         fi
405 }
406
407 config_BackupKernelSymbolFiles () {
408         if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
409                 case $1 in
410                 [Yy][Ee][Ss])
411                         BACKUPKERNELSYMBOLFILES=yes
412                         ;;
413                 [Nn][Oo])
414                         BACKUPKERNELSYMBOLFILES=no
415                         ;;
416                 *)
417                         return 1
418                         ;;
419                 esac
420         else
421                 return 1
422         fi
423 }
424
425 config_CreateBootEnv () {
426         if [ -z ${BOOTENV} ]; then
427                 case $1 in
428                 [Yy][Ee][Ss])
429                         BOOTENV=yes
430                         ;;
431                 [Nn][Oo])
432                         BOOTENV=no
433                         ;;
434                 *)
435                         return 1
436                         ;;
437                 esac
438         else
439                 return 1
440         fi
441 }
442 # Handle one line of configuration
443 configline () {
444         if [ $# -eq 0 ]; then
445                 return
446         fi
447
448         OPT=$1
449         shift
450         config_${OPT} $@
451 }
452
453 #### Parameter handling functions.
454
455 # Initialize parameters to null, just in case they're
456 # set in the environment.
457 init_params () {
458         # Configration settings
459         nullconfig
460
461         # No configuration file set yet
462         CONFFILE=""
463
464         # No commands specified yet
465         COMMANDS=""
466
467         # Force fetch to proceed
468         FORCEFETCH=0
469
470         # Run without a TTY
471         NOTTYOK=0
472
473         # Fetched first in a chain of commands
474         ISFETCHED=0
475 }
476
477 # Parse the command line
478 parse_cmdline () {
479         while [ $# -gt 0 ]; do
480                 case "$1" in
481                 # Location of configuration file
482                 -f)
483                         if [ $# -eq 1 ]; then usage; fi
484                         if [ ! -z "${CONFFILE}" ]; then usage; fi
485                         shift; CONFFILE="$1"
486                         ;;
487                 -F)
488                         FORCEFETCH=1
489                         ;;
490                 --not-running-from-cron)
491                         NOTTYOK=1
492                         ;;
493                 --currently-running)
494                         shift
495                         config_SourceRelease $1 || usage
496                         ;;
497
498                 # Configuration file equivalents
499                 -b)
500                         if [ $# -eq 1 ]; then usage; fi; shift
501                         config_BaseDir $1 || usage
502                         ;;
503                 -d)
504                         if [ $# -eq 1 ]; then usage; fi; shift
505                         config_WorkDir $1 || usage
506                         ;;
507                 -j)
508                         if [ $# -eq 1 ]; then usage; fi; shift
509                         config_TargetJail $1 || usage
510                         ;;
511                 -k)
512                         if [ $# -eq 1 ]; then usage; fi; shift
513                         config_KeyPrint $1 || usage
514                         ;;
515                 -s)
516                         if [ $# -eq 1 ]; then usage; fi; shift
517                         config_ServerName $1 || usage
518                         ;;
519                 -r)
520                         if [ $# -eq 1 ]; then usage; fi; shift
521                         config_TargetRelease $1 || usage
522                         ;;
523                 -t)
524                         if [ $# -eq 1 ]; then usage; fi; shift
525                         config_MailTo $1 || usage
526                         ;;
527                 -v)
528                         if [ $# -eq 1 ]; then usage; fi; shift
529                         config_VerboseLevel $1 || usage
530                         ;;
531
532                 # Aliases for "-v debug" and "-v nostats"
533                 --debug)
534                         config_VerboseLevel debug || usage
535                         ;;
536                 --no-stats)
537                         config_VerboseLevel nostats || usage
538                         ;;
539
540                 # Commands
541                 cron | fetch | upgrade | updatesready | install | rollback |\
542                 IDS | showconfig)
543                         COMMANDS="${COMMANDS} $1"
544                         ;;
545
546                 # Anything else is an error
547                 *)
548                         usage
549                         ;;
550                 esac
551                 shift
552         done
553
554         # Make sure we have at least one command
555         if [ -z "${COMMANDS}" ]; then
556                 usage
557         fi
558 }
559
560 # Parse the configuration file
561 parse_conffile () {
562         # If a configuration file was specified on the command line, check
563         # that it exists and is readable.
564         if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
565                 echo -n "File does not exist "
566                 echo -n "or is not readable: "
567                 echo ${CONFFILE}
568                 exit 1
569         fi
570
571         # If a configuration file was not specified on the command line,
572         # use the default configuration file path.  If that default does
573         # not exist, give up looking for any configuration.
574         if [ -z "${CONFFILE}" ]; then
575                 CONFFILE="/etc/freebsd-update.conf"
576                 if [ ! -r "${CONFFILE}" ]; then
577                         return
578                 fi
579         fi
580
581         # Save the configuration options specified on the command line, and
582         # clear all the options in preparation for reading the config file.
583         saveconfig
584         nullconfig
585
586         # Read the configuration file.  Anything after the first '#' is
587         # ignored, and any blank lines are ignored.
588         L=0
589         while read LINE; do
590                 L=$(($L + 1))
591                 LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
592                 if ! configline ${LINEX}; then
593                         echo "Error processing configuration file, line $L:"
594                         echo "==> ${LINE}"
595                         exit 1
596                 fi
597         done < ${CONFFILE}
598
599         # Merge the settings read from the configuration file with those
600         # provided at the command line.
601         mergeconfig
602 }
603
604 # Provide some default parameters
605 default_params () {
606         # Save any parameters already configured, and clear the slate
607         saveconfig
608         nullconfig
609
610         # Default configurations
611         config_WorkDir /var/db/freebsd-update
612         config_MailTo root
613         config_AllowAdd yes
614         config_AllowDelete yes
615         config_KeepModifiedMetadata yes
616         config_BaseDir /
617         config_VerboseLevel stats
618         config_StrictComponents no
619         config_BackupKernel yes
620         config_BackupKernelDir /boot/kernel.old
621         config_BackupKernelSymbolFiles no
622         config_CreateBootEnv yes
623
624         # Merge these defaults into the earlier-configured settings
625         mergeconfig
626 }
627
628 # Set utility output filtering options, based on ${VERBOSELEVEL}
629 fetch_setup_verboselevel () {
630         case ${VERBOSELEVEL} in
631         debug)
632                 QUIETREDIR="/dev/stderr"
633                 QUIETFLAG=" "
634                 STATSREDIR="/dev/stderr"
635                 DDSTATS=".."
636                 XARGST="-t"
637                 NDEBUG=" "
638                 ;;
639         nostats)
640                 QUIETREDIR=""
641                 QUIETFLAG=""
642                 STATSREDIR="/dev/null"
643                 DDSTATS=".."
644                 XARGST=""
645                 NDEBUG=""
646                 ;;
647         stats)
648                 QUIETREDIR="/dev/null"
649                 QUIETFLAG="-q"
650                 STATSREDIR="/dev/stdout"
651                 DDSTATS=""
652                 XARGST=""
653                 NDEBUG="-n"
654                 ;;
655         esac
656 }
657
658 # Perform sanity checks and set some final parameters
659 # in preparation for fetching files.  Figure out which
660 # set of updates should be downloaded: If the user is
661 # running *-p[0-9]+, strip off the last part; if the
662 # user is running -SECURITY, call it -RELEASE.  Chdir
663 # into the working directory.
664 fetchupgrade_check_params () {
665         export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
666
667         _SERVERNAME_z=\
668 "SERVERNAME must be given via command line or configuration file."
669         _KEYPRINT_z="Key must be given via -k option or configuration file."
670         _KEYPRINT_bad="Invalid key fingerprint: "
671         _WORKDIR_bad="Directory does not exist or is not writable: "
672         _WORKDIR_bad2="Directory is not on a persistent filesystem: "
673
674         if [ -z "${SERVERNAME}" ]; then
675                 echo -n "`basename $0`: "
676                 echo "${_SERVERNAME_z}"
677                 exit 1
678         fi
679         if [ -z "${KEYPRINT}" ]; then
680                 echo -n "`basename $0`: "
681                 echo "${_KEYPRINT_z}"
682                 exit 1
683         fi
684         if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
685                 echo -n "`basename $0`: "
686                 echo -n "${_KEYPRINT_bad}"
687                 echo ${KEYPRINT}
688                 exit 1
689         fi
690         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
691                 echo -n "`basename $0`: "
692                 echo -n "${_WORKDIR_bad}"
693                 echo ${WORKDIR}
694                 exit 1
695         fi
696         case `df -T ${WORKDIR}` in */dev/md[0-9]* | *tmpfs*)
697                 echo -n "`basename $0`: "
698                 echo -n "${_WORKDIR_bad2}"
699                 echo ${WORKDIR}
700                 exit 1
701                 ;;
702         esac
703         chmod 700 ${WORKDIR}
704         cd ${WORKDIR} || exit 1
705
706         # Generate release number.  The s/SECURITY/RELEASE/ bit exists
707         # to provide an upgrade path for FreeBSD Update 1.x users, since
708         # the kernels provided by FreeBSD Update 1.x are always labelled
709         # as X.Y-SECURITY.
710         RELNUM=`uname -r |
711             sed -E 's,-p[0-9]+,,' |
712             sed -E 's,-SECURITY,-RELEASE,'`
713         ARCH=`uname -m`
714         FETCHDIR=${RELNUM}/${ARCH}
715         PATCHDIR=${RELNUM}/${ARCH}/bp
716
717         # Disallow upgrade from a version that is not a release
718         case ${RELNUM} in
719         *-RELEASE | *-ALPHA*  | *-BETA* | *-RC*)
720                 ;;
721         *)
722                 echo -n "`basename $0`: "
723                 cat <<- EOF
724                         Cannot upgrade from a version that is not a release
725                         (including alpha, beta and release candidates)
726                         using `basename $0`. Instead, FreeBSD can be directly
727                         upgraded by source or upgraded to a RELEASE/RELENG version
728                         prior to running `basename $0`.
729                         Currently running: ${RELNUM}
730                 EOF
731                 exit 1
732                 ;;
733         esac
734
735         # Figure out what directory contains the running kernel
736         BOOTFILE=`sysctl -n kern.bootfile`
737         KERNELDIR=${BOOTFILE%/kernel}
738         if ! [ -d ${KERNELDIR} ]; then
739                 echo "Cannot identify running kernel"
740                 exit 1
741         fi
742
743         # Figure out what kernel configuration is running.  We start with
744         # the output of `uname -i`, and then make the following adjustments:
745         # 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
746         # file says "ident SMP-GENERIC", I don't know...
747         # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
748         # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
749         # we're running an SMP kernel.  This mis-identification is a bug
750         # which was fixed in 6.2-STABLE.
751         KERNCONF=`uname -i`
752         if [ ${KERNCONF} = "SMP-GENERIC" ]; then
753                 KERNCONF=SMP
754         fi
755         if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
756                 if sysctl kern.version | grep -qE '/SMP$'; then
757                         KERNCONF=SMP
758                 fi
759         fi
760
761         # Define some paths
762         BSPATCH=/usr/bin/bspatch
763         SHA256=/sbin/sha256
764         PHTTPGET=/usr/libexec/phttpget
765
766         # Set up variables relating to VERBOSELEVEL
767         fetch_setup_verboselevel
768
769         # Construct a unique name from ${BASEDIR}
770         BDHASH=`echo ${BASEDIR} | sha256 -q`
771 }
772
773 # Perform sanity checks etc. before fetching updates.
774 fetch_check_params () {
775         fetchupgrade_check_params
776
777         if ! [ -z "${TARGETRELEASE}" ]; then
778                 echo -n "`basename $0`: "
779                 echo -n "-r option is meaningless with 'fetch' command.  "
780                 echo "(Did you mean 'upgrade' instead?)"
781                 exit 1
782         fi
783
784         # Check that we have updates ready to install
785         if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
786                 echo "You have a partially completed upgrade pending"
787                 echo "Run '$0 install' first."
788                 echo "Run '$0 fetch -F' to proceed anyway."
789                 exit 1
790         fi
791 }
792
793 # Perform sanity checks etc. before fetching upgrades.
794 upgrade_check_params () {
795         fetchupgrade_check_params
796
797         # Unless set otherwise, we're upgrading to the same kernel config.
798         NKERNCONF=${KERNCONF}
799
800         # We need TARGETRELEASE set
801         _TARGETRELEASE_z="Release target must be specified via -r option."
802         if [ -z "${TARGETRELEASE}" ]; then
803                 echo -n "`basename $0`: "
804                 echo "${_TARGETRELEASE_z}"
805                 exit 1
806         fi
807
808         # The target release should be != the current release.
809         if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
810                 echo -n "`basename $0`: "
811                 echo "Cannot upgrade from ${RELNUM} to itself"
812                 exit 1
813         fi
814
815         # Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
816         if [ "${ALLOWADD}" = "no" ]; then
817                 echo -n "`basename $0`: "
818                 echo -n "WARNING: \"AllowAdd no\" is a bad idea "
819                 echo "when upgrading between releases."
820                 echo
821         fi
822         if [ "${ALLOWDELETE}" = "no" ]; then
823                 echo -n "`basename $0`: "
824                 echo -n "WARNING: \"AllowDelete no\" is a bad idea "
825                 echo "when upgrading between releases."
826                 echo
827         fi
828
829         # Set EDITOR to /usr/bin/vi if it isn't already set
830         : ${EDITOR:='/usr/bin/vi'}
831 }
832
833 # Perform sanity checks and set some final parameters in
834 # preparation for installing updates.
835 install_check_params () {
836         # Check that we are root.  All sorts of things won't work otherwise.
837         if [ `id -u` != 0 ]; then
838                 echo "You must be root to run this."
839                 exit 1
840         fi
841
842         # Check that securelevel <= 0.  Otherwise we can't update schg files.
843         if [ `sysctl -n kern.securelevel` -gt 0 ]; then
844                 echo "Updates cannot be installed when the system securelevel"
845                 echo "is greater than zero."
846                 exit 1
847         fi
848
849         # Check that we have a working directory
850         _WORKDIR_bad="Directory does not exist or is not writable: "
851         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
852                 echo -n "`basename $0`: "
853                 echo -n "${_WORKDIR_bad}"
854                 echo ${WORKDIR}
855                 exit 1
856         fi
857         cd ${WORKDIR} || exit 1
858
859         # Construct a unique name from ${BASEDIR}
860         BDHASH=`echo ${BASEDIR} | sha256 -q`
861
862         # Check that we have updates ready to install
863         if ! [ -L ${BDHASH}-install ]; then
864                 echo "No updates are available to install."
865                 if [ $ISFETCHED -eq 0 ]; then
866                         echo "Run '$0 fetch' first."
867                         exit 2
868                 fi
869                 exit 0
870         fi
871         if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
872             ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
873                 echo "Update manifest is corrupt -- this should never happen."
874                 echo "Re-run '$0 fetch'."
875                 exit 1
876         fi
877
878         # Figure out what directory contains the running kernel
879         BOOTFILE=`sysctl -n kern.bootfile`
880         KERNELDIR=${BOOTFILE%/kernel}
881         if ! [ -d ${KERNELDIR} ]; then
882                 echo "Cannot identify running kernel"
883                 exit 1
884         fi
885 }
886
887 # Creates a new boot environment
888 install_create_be () {
889         # Figure out if we're running in a jail and return if we are
890         if [ `sysctl -n security.jail.jailed` = 1 ]; then
891                 return 1
892         fi
893         # Operating on roots that aren't located at / will, more often than not,
894         # not touch the boot environment.
895         if [ "$BASEDIR" != "/" ]; then
896                 return 1
897         fi
898         # Create a boot environment if enabled
899         if [ ${BOOTENV} = yes ]; then
900                 bectl check 2>/dev/null
901                 case $? in
902                         0)
903                                 # Boot environment are supported
904                                 CREATEBE=yes
905                                 ;;
906                         255)
907                                 # Boot environments are not supported
908                                 CREATEBE=no
909                                 ;;
910                         *)
911                                 # If bectl returns an unexpected exit code, don't create a BE
912                                 CREATEBE=no
913                                 ;;
914                 esac
915                 if [ ${CREATEBE} = yes ]; then
916                         echo -n "Creating snapshot of existing boot environment... "
917                         VERSION=`freebsd-version -ku | sort -V | tail -n 1`
918                         TIMESTAMP=`date +"%Y-%m-%d_%H%M%S"`
919                         bectl create -r ${VERSION}_${TIMESTAMP}
920                         if [ $? -eq 0 ]; then
921                                 echo "done.";
922                         else
923                                 echo "failed."
924                                 exit 1
925                         fi
926                 fi
927         fi
928 }
929
930 # Perform sanity checks and set some final parameters in
931 # preparation for UNinstalling updates.
932 rollback_check_params () {
933         # Check that we are root.  All sorts of things won't work otherwise.
934         if [ `id -u` != 0 ]; then
935                 echo "You must be root to run this."
936                 exit 1
937         fi
938
939         # Check that we have a working directory
940         _WORKDIR_bad="Directory does not exist or is not writable: "
941         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
942                 echo -n "`basename $0`: "
943                 echo -n "${_WORKDIR_bad}"
944                 echo ${WORKDIR}
945                 exit 1
946         fi
947         cd ${WORKDIR} || exit 1
948
949         # Construct a unique name from ${BASEDIR}
950         BDHASH=`echo ${BASEDIR} | sha256 -q`
951
952         # Check that we have updates ready to rollback
953         if ! [ -L ${BDHASH}-rollback ]; then
954                 echo "No rollback directory found."
955                 exit 1
956         fi
957         if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
958             ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
959                 echo "Update manifest is corrupt -- this should never happen."
960                 exit 1
961         fi
962 }
963
964 # Perform sanity checks and set some final parameters
965 # in preparation for comparing the system against the
966 # published index.  Figure out which index we should
967 # compare against: If the user is running *-p[0-9]+,
968 # strip off the last part; if the user is running
969 # -SECURITY, call it -RELEASE.  Chdir into the working
970 # directory.
971 IDS_check_params () {
972         export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
973
974         _SERVERNAME_z=\
975 "SERVERNAME must be given via command line or configuration file."
976         _KEYPRINT_z="Key must be given via -k option or configuration file."
977         _KEYPRINT_bad="Invalid key fingerprint: "
978         _WORKDIR_bad="Directory does not exist or is not writable: "
979
980         if [ -z "${SERVERNAME}" ]; then
981                 echo -n "`basename $0`: "
982                 echo "${_SERVERNAME_z}"
983                 exit 1
984         fi
985         if [ -z "${KEYPRINT}" ]; then
986                 echo -n "`basename $0`: "
987                 echo "${_KEYPRINT_z}"
988                 exit 1
989         fi
990         if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
991                 echo -n "`basename $0`: "
992                 echo -n "${_KEYPRINT_bad}"
993                 echo ${KEYPRINT}
994                 exit 1
995         fi
996         if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
997                 echo -n "`basename $0`: "
998                 echo -n "${_WORKDIR_bad}"
999                 echo ${WORKDIR}
1000                 exit 1
1001         fi
1002         cd ${WORKDIR} || exit 1
1003
1004         # Generate release number.  The s/SECURITY/RELEASE/ bit exists
1005         # to provide an upgrade path for FreeBSD Update 1.x users, since
1006         # the kernels provided by FreeBSD Update 1.x are always labelled
1007         # as X.Y-SECURITY.
1008         RELNUM=`uname -r |
1009             sed -E 's,-p[0-9]+,,' |
1010             sed -E 's,-SECURITY,-RELEASE,'`
1011         ARCH=`uname -m`
1012         FETCHDIR=${RELNUM}/${ARCH}
1013         PATCHDIR=${RELNUM}/${ARCH}/bp
1014
1015         # Figure out what directory contains the running kernel
1016         BOOTFILE=`sysctl -n kern.bootfile`
1017         KERNELDIR=${BOOTFILE%/kernel}
1018         if ! [ -d ${KERNELDIR} ]; then
1019                 echo "Cannot identify running kernel"
1020                 exit 1
1021         fi
1022
1023         # Figure out what kernel configuration is running.  We start with
1024         # the output of `uname -i`, and then make the following adjustments:
1025         # 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
1026         # file says "ident SMP-GENERIC", I don't know...
1027         # 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
1028         # _and_ `sysctl kern.version` contains a line which ends "/SMP", then
1029         # we're running an SMP kernel.  This mis-identification is a bug
1030         # which was fixed in 6.2-STABLE.
1031         KERNCONF=`uname -i`
1032         if [ ${KERNCONF} = "SMP-GENERIC" ]; then
1033                 KERNCONF=SMP
1034         fi
1035         if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
1036                 if sysctl kern.version | grep -qE '/SMP$'; then
1037                         KERNCONF=SMP
1038                 fi
1039         fi
1040
1041         # Define some paths
1042         SHA256=/sbin/sha256
1043         PHTTPGET=/usr/libexec/phttpget
1044
1045         # Set up variables relating to VERBOSELEVEL
1046         fetch_setup_verboselevel
1047 }
1048
1049 #### Core functionality -- the actual work gets done here
1050
1051 # Use an SRV query to pick a server.  If the SRV query doesn't provide
1052 # a useful answer, use the server name specified by the user.
1053 # Put another way... look up _http._tcp.${SERVERNAME} and pick a server
1054 # from that; or if no servers are returned, use ${SERVERNAME}.
1055 # This allows a user to specify "portsnap.freebsd.org" (in which case
1056 # portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
1057 # (in which case portsnap will use that particular server, since there
1058 # won't be an SRV entry for that name).
1059 #
1060 # We ignore the Port field, since we are always going to use port 80.
1061
1062 # Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
1063 # no mirrors are available for any reason.
1064 fetch_pick_server_init () {
1065         : > serverlist_tried
1066
1067 # Check that host(1) exists (i.e., that the system wasn't built with the
1068 # WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
1069         if ! which -s host; then
1070                 : > serverlist_full
1071                 return 1
1072         fi
1073
1074         echo -n "Looking up ${SERVERNAME} mirrors... "
1075
1076 # Issue the SRV query and pull out the Priority, Weight, and Target fields.
1077 # BIND 9 prints "$name has SRV record ..." while BIND 8 prints
1078 # "$name server selection ..."; we allow either format.
1079         MLIST="_http._tcp.${SERVERNAME}"
1080         host -t srv "${MLIST}" |
1081             sed -nE "s/${MLIST} (has SRV record|server selection) //Ip" |
1082             cut -f 1,2,4 -d ' ' |
1083             sed -e 's/\.$//' |
1084             sort > serverlist_full
1085
1086 # If no records, give up -- we'll just use the server name we were given.
1087         if [ `wc -l < serverlist_full` -eq 0 ]; then
1088                 echo "none found."
1089                 return 1
1090         fi
1091
1092 # Report how many mirrors we found.
1093         echo `wc -l < serverlist_full` "mirrors found."
1094
1095 # Generate a random seed for use in picking mirrors.  If HTTP_PROXY
1096 # is set, this will be used to generate the seed; otherwise, the seed
1097 # will be random.
1098         if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
1099                 RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
1100                     tr -d 'a-f' |
1101                     cut -c 1-9`
1102         else
1103                 RANDVALUE=`jot -r 1 0 999999999`
1104         fi
1105 }
1106
1107 # Pick a mirror.  Returns 1 if we have run out of mirrors to try.
1108 fetch_pick_server () {
1109 # Generate a list of not-yet-tried mirrors
1110         sort serverlist_tried |
1111             comm -23 serverlist_full - > serverlist
1112
1113 # Have we run out of mirrors?
1114         if [ `wc -l < serverlist` -eq 0 ]; then
1115                 cat <<- EOF
1116                         No mirrors remaining, giving up.
1117
1118                         This may be because upgrading from this platform (${ARCH})
1119                         or release (${RELNUM}) is unsupported by `basename $0`. Only
1120                         platforms with Tier 1 support can be upgraded by `basename $0`.
1121                         See https://www.freebsd.org/platforms/ for more info.
1122
1123                         If unsupported, FreeBSD must be upgraded by source.
1124                 EOF
1125                 return 1
1126         fi
1127
1128 # Find the highest priority level (lowest numeric value).
1129         SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
1130
1131 # Add up the weights of the response lines at that priority level.
1132         SRV_WSUM=0;
1133         while read X; do
1134                 case "$X" in
1135                 ${SRV_PRIORITY}\ *)
1136                         SRV_W=`echo $X | cut -f 2 -d ' '`
1137                         SRV_WSUM=$(($SRV_WSUM + $SRV_W))
1138                         ;;
1139                 esac
1140         done < serverlist
1141
1142 # If all the weights are 0, pretend that they are all 1 instead.
1143         if [ ${SRV_WSUM} -eq 0 ]; then
1144                 SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
1145                 SRV_W_ADD=1
1146         else
1147                 SRV_W_ADD=0
1148         fi
1149
1150 # Pick a value between 0 and the sum of the weights - 1
1151         SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
1152
1153 # Read through the list of mirrors and set SERVERNAME.  Write the line
1154 # corresponding to the mirror we selected into serverlist_tried so that
1155 # we won't try it again.
1156         while read X; do
1157                 case "$X" in
1158                 ${SRV_PRIORITY}\ *)
1159                         SRV_W=`echo $X | cut -f 2 -d ' '`
1160                         SRV_W=$(($SRV_W + $SRV_W_ADD))
1161                         if [ $SRV_RND -lt $SRV_W ]; then
1162                                 SERVERNAME=`echo $X | cut -f 3 -d ' '`
1163                                 echo "$X" >> serverlist_tried
1164                                 break
1165                         else
1166                                 SRV_RND=$(($SRV_RND - $SRV_W))
1167                         fi
1168                         ;;
1169                 esac
1170         done < serverlist
1171 }
1172
1173 # Take a list of ${oldhash}|${newhash} and output a list of needed patches,
1174 # i.e., those for which we have ${oldhash} and don't have ${newhash}.
1175 fetch_make_patchlist () {
1176         grep -vE "^([0-9a-f]{64})\|\1$" |
1177             tr '|' ' ' |
1178                 while read X Y; do
1179                         if [ -f "files/${Y}.gz" ] ||
1180                             [ ! -f "files/${X}.gz" ]; then
1181                                 continue
1182                         fi
1183                         echo "${X}|${Y}"
1184                 done | sort -u
1185 }
1186
1187 # Print user-friendly progress statistics
1188 fetch_progress () {
1189         LNC=0
1190         while read x; do
1191                 LNC=$(($LNC + 1))
1192                 if [ $(($LNC % 10)) = 0 ]; then
1193                         echo -n $LNC
1194                 elif [ $(($LNC % 2)) = 0 ]; then
1195                         echo -n .
1196                 fi
1197         done
1198         echo -n " "
1199 }
1200
1201 # Function for asking the user if everything is ok
1202 continuep () {
1203         while read -p "Does this look reasonable (y/n)? " CONTINUE; do
1204                 case "${CONTINUE}" in
1205                 [yY]*)
1206                         return 0
1207                         ;;
1208                 [nN]*)
1209                         return 1
1210                         ;;
1211                 esac
1212         done
1213 }
1214
1215 # Initialize the working directory
1216 workdir_init () {
1217         mkdir -p files
1218         touch tINDEX.present
1219 }
1220
1221 # Check that we have a public key with an appropriate hash, or
1222 # fetch the key if it doesn't exist.  Returns 1 if the key has
1223 # not yet been fetched.
1224 fetch_key () {
1225         if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1226                 return 0
1227         fi
1228
1229         echo -n "Fetching public key from ${SERVERNAME}... "
1230         rm -f pub.ssl
1231         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
1232             2>${QUIETREDIR} || true
1233         if ! [ -r pub.ssl ]; then
1234                 echo "failed."
1235                 return 1
1236         fi
1237         if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1238                 echo "key has incorrect hash."
1239                 rm -f pub.ssl
1240                 return 1
1241         fi
1242         echo "done."
1243 }
1244
1245 # Fetch metadata signature, aka "tag".
1246 fetch_tag () {
1247         echo -n "Fetching metadata signature "
1248         echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
1249         rm -f latest.ssl
1250         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl  \
1251             2>${QUIETREDIR} || true
1252         if ! [ -r latest.ssl ]; then
1253                 echo "failed."
1254                 return 1
1255         fi
1256
1257         openssl rsautl -pubin -inkey pub.ssl -verify            \
1258             < latest.ssl > tag.new 2>${QUIETREDIR} || true
1259         rm latest.ssl
1260
1261         if ! [ `wc -l < tag.new` = 1 ] ||
1262             ! grep -qE  \
1263     "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1264                 tag.new; then
1265                 echo "invalid signature."
1266                 return 1
1267         fi
1268
1269         echo "done."
1270
1271         RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
1272         TINDEXHASH=`cut -f 5 -d '|' < tag.new`
1273         EOLTIME=`cut -f 6 -d '|' < tag.new`
1274 }
1275
1276 # Sanity-check the patch number in a tag, to make sure that we're not
1277 # going to "update" backwards and to prevent replay attacks.
1278 fetch_tagsanity () {
1279         # Check that we're not going to move from -pX to -pY with Y < X.
1280         RELPX=`uname -r | sed -E 's,.*-,,'`
1281         if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
1282                 RELPX=`echo ${RELPX} | cut -c 2-`
1283         else
1284                 RELPX=0
1285         fi
1286         if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
1287                 echo
1288                 echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1289                 echo " appear older than what"
1290                 echo "we are currently running (`uname -r`)!"
1291                 echo "Cowardly refusing to proceed any further."
1292                 return 1
1293         fi
1294
1295         # If "tag" exists and corresponds to ${RELNUM}, make sure that
1296         # it contains a patch number <= RELPATCHNUM, in order to protect
1297         # against rollback (replay) attacks.
1298         if [ -f tag ] &&
1299             grep -qE    \
1300     "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1301                 tag; then
1302                 LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
1303
1304                 if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
1305                         echo
1306                         echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1307                         echo " are older than the"
1308                         echo -n "most recently seen updates"
1309                         echo " (${RELNUM}-p${LASTRELPATCHNUM})."
1310                         echo "Cowardly refusing to proceed any further."
1311                         return 1
1312                 fi
1313         fi
1314 }
1315
1316 # Fetch metadata index file
1317 fetch_metadata_index () {
1318         echo ${NDEBUG} "Fetching metadata index... "
1319         rm -f ${TINDEXHASH}
1320         fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
1321             2>${QUIETREDIR}
1322         if ! [ -f ${TINDEXHASH} ]; then
1323                 echo "failed."
1324                 return 1
1325         fi
1326         if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
1327                 echo "update metadata index corrupt."
1328                 return 1
1329         fi
1330         echo "done."
1331 }
1332
1333 # Print an error message about signed metadata being bogus.
1334 fetch_metadata_bogus () {
1335         echo
1336         echo "The update metadata$1 is correctly signed, but"
1337         echo "failed an integrity check."
1338         echo "Cowardly refusing to proceed any further."
1339         return 1
1340 }
1341
1342 # Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
1343 # with the lines not named in $@ from tINDEX.present (if that file exists).
1344 fetch_metadata_index_merge () {
1345         for METAFILE in $@; do
1346                 if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l`   \
1347                     -ne 1 ]; then
1348                         fetch_metadata_bogus " index"
1349                         return 1
1350                 fi
1351
1352                 grep -E "${METAFILE}\|" ${TINDEXHASH}
1353         done |
1354             sort > tINDEX.wanted
1355
1356         if [ -f tINDEX.present ]; then
1357                 join -t '|' -v 2 tINDEX.wanted tINDEX.present |
1358                     sort -m - tINDEX.wanted > tINDEX.new
1359                 rm tINDEX.wanted
1360         else
1361                 mv tINDEX.wanted tINDEX.new
1362         fi
1363 }
1364
1365 # Sanity check all the lines of tINDEX.new.  Even if more metadata lines
1366 # are added by future versions of the server, this won't cause problems,
1367 # since the only lines which appear in tINDEX.new are the ones which we
1368 # specifically grepped out of ${TINDEXHASH}.
1369 fetch_metadata_index_sanity () {
1370         if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
1371                 fetch_metadata_bogus " index"
1372                 return 1
1373         fi
1374 }
1375
1376 # Sanity check the metadata file $1.
1377 fetch_metadata_sanity () {
1378         # Some aliases to save space later: ${P} is a character which can
1379         # appear in a path; ${M} is the four numeric metadata fields; and
1380         # ${H} is a sha256 hash.
1381         P="[-+./:=,%@_[~[:alnum:]]"
1382         M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
1383         H="[0-9a-f]{64}"
1384
1385         # Check that the first four fields make sense.
1386         if gunzip -c < files/$1.gz |
1387             grep -qvE "^[a-z]+\|[0-9a-z-]+\|${P}+\|[fdL-]\|"; then
1388                 fetch_metadata_bogus ""
1389                 return 1
1390         fi
1391
1392         # Remove the first three fields.
1393         gunzip -c < files/$1.gz |
1394             cut -f 4- -d '|' > sanitycheck.tmp
1395
1396         # Sanity check entries with type 'f'
1397         if grep -E '^f' sanitycheck.tmp |
1398             grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
1399                 fetch_metadata_bogus ""
1400                 return 1
1401         fi
1402
1403         # Sanity check entries with type 'd'
1404         if grep -E '^d' sanitycheck.tmp |
1405             grep -qvE "^d\|${M}\|\|\$"; then
1406                 fetch_metadata_bogus ""
1407                 return 1
1408         fi
1409
1410         # Sanity check entries with type 'L'
1411         if grep -E '^L' sanitycheck.tmp |
1412             grep -qvE "^L\|${M}\|${P}*\|\$"; then
1413                 fetch_metadata_bogus ""
1414                 return 1
1415         fi
1416
1417         # Sanity check entries with type '-'
1418         if grep -E '^-' sanitycheck.tmp |
1419             grep -qvE "^-\|\|\|\|\|\|"; then
1420                 fetch_metadata_bogus ""
1421                 return 1
1422         fi
1423
1424         # Clean up
1425         rm sanitycheck.tmp
1426 }
1427
1428 # Fetch the metadata index and metadata files listed in $@,
1429 # taking advantage of metadata patches where possible.
1430 fetch_metadata () {
1431         fetch_metadata_index || return 1
1432         fetch_metadata_index_merge $@ || return 1
1433         fetch_metadata_index_sanity || return 1
1434
1435         # Generate a list of wanted metadata patches
1436         join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
1437             fetch_make_patchlist > patchlist
1438
1439         if [ -s patchlist ]; then
1440                 # Attempt to fetch metadata patches
1441                 echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1442                 echo ${NDEBUG} "metadata patches.${DDSTATS}"
1443                 tr '|' '-' < patchlist |
1444                     lam -s "${FETCHDIR}/tp/" - -s ".gz" |
1445                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1446                         2>${STATSREDIR} | fetch_progress
1447                 echo "done."
1448
1449                 # Attempt to apply metadata patches
1450                 echo -n "Applying metadata patches... "
1451                 tr '|' ' ' < patchlist |
1452                     while read X Y; do
1453                         if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
1454                         gunzip -c < ${X}-${Y}.gz > diff
1455                         gunzip -c < files/${X}.gz > diff-OLD
1456
1457                         # Figure out which lines are being added and removed
1458                         grep -E '^-' diff |
1459                             cut -c 2- |
1460                             while read PREFIX; do
1461                                 look "${PREFIX}" diff-OLD
1462                             done |
1463                             sort > diff-rm
1464                         grep -E '^\+' diff |
1465                             cut -c 2- > diff-add
1466
1467                         # Generate the new file
1468                         comm -23 diff-OLD diff-rm |
1469                             sort - diff-add > diff-NEW
1470
1471                         if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
1472                                 mv diff-NEW files/${Y}
1473                                 gzip -n files/${Y}
1474                         else
1475                                 mv diff-NEW ${Y}.bad
1476                         fi
1477                         rm -f ${X}-${Y}.gz diff
1478                         rm -f diff-OLD diff-NEW diff-add diff-rm
1479                 done 2>${QUIETREDIR}
1480                 echo "done."
1481         fi
1482
1483         # Update metadata without patches
1484         cut -f 2 -d '|' < tINDEX.new |
1485             while read Y; do
1486                 if [ ! -f "files/${Y}.gz" ]; then
1487                         echo ${Y};
1488                 fi
1489             done |
1490             sort -u > filelist
1491
1492         if [ -s filelist ]; then
1493                 echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1494                 echo ${NDEBUG} "metadata files... "
1495                 lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
1496                     xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}   \
1497                     2>${QUIETREDIR}
1498
1499                 while read Y; do
1500                         if ! [ -f ${Y}.gz ]; then
1501                                 echo "failed."
1502                                 return 1
1503                         fi
1504                         if [ `gunzip -c < ${Y}.gz |
1505                             ${SHA256} -q` = ${Y} ]; then
1506                                 mv ${Y}.gz files/${Y}.gz
1507                         else
1508                                 echo "metadata is corrupt."
1509                                 return 1
1510                         fi
1511                 done < filelist
1512                 echo "done."
1513         fi
1514
1515 # Sanity-check the metadata files.
1516         cut -f 2 -d '|' tINDEX.new > filelist
1517         while read X; do
1518                 fetch_metadata_sanity ${X} || return 1
1519         done < filelist
1520
1521 # Remove files which are no longer needed
1522         cut -f 2 -d '|' tINDEX.present |
1523             sort > oldfiles
1524         cut -f 2 -d '|' tINDEX.new |
1525             sort |
1526             comm -13 - oldfiles |
1527             lam -s "files/" - -s ".gz" |
1528             xargs rm -f
1529         rm patchlist filelist oldfiles
1530         rm ${TINDEXHASH}
1531
1532 # We're done!
1533         mv tINDEX.new tINDEX.present
1534         mv tag.new tag
1535
1536         return 0
1537 }
1538
1539 # Extract a subset of a downloaded metadata file containing only the parts
1540 # which are listed in COMPONENTS.
1541 fetch_filter_metadata_components () {
1542         METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
1543         gunzip -c < files/${METAHASH}.gz > $1.all
1544
1545         # Fish out the lines belonging to components we care about.
1546         for C in ${COMPONENTS}; do
1547                 look "`echo ${C} | tr '/' '|'`|" $1.all
1548         done > $1
1549
1550         # Remove temporary file.
1551         rm $1.all
1552 }
1553
1554 # Generate a filtered version of the metadata file $1 from the downloaded
1555 # file, by fishing out the lines corresponding to components we're trying
1556 # to keep updated, and then removing lines corresponding to paths we want
1557 # to ignore.
1558 fetch_filter_metadata () {
1559         # Fish out the lines belonging to components we care about.
1560         fetch_filter_metadata_components $1
1561
1562         # Canonicalize directory names by removing any trailing / in
1563         # order to avoid listing directories multiple times if they
1564         # belong to multiple components.  Turning "/" into "" doesn't
1565         # matter, since we add a leading "/" when we use paths later.
1566         cut -f 3- -d '|' $1 |
1567             sed -e 's,/|d|,|d|,' |
1568             sed -e 's,/|-|,|-|,' |
1569             sort -u > $1.tmp
1570
1571         # Figure out which lines to ignore and remove them.
1572         for X in ${IGNOREPATHS}; do
1573                 grep -E "^${X}" $1.tmp
1574         done |
1575             sort -u |
1576             comm -13 - $1.tmp > $1
1577
1578         # Remove temporary files.
1579         rm $1.tmp
1580 }
1581
1582 # Filter the metadata file $1 by adding lines with "/boot/$2"
1583 # replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
1584 # trailing "/kernel"); and if "/boot/$2" does not exist, remove
1585 # the original lines which start with that.
1586 # Put another way: Deal with the fact that the FOO kernel is sometimes
1587 # installed in /boot/FOO/ and is sometimes installed elsewhere.
1588 fetch_filter_kernel_names () {
1589         grep ^/boot/$2 $1 |
1590             sed -e "s,/boot/$2,${KERNELDIR},g" |
1591             sort - $1 > $1.tmp
1592         mv $1.tmp $1
1593
1594         if ! [ -d /boot/$2 ]; then
1595                 grep -v ^/boot/$2 $1 > $1.tmp
1596                 mv $1.tmp $1
1597         fi
1598 }
1599
1600 # For all paths appearing in $1 or $3, inspect the system
1601 # and generate $2 describing what is currently installed.
1602 fetch_inspect_system () {
1603         # No errors yet...
1604         rm -f .err
1605
1606         # Tell the user why his disk is suddenly making lots of noise
1607         echo -n "Inspecting system... "
1608
1609         # Generate list of files to inspect
1610         cat $1 $3 |
1611             cut -f 1 -d '|' |
1612             sort -u > filelist
1613
1614         # Examine each file and output lines of the form
1615         # /path/to/file|type|device-inum|user|group|perm|flags|value
1616         # sorted by device and inode number.
1617         while read F; do
1618                 # If the symlink/file/directory does not exist, record this.
1619                 if ! [ -e ${BASEDIR}/${F} ]; then
1620                         echo "${F}|-||||||"
1621                         continue
1622                 fi
1623                 if ! [ -r ${BASEDIR}/${F} ]; then
1624                         echo "Cannot read file: ${BASEDIR}/${F}"        \
1625                             >/dev/stderr
1626                         touch .err
1627                         return 1
1628                 fi
1629
1630                 # Otherwise, output an index line.
1631                 if [ -L ${BASEDIR}/${F} ]; then
1632                         echo -n "${F}|L|"
1633                         stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1634                         readlink ${BASEDIR}/${F};
1635                 elif [ -f ${BASEDIR}/${F} ]; then
1636                         echo -n "${F}|f|"
1637                         stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1638                         sha256 -q ${BASEDIR}/${F};
1639                 elif [ -d ${BASEDIR}/${F} ]; then
1640                         echo -n "${F}|d|"
1641                         stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1642                 else
1643                         echo "Unknown file type: ${BASEDIR}/${F}"       \
1644                             >/dev/stderr
1645                         touch .err
1646                         return 1
1647                 fi
1648         done < filelist |
1649             sort -k 3,3 -t '|' > $2.tmp
1650         rm filelist
1651
1652         # Check if an error occurred during system inspection
1653         if [ -f .err ]; then
1654                 return 1
1655         fi
1656
1657         # Convert to the form
1658         # /path/to/file|type|user|group|perm|flags|value|hlink
1659         # by resolving identical device and inode numbers into hard links.
1660         cut -f 1,3 -d '|' $2.tmp |
1661             sort -k 1,1 -t '|' |
1662             sort -s -u -k 2,2 -t '|' |
1663             join -1 2 -2 3 -t '|' - $2.tmp |
1664             awk -F \| -v OFS=\|         \
1665                 '{
1666                     if (($2 == $3) || ($4 == "-"))
1667                         print $3,$4,$5,$6,$7,$8,$9,""
1668                     else
1669                         print $3,$4,$5,$6,$7,$8,$9,$2
1670                 }' |
1671             sort > $2
1672         rm $2.tmp
1673
1674         # We're finished looking around
1675         echo "done."
1676 }
1677
1678 # For any paths matching ${MERGECHANGES}, compare $2 against $1 and $3 and
1679 # find any files with values unique to $2; generate $4 containing these paths
1680 # and their corresponding hashes from $1.
1681 fetch_filter_mergechanges () {
1682         # Pull out the paths and hashes of the files matching ${MERGECHANGES}.
1683         for F in $1 $2 $3; do
1684                 for X in ${MERGECHANGES}; do
1685                         grep -E "^${X}" ${F}
1686                 done |
1687                     cut -f 1,2,7 -d '|' |
1688                     sort > ${F}-values
1689         done
1690
1691         # Any line in $2-values which doesn't appear in $1-values or $3-values
1692         # and is a file means that we should list the path in $3.
1693         sort $1-values $3-values |
1694             comm -13 - $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 > $4
1707
1708         # Clean up
1709         rm $1-values $2-values $3-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 response </dev/tty
2548                                 if expr "${response}" : '[Aa][Cc][Cc][Ee][Pp][Tt]' > /dev/null; then
2549                                         echo
2550                                         break
2551                                 fi
2552                                 ${EDITOR} `pwd`/merge/new/${F} < /dev/tty
2553
2554                                 if ! grep -qE '^(<<<<<<<|=======|>>>>>>>)([[:space:]].*)?$' $(pwd)/merge/new/${F} ; then
2555                                         break
2556                                 fi
2557                                 cat <<-EOF
2558
2559 Merge conflict markers remain in: ${F}
2560 These must be resolved for the system to be functional.
2561
2562 Press Enter to return to editing this file, or type "ACCEPT" to carry on with
2563 these lines remaining in the file.
2564                                 EOF
2565                         done
2566                 done < failed.merges
2567                 rm failed.merges
2568
2569                 # Ask the user to confirm that he likes how the result
2570                 # of merging files.
2571                 while read F; do
2572                         # Skip files which haven't changed except possibly
2573                         # in their RCS tags.
2574                         if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
2575                             samef merge/old/${F} merge/new/${F}; then
2576                                 continue
2577                         fi
2578
2579                         # Skip files where the installed file differs from
2580                         # the old file only due to RCS tags.
2581                         if [ -f merge/old/${F} ] &&
2582                             [ -f merge/${OLDRELNUM}/${F} ] &&
2583                             samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
2584                                 continue
2585                         fi
2586
2587                         # Warn about files which are ceasing to exist.
2588                         if ! [ -f merge/new/${F} ]; then
2589                                 cat <<-EOF
2590
2591 The following file will be removed, as it no longer exists in
2592 FreeBSD ${RELNUM}: ${F}
2593                                 EOF
2594                                 continuep < /dev/tty || return 1
2595                                 continue
2596                         fi
2597
2598                         # Print changes for the user's approval.
2599                         cat <<-EOF
2600
2601 The following changes, which occurred between FreeBSD ${OLDRELNUM} and
2602 FreeBSD ${RELNUM} have been merged into ${F}:
2603 EOF
2604                         diff -U 5 -L "current version" -L "new version" \
2605                             merge/old/${F} merge/new/${F} || true
2606                         continuep < /dev/tty || return 1
2607                 done < $1-paths
2608
2609                 # Store merged files.
2610                 while read F; do
2611                         if [ -f merge/new/${F} ]; then
2612                                 V=`${SHA256} -q merge/new/${F}`
2613
2614                                 gzip -c < merge/new/${F} > files/${V}.gz
2615                                 echo "${F}|${V}"
2616                         fi
2617                 done < $1-paths > newhashes
2618
2619                 # Pull lines out from $3 which need to be updated to
2620                 # reflect merged files.
2621                 while read F; do
2622                         look "${F}|" $3
2623                 done < $1-paths > $3-oldlines
2624
2625                 # Update lines to reflect merged files
2626                 join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8          \
2627                     $3-oldlines newhashes > $3-newlines
2628
2629                 # Remove old lines from $3 and add new lines.
2630                 sort $3-oldlines |
2631                     comm -13 - $3 |
2632                     sort - $3-newlines > $3.tmp
2633                 mv $3.tmp $3
2634
2635                 # Clean up
2636                 rm $1-paths newhashes $3-oldlines $3-newlines
2637                 rm -rf merge/
2638         fi
2639
2640         # We're done with merging files.
2641         rm $1
2642 }
2643
2644 # Do the work involved in fetching upgrades to a new release
2645 upgrade_run () {
2646         workdir_init || return 1
2647
2648         # Prepare the mirror list.
2649         fetch_pick_server_init && fetch_pick_server
2650
2651         # Try to fetch the public key until we run out of servers.
2652         while ! fetch_key; do
2653                 fetch_pick_server || return 1
2654         done
2655  
2656         # Try to fetch the metadata index signature ("tag") until we run
2657         # out of available servers; and sanity check the downloaded tag.
2658         while ! fetch_tag; do
2659                 fetch_pick_server || return 1
2660         done
2661         fetch_tagsanity || return 1
2662
2663         # Fetch the INDEX-OLD and INDEX-ALL.
2664         fetch_metadata INDEX-OLD INDEX-ALL || return 1
2665
2666         # If StrictComponents is not "yes", generate a new components list
2667         # with only the components which appear to be installed.
2668         upgrade_guess_components INDEX-ALL || return 1
2669
2670         # Generate filtered INDEX-OLD and INDEX-ALL files containing only
2671         # the components we want and without anything marked as "Ignore".
2672         fetch_filter_metadata INDEX-OLD || return 1
2673         fetch_filter_metadata INDEX-ALL || return 1
2674
2675         # Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
2676         sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
2677         mv INDEX-OLD.tmp INDEX-OLD
2678         rm INDEX-ALL
2679
2680         # Adjust variables for fetching files from the new release.
2681         OLDRELNUM=${RELNUM}
2682         RELNUM=${TARGETRELEASE}
2683         OLDFETCHDIR=${FETCHDIR}
2684         FETCHDIR=${RELNUM}/${ARCH}
2685
2686         # Try to fetch the NEW metadata index signature ("tag") until we run
2687         # out of available servers; and sanity check the downloaded tag.
2688         while ! fetch_tag; do
2689                 fetch_pick_server || return 1
2690         done
2691
2692         # Fetch the new INDEX-ALL.
2693         fetch_metadata INDEX-ALL || return 1
2694
2695         # If StrictComponents is not "yes", COMPONENTS contains an entry
2696         # corresponding to the currently running kernel, and said kernel
2697         # does not exist in the new release, add "kernel/generic" to the
2698         # list of components.
2699         upgrade_guess_new_kernel INDEX-ALL || return 1
2700
2701         # Filter INDEX-ALL to contain only the components we want and without
2702         # anything marked as "Ignore".
2703         fetch_filter_metadata INDEX-ALL || return 1
2704
2705         # Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2706         # INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2707         upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
2708
2709         # Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
2710         fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
2711         fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2712
2713         # For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2714         # system and generate an INDEX-PRESENT file.
2715         fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2716
2717         # Based on ${MERGECHANGES}, generate a file tomerge-old with the
2718         # paths and hashes of old versions of files to merge.
2719         fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
2720
2721         # Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2722         # correspond to lines in INDEX-PRESENT with hashes not appearing
2723         # in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2724         # INDEX-PRESENT has type - and there isn't a corresponding entry in
2725         # INDEX-OLD with type -.
2726         fetch_filter_unmodified_notpresent      \
2727             INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
2728
2729         # For each entry in INDEX-PRESENT of type -, remove any corresponding
2730         # entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2731         # of type - from INDEX-PRESENT.
2732         fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2733
2734         # If ${ALLOWDELETE} != "yes", then remove any entries from
2735         # INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2736         fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2737
2738         # If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2739         # INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2740         # replace the corresponding line of INDEX-NEW with one having the
2741         # same metadata as the entry in INDEX-PRESENT.
2742         fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2743
2744         # Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2745         # no need to update a file if it isn't changing.
2746         fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2747
2748         # Fetch "clean" files from the old release for merging changes.
2749         fetch_files_premerge tomerge-old
2750
2751         # Prepare to fetch files: Generate a list of the files we need,
2752         # copy the unmodified files we have into /files/, and generate
2753         # a list of patches to download.
2754         fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2755
2756         # Fetch patches from to-${RELNUM}/${ARCH}/bp/
2757         PATCHDIR=to-${RELNUM}/${ARCH}/bp
2758         fetch_files || return 1
2759
2760         # Merge configuration file changes.
2761         upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
2762
2763         # Create and populate install manifest directory; and report what
2764         # updates are available.
2765         fetch_create_manifest || return 1
2766
2767         # Leave a note behind to tell the "install" command that the kernel
2768         # needs to be installed before the world.
2769         touch ${BDHASH}-install/kernelfirst
2770
2771         # Remind the user that they need to run "freebsd-update install"
2772         # to install the downloaded bits, in case they didn't RTFM.
2773         echo "To install the downloaded upgrades, run \"$0 install\"."
2774 }
2775
2776 # Make sure that all the file hashes mentioned in $@ have corresponding
2777 # gzipped files stored in /files/.
2778 install_verify () {
2779         # Generate a list of hashes
2780         cat $@ |
2781             cut -f 2,7 -d '|' |
2782             grep -E '^f' |
2783             cut -f 2 -d '|' |
2784             sort -u > filelist
2785
2786         # Make sure all the hashes exist
2787         while read HASH; do
2788                 if ! [ -f files/${HASH}.gz ]; then
2789                         echo -n "Update files missing -- "
2790                         echo "this should never happen."
2791                         echo "Re-run '$0 fetch'."
2792                         return 1
2793                 fi
2794         done < filelist
2795
2796         # Clean up
2797         rm filelist
2798 }
2799
2800 # Remove the system immutable flag from files
2801 install_unschg () {
2802         # Generate file list
2803         cat $@ |
2804             cut -f 1 -d '|' > filelist
2805
2806         # Remove flags
2807         while read F; do
2808                 if ! [ -e ${BASEDIR}/${F} ]; then
2809                         continue
2810                 else
2811                         echo ${BASEDIR}/${F}
2812                 fi
2813         done < filelist | xargs chflags noschg || return 1
2814
2815         # Clean up
2816         rm filelist
2817 }
2818
2819 # Decide which directory name to use for kernel backups.
2820 backup_kernel_finddir () {
2821         CNT=0
2822         while true ; do
2823                 # Pathname does not exist, so it is OK use that name
2824                 # for backup directory.
2825                 if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
2826                         return 0
2827                 fi
2828
2829                 # If directory do exist, we only use if it has our
2830                 # marker file.
2831                 if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
2832                         -e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
2833                         return 0
2834                 fi
2835
2836                 # We could not use current directory name, so add counter to
2837                 # the end and try again.
2838                 CNT=$((CNT + 1))
2839                 if [ $CNT -gt 9 ]; then
2840                         echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
2841                         exit 1
2842                 fi
2843                 BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
2844                 BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
2845         done
2846 }
2847
2848 # Backup the current kernel using hardlinks, if not disabled by user.
2849 # Since we delete all files in the directory used for previous backups
2850 # we create a marker file called ".freebsd-update" in the directory so
2851 # we can determine on the next run that the directory was created by
2852 # freebsd-update and we then do not accidentally remove user files in
2853 # the unlikely case that the user has created a directory with a
2854 # conflicting name.
2855 backup_kernel () {
2856         # Only make kernel backup is so configured.
2857         if [ $BACKUPKERNEL != yes ]; then
2858                 return 0
2859         fi
2860
2861         # Decide which directory name to use for kernel backups.
2862         backup_kernel_finddir
2863
2864         # Remove old kernel backup files.  If $BACKUPKERNELDIR was
2865         # "not ours", backup_kernel_finddir would have exited, so
2866         # deleting the directory content is as safe as we can make it.
2867         if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
2868                 rm -fr $BASEDIR/$BACKUPKERNELDIR
2869         fi
2870
2871         # Create directories for backup.
2872         mkdir -p $BASEDIR/$BACKUPKERNELDIR
2873         mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
2874             mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
2875
2876         # Mark the directory as having been created by freebsd-update.
2877         touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
2878         if [ $? -ne 0 ]; then
2879                 echo "Could not create kernel backup directory"
2880                 exit 1
2881         fi
2882
2883         # Disable pathname expansion to be sure *.symbols is not
2884         # expanded.
2885         set -f
2886
2887         # Use find to ignore symbol files, unless disabled by user.
2888         if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
2889                 FINDFILTER=""
2890         else
2891                 FINDFILTER="-a ! -name *.debug -a ! -name *.symbols"
2892         fi
2893
2894         # Backup all the kernel files using hardlinks.
2895         (cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
2896             cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
2897
2898         # Re-enable patchname expansion.
2899         set +f
2900 }
2901
2902 # Check for and remove an existing directory that conflicts with the file or
2903 # symlink that we are going to install.
2904 dir_conflict () {
2905         if [ -d "$1" ]; then
2906                 echo "Removing conflicting directory $1"
2907                 rm -rf -- "$1"
2908         fi
2909 }
2910
2911 # Install new files
2912 install_from_index () {
2913         # First pass: Do everything apart from setting file flags.  We
2914         # can't set flags yet, because schg inhibits hard linking.
2915         sort -k 1,1 -t '|' $1 |
2916             tr '|' ' ' |
2917             while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2918                 case ${TYPE} in
2919                 d)
2920                         # Create a directory.  A file may change to a directory
2921                         # on upgrade (PR273661).  If that happens, remove the
2922                         # file first.
2923                         if [ -e "${BASEDIR}/${FPATH}" ] && \
2924                             ! [ -d "${BASEDIR}/${FPATH}" ]; then
2925                                 rm -f -- "${BASEDIR}/${FPATH}"
2926                         fi
2927                         install -d -o ${OWNER} -g ${GROUP}              \
2928                             -m ${PERM} ${BASEDIR}/${FPATH}
2929                         ;;
2930                 f)
2931                         dir_conflict "${BASEDIR}/${FPATH}"
2932                         if [ -z "${LINK}" ]; then
2933                                 # Create a file, without setting flags.
2934                                 gunzip < files/${HASH}.gz > ${HASH}
2935                                 install -S -o ${OWNER} -g ${GROUP}      \
2936                                     -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
2937                                 rm ${HASH}
2938                         else
2939                                 # Create a hard link.
2940                                 ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
2941                         fi
2942                         ;;
2943                 L)
2944                         dir_conflict "${BASEDIR}/${FPATH}"
2945                         # Create a symlink
2946                         ln -sfh ${HASH} ${BASEDIR}/${FPATH}
2947                         ;;
2948                 esac
2949             done
2950
2951         # Perform a second pass, adding file flags.
2952         tr '|' ' ' < $1 |
2953             while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2954                 if [ ${TYPE} = "f" ] &&
2955                     ! [ ${FLAGS} = "0" ]; then
2956                         chflags ${FLAGS} ${BASEDIR}/${FPATH}
2957                 fi
2958             done
2959 }
2960
2961 # Remove files which we want to delete
2962 install_delete () {
2963         # Generate list of new files
2964         cut -f 1 -d '|' < $2 |
2965             sort > newfiles
2966
2967         # Generate subindex of old files we want to nuke
2968         sort -k 1,1 -t '|' $1 |
2969             join -t '|' -v 1 - newfiles |
2970             sort -r -k 1,1 -t '|' |
2971             cut -f 1,2 -d '|' |
2972             tr '|' ' ' > killfiles
2973
2974         # Remove the offending bits
2975         while read FPATH TYPE; do
2976                 case ${TYPE} in
2977                 d)
2978                         rmdir ${BASEDIR}/${FPATH}
2979                         ;;
2980                 f)
2981                         if [ -f "${BASEDIR}/${FPATH}" ]; then
2982                                 rm "${BASEDIR}/${FPATH}"
2983                         fi
2984                         ;;
2985                 L)
2986                         if [ -L "${BASEDIR}/${FPATH}" ]; then
2987                                 rm "${BASEDIR}/${FPATH}"
2988                         fi
2989                         ;;
2990                 esac
2991         done < killfiles
2992
2993         # Clean up
2994         rm newfiles killfiles
2995 }
2996
2997 # Install new files, delete old files, and update generated files
2998 install_files () {
2999         # If we haven't already dealt with the kernel, deal with it.
3000         if ! [ -f $1/kerneldone ]; then
3001                 grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
3002                 grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
3003
3004                 # Backup current kernel before installing a new one
3005                 backup_kernel || return 1
3006
3007                 # Install new files
3008                 install_from_index INDEX-NEW || return 1
3009
3010                 # Remove files which need to be deleted
3011                 install_delete INDEX-OLD INDEX-NEW || return 1
3012
3013                 # Update linker.hints if necessary
3014                 if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
3015                         kldxref -R ${BASEDIR}/boot/ 2>/dev/null
3016                 fi
3017
3018                 # We've finished updating the kernel.
3019                 touch $1/kerneldone
3020
3021                 # Do we need to ask for a reboot now?
3022                 if [ -f $1/kernelfirst ] &&
3023                     [ -s INDEX-OLD -o -s INDEX-NEW ]; then
3024                         cat <<-EOF
3025
3026 Kernel updates have been installed.  Please reboot and run
3027 "$0 install" again to finish installing updates.
3028                         EOF
3029                         exit 0
3030                 fi
3031         fi
3032
3033         # If we haven't already dealt with the world, deal with it.
3034         if ! [ -f $1/worlddone ]; then
3035                 # Create any necessary directories first
3036                 grep -vE '^/boot/' $1/INDEX-NEW |
3037                     grep -E '^[^|]+\|d\|' > INDEX-NEW
3038                 install_from_index INDEX-NEW || return 1
3039
3040                 # Install new runtime linker
3041                 grep -vE '^/boot/' $1/INDEX-NEW |
3042                     grep -vE '^[^|]+\|d\|' |
3043                     grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW
3044                 install_from_index INDEX-NEW || return 1
3045
3046                 # Install new shared libraries next
3047                 grep -vE '^/boot/' $1/INDEX-NEW |
3048                     grep -vE '^[^|]+\|d\|' |
3049                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
3050                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
3051                 install_from_index INDEX-NEW || return 1
3052
3053                 # Deal with everything else
3054                 grep -vE '^/boot/' $1/INDEX-OLD |
3055                     grep -vE '^[^|]+\|d\|' |
3056                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
3057                     grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
3058                 grep -vE '^/boot/' $1/INDEX-NEW |
3059                     grep -vE '^[^|]+\|d\|' |
3060                     grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
3061                     grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
3062                 install_from_index INDEX-NEW || return 1
3063                 install_delete INDEX-OLD INDEX-NEW || return 1
3064
3065                 # Restart host sshd if running (PR263489).  Note that this does
3066                 # not affect child sshd processes handling existing sessions.
3067                 if [ "$BASEDIR" = / ] && \
3068                     service sshd status >/dev/null 2>/dev/null; then
3069                         echo
3070                         echo "Restarting sshd after upgrade"
3071                         service sshd restart
3072                 fi
3073
3074                 # Rehash certs if we actually have certctl installed.
3075                 if which certctl>/dev/null; then
3076                         env DESTDIR=${BASEDIR} certctl rehash
3077                 fi
3078
3079                 # Rebuild generated pwd files and /etc/login.conf.db.
3080                 pwd_mkdb -d ${BASEDIR}/etc -p ${BASEDIR}/etc/master.passwd
3081                 cap_mkdb ${BASEDIR}/etc/login.conf
3082
3083                 # Rebuild man page databases, if necessary.
3084                 for D in /usr/share/man /usr/share/openssl/man; do
3085                         if [ ! -d ${BASEDIR}/$D ]; then
3086                                 continue
3087                         fi
3088                         if [ -f ${BASEDIR}/$D/mandoc.db ] && \
3089                             [ -z "$(find ${BASEDIR}/$D -type f -newer ${BASEDIR}/$D/mandoc.db)" ]; then
3090                                 continue;
3091                         fi
3092                         makewhatis ${BASEDIR}/$D
3093                 done
3094
3095                 # We've finished installing the world and deleting old files
3096                 # which are not shared libraries.
3097                 touch $1/worlddone
3098
3099                 # Do we need to ask the user to portupgrade now?
3100                 grep -vE '^/boot/' $1/INDEX-NEW |
3101                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
3102                     cut -f 1 -d '|' |
3103                     sort > newfiles
3104                 if grep -vE '^/boot/' $1/INDEX-OLD |
3105                     grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
3106                     cut -f 1 -d '|' |
3107                     sort |
3108                     join -v 1 - newfiles |
3109                     grep -q .; then
3110                         cat <<-EOF
3111
3112 Completing this upgrade requires removing old shared object files.
3113 Please rebuild all installed 3rd party software (e.g., programs
3114 installed from the ports tree) and then run "$0 install"
3115 again to finish installing updates.
3116                         EOF
3117                         rm newfiles
3118                         exit 0
3119                 fi
3120                 rm newfiles
3121         fi
3122
3123         # Remove old shared libraries
3124         grep -vE '^/boot/' $1/INDEX-NEW |
3125             grep -vE '^[^|]+\|d\|' |
3126             grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
3127         grep -vE '^/boot/' $1/INDEX-OLD |
3128             grep -vE '^[^|]+\|d\|' |
3129             grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
3130         install_delete INDEX-OLD INDEX-NEW || return 1
3131
3132         # Remove old directories
3133         grep -vE '^/boot/' $1/INDEX-NEW |
3134             grep -E '^[^|]+\|d\|' > INDEX-NEW
3135         grep -vE '^/boot/' $1/INDEX-OLD |
3136             grep -E '^[^|]+\|d\|' > INDEX-OLD
3137         install_delete INDEX-OLD INDEX-NEW || return 1
3138
3139         # Remove temporary files
3140         rm INDEX-OLD INDEX-NEW
3141 }
3142
3143 # Rearrange bits to allow the installed updates to be rolled back
3144 install_setup_rollback () {
3145         # Remove the "reboot after installing kernel", "kernel updated", and
3146         # "finished installing the world" flags if present -- they are
3147         # irrelevant when rolling back updates.
3148         if [ -f ${BDHASH}-install/kernelfirst ]; then
3149                 rm ${BDHASH}-install/kernelfirst
3150                 rm ${BDHASH}-install/kerneldone
3151         fi
3152         if [ -f ${BDHASH}-install/worlddone ]; then
3153                 rm ${BDHASH}-install/worlddone
3154         fi
3155
3156         if [ -L ${BDHASH}-rollback ]; then
3157                 mv ${BDHASH}-rollback ${BDHASH}-install/rollback
3158         fi
3159
3160         mv ${BDHASH}-install ${BDHASH}-rollback
3161 }
3162
3163 # Actually install updates
3164 install_run () {
3165         echo -n "Installing updates..."
3166
3167         # Make sure we have all the files we should have
3168         install_verify ${BDHASH}-install/INDEX-OLD      \
3169             ${BDHASH}-install/INDEX-NEW || return 1
3170
3171         # Remove system immutable flag from files
3172         install_unschg ${BDHASH}-install/INDEX-OLD      \
3173             ${BDHASH}-install/INDEX-NEW || return 1
3174
3175         # Install new files, delete old files, and update linker.hints
3176         install_files ${BDHASH}-install || return 1
3177
3178         # Rearrange bits to allow the installed updates to be rolled back
3179         install_setup_rollback
3180
3181         echo " done."
3182 }
3183
3184 # Rearrange bits to allow the previous set of updates to be rolled back next.
3185 rollback_setup_rollback () {
3186         if [ -L ${BDHASH}-rollback/rollback ]; then
3187                 mv ${BDHASH}-rollback/rollback rollback-tmp
3188                 rm -r ${BDHASH}-rollback/
3189                 rm ${BDHASH}-rollback
3190                 mv rollback-tmp ${BDHASH}-rollback
3191         else
3192                 rm -r ${BDHASH}-rollback/
3193                 rm ${BDHASH}-rollback
3194         fi
3195 }
3196
3197 # Install old files, delete new files, and update linker.hints
3198 rollback_files () {
3199         # Create directories first.  They may be needed by files we will
3200         # install in subsequent steps (PR273950).
3201         awk -F \| '{if ($2 == "d") print }' $1/INDEX-OLD > INDEX-OLD
3202         install_from_index INDEX-OLD || return 1
3203
3204         # Install old shared library files which don't have the same path as
3205         # a new shared library file.
3206         grep -vE '^/boot/' $1/INDEX-NEW |
3207             grep -E '/lib/.*\.so\.[0-9]+\|' |
3208             cut -f 1 -d '|' |
3209             sort > INDEX-NEW.libs.flist
3210         grep -vE '^/boot/' $1/INDEX-OLD |
3211             grep -E '/lib/.*\.so\.[0-9]+\|' |
3212             sort -k 1,1 -t '|' - |
3213             join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
3214         install_from_index INDEX-OLD || return 1
3215
3216         # Deal with files which are neither kernel nor shared library
3217         grep -vE '^/boot/' $1/INDEX-OLD |
3218             grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3219         grep -vE '^/boot/' $1/INDEX-NEW |
3220             grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3221         install_from_index INDEX-OLD || return 1
3222         install_delete INDEX-NEW INDEX-OLD || return 1
3223
3224         # Install any old shared library files which we didn't install above.
3225         grep -vE '^/boot/' $1/INDEX-OLD |
3226             grep -E '/lib/.*\.so\.[0-9]+\|' |
3227             sort -k 1,1 -t '|' - |
3228             join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
3229         install_from_index INDEX-OLD || return 1
3230
3231         # Delete unneeded shared library files
3232         grep -vE '^/boot/' $1/INDEX-OLD |
3233             grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3234         grep -vE '^/boot/' $1/INDEX-NEW |
3235             grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3236         install_delete INDEX-NEW INDEX-OLD || return 1
3237
3238         # Deal with kernel files
3239         grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
3240         grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
3241         install_from_index INDEX-OLD || return 1
3242         install_delete INDEX-NEW INDEX-OLD || return 1
3243         if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
3244                 kldxref -R /boot/ 2>/dev/null
3245         fi
3246
3247         # Remove temporary files
3248         rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
3249 }
3250
3251 # Actually rollback updates
3252 rollback_run () {
3253         echo -n "Uninstalling updates..."
3254
3255         # If there are updates waiting to be installed, remove them; we
3256         # want the user to re-run 'fetch' after rolling back updates.
3257         if [ -L ${BDHASH}-install ]; then
3258                 rm -r ${BDHASH}-install/
3259                 rm ${BDHASH}-install
3260         fi
3261
3262         # Make sure we have all the files we should have
3263         install_verify ${BDHASH}-rollback/INDEX-NEW     \
3264             ${BDHASH}-rollback/INDEX-OLD || return 1
3265
3266         # Remove system immutable flag from files
3267         install_unschg ${BDHASH}-rollback/INDEX-NEW     \
3268             ${BDHASH}-rollback/INDEX-OLD || return 1
3269
3270         # Install old files, delete new files, and update linker.hints
3271         rollback_files ${BDHASH}-rollback || return 1
3272
3273         # Remove the rollback directory and the symlink pointing to it; and
3274         # rearrange bits to allow the previous set of updates to be rolled
3275         # back next.
3276         rollback_setup_rollback
3277
3278         echo " done."
3279 }
3280
3281 # Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences.
3282 IDS_compare () {
3283         # Get all the lines which mismatch in something other than file
3284         # flags.  We ignore file flags because sysinstall doesn't seem to
3285         # set them when it installs FreeBSD; warning about these adds a
3286         # very large amount of noise.
3287         cut -f 1-5,7-8 -d '|' $1 > $1.noflags
3288         sort -k 1,1 -t '|' $1.noflags > $1.sorted
3289         cut -f 1-5,7-8 -d '|' $2 |
3290             comm -13 $1.noflags - |
3291             fgrep -v '|-|||||' |
3292             sort -k 1,1 -t '|' |
3293             join -t '|' $1.sorted - > INDEX-NOTMATCHING
3294
3295         # Ignore files which match IDSIGNOREPATHS.
3296         for X in ${IDSIGNOREPATHS}; do
3297                 grep -E "^${X}" INDEX-NOTMATCHING
3298         done |
3299             sort -u |
3300             comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp
3301         mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
3302
3303         # Go through the lines and print warnings.
3304         local IFS='|'
3305         while read FPATH TYPE OWNER GROUP PERM HASH LINK P_TYPE P_OWNER P_GROUP P_PERM P_HASH P_LINK; do
3306                 # Warn about different object types.
3307                 if ! [ "${TYPE}" = "${P_TYPE}" ]; then
3308                         echo -n "${FPATH} is a "
3309                         case "${P_TYPE}" in
3310                         f)      echo -n "regular file, "
3311                                 ;;
3312                         d)      echo -n "directory, "
3313                                 ;;
3314                         L)      echo -n "symlink, "
3315                                 ;;
3316                         esac
3317                         echo -n "but should be a "
3318                         case "${TYPE}" in
3319                         f)      echo -n "regular file."
3320                                 ;;
3321                         d)      echo -n "directory."
3322                                 ;;
3323                         L)      echo -n "symlink."
3324                                 ;;
3325                         esac
3326                         echo
3327
3328                         # Skip other tests, since they don't make sense if
3329                         # we're comparing different object types.
3330                         continue
3331                 fi
3332
3333                 # Warn about different owners.
3334                 if ! [ "${OWNER}" = "${P_OWNER}" ]; then
3335                         echo -n "${FPATH} is owned by user id ${P_OWNER}, "
3336                         echo "but should be owned by user id ${OWNER}."
3337                 fi
3338
3339                 # Warn about different groups.
3340                 if ! [ "${GROUP}" = "${P_GROUP}" ]; then
3341                         echo -n "${FPATH} is owned by group id ${P_GROUP}, "
3342                         echo "but should be owned by group id ${GROUP}."
3343                 fi
3344
3345                 # Warn about different permissions.  We do not warn about
3346                 # different permissions on symlinks, since some archivers
3347                 # don't extract symlink permissions correctly and they are
3348                 # ignored anyway.
3349                 if ! [ "${PERM}" = "${P_PERM}" ] &&
3350                     ! [ "${TYPE}" = "L" ]; then
3351                         echo -n "${FPATH} has ${P_PERM} permissions, "
3352                         echo "but should have ${PERM} permissions."
3353                 fi
3354
3355                 # Warn about different file hashes / symlink destinations.
3356                 if ! [ "${HASH}" = "${P_HASH}" ]; then
3357                         if [ "${TYPE}" = "L" ]; then
3358                                 echo -n "${FPATH} is a symlink to ${P_HASH}, "
3359                                 echo "but should be a symlink to ${HASH}."
3360                         fi
3361                         if [ "${TYPE}" = "f" ]; then
3362                                 echo -n "${FPATH} has SHA256 hash ${P_HASH}, "
3363                                 echo "but should have SHA256 hash ${HASH}."
3364                         fi
3365                 fi
3366
3367                 # We don't warn about different hard links, since some
3368                 # some archivers break hard links, and as long as the
3369                 # underlying data is correct they really don't matter.
3370         done < INDEX-NOTMATCHING
3371
3372         # Clean up
3373         rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING
3374 }
3375
3376 # Do the work involved in comparing the system to a "known good" index
3377 IDS_run () {
3378         workdir_init || return 1
3379
3380         # Prepare the mirror list.
3381         fetch_pick_server_init && fetch_pick_server
3382
3383         # Try to fetch the public key until we run out of servers.
3384         while ! fetch_key; do
3385                 fetch_pick_server || return 1
3386         done
3387  
3388         # Try to fetch the metadata index signature ("tag") until we run
3389         # out of available servers; and sanity check the downloaded tag.
3390         while ! fetch_tag; do
3391                 fetch_pick_server || return 1
3392         done
3393         fetch_tagsanity || return 1
3394
3395         # Fetch INDEX-OLD and INDEX-ALL.
3396         fetch_metadata INDEX-OLD INDEX-ALL || return 1
3397
3398         # Generate filtered INDEX-OLD and INDEX-ALL files containing only
3399         # the components we want and without anything marked as "Ignore".
3400         fetch_filter_metadata INDEX-OLD || return 1
3401         fetch_filter_metadata INDEX-ALL || return 1
3402
3403         # Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL.
3404         sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp
3405         mv INDEX-ALL.tmp INDEX-ALL
3406         rm INDEX-OLD
3407
3408         # Translate /boot/${KERNCONF} to ${KERNELDIR}
3409         fetch_filter_kernel_names INDEX-ALL ${KERNCONF}
3410
3411         # Inspect the system and generate an INDEX-PRESENT file.
3412         fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1
3413
3414         # Compare INDEX-ALL and INDEX-PRESENT and print warnings about any
3415         # differences.
3416         IDS_compare INDEX-ALL INDEX-PRESENT
3417 }
3418
3419 #### Main functions -- call parameter-handling and core functions
3420
3421 # Using the command line, configuration file, and defaults,
3422 # set all the parameters which are needed later.
3423 get_params () {
3424         init_params
3425         parse_cmdline $@
3426         parse_conffile
3427         default_params
3428 }
3429
3430 # Fetch command.  Make sure that we're being called
3431 # interactively, then run fetch_check_params and fetch_run
3432 cmd_fetch () {
3433         finalize_components_config ${COMPONENTS}
3434         if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
3435                 echo -n "`basename $0` fetch should not "
3436                 echo "be run non-interactively."
3437                 echo "Run `basename $0` cron instead."
3438                 exit 1
3439         fi
3440         fetch_check_params
3441         fetch_run || exit 1
3442         ISFETCHED=1
3443 }
3444
3445 # Cron command.  Make sure the parameters are sensible; wait
3446 # rand(3600) seconds; then fetch updates.  While fetching updates,
3447 # send output to a temporary file; only print that file if the
3448 # fetching failed.
3449 cmd_cron () {
3450         fetch_check_params
3451         sleep `jot -r 1 0 3600`
3452
3453         TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
3454         finalize_components_config ${COMPONENTS} >> ${TMPFILE}
3455         if ! fetch_run >> ${TMPFILE} ||
3456             ! grep -q "No updates needed" ${TMPFILE} ||
3457             [ ${VERBOSELEVEL} = "debug" ]; then
3458                 mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
3459         fi
3460
3461         rm ${TMPFILE}
3462 }
3463
3464 # Fetch files for upgrading to a new release.
3465 cmd_upgrade () {
3466         finalize_components_config ${COMPONENTS}
3467         upgrade_check_params
3468         upgrade_run || exit 1
3469 }
3470
3471 # Check if there are fetched updates ready to install.
3472 # Chdir into the working directory.
3473 cmd_updatesready () {
3474         finalize_components_config ${COMPONENTS}
3475         # Check if working directory exists (if not, no updates pending)
3476         if ! [ -e "${WORKDIR}" ]; then
3477                 echo "No updates are available to install."
3478                 exit 2
3479         fi
3480         
3481         # Change into working directory (fail if no permission/directory etc.)
3482         cd ${WORKDIR} || exit 1
3483
3484         # Construct a unique name from ${BASEDIR}
3485         BDHASH=`echo ${BASEDIR} | sha256 -q`
3486
3487         # Check that we have updates ready to install
3488         if ! [ -L ${BDHASH}-install ]; then
3489                 echo "No updates are available to install."
3490                 exit 2
3491         fi
3492
3493         echo "There are updates available to install."
3494         echo "Run '$0 install' to proceed."
3495 }
3496
3497 # Install downloaded updates.
3498 cmd_install () {
3499         finalize_components_config ${COMPONENTS}
3500         install_check_params
3501         install_create_be
3502         install_run || exit 1
3503 }
3504
3505 # Rollback most recently installed updates.
3506 cmd_rollback () {
3507         finalize_components_config ${COMPONENTS}
3508         rollback_check_params
3509         rollback_run || exit 1
3510 }
3511
3512 # Compare system against a "known good" index.
3513 cmd_IDS () {
3514         finalize_components_config ${COMPONENTS}
3515         IDS_check_params
3516         IDS_run || exit 1
3517 }
3518
3519 # Output configuration.
3520 cmd_showconfig () {
3521         finalize_components_config ${COMPONENTS}
3522         for X in ${CONFIGOPTIONS}; do
3523                 echo $X=$(eval echo \$${X})
3524         done
3525 }
3526
3527 #### Entry point
3528
3529 # Make sure we find utilities from the base system
3530 export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
3531
3532 # Set a pager if the user doesn't
3533 if [ -z "$PAGER" ]; then
3534         PAGER=/usr/bin/less
3535 fi
3536
3537 # Set LC_ALL in order to avoid problems with character ranges like [A-Z].
3538 export LC_ALL=C
3539
3540 # Clear environment variables that may affect operation of tools that we use.
3541 unset GREP_OPTIONS
3542
3543 get_params $@
3544 for COMMAND in ${COMMANDS}; do
3545         cmd_${COMMAND}
3546 done