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