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