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