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