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