1 #!/bin/ksh -p
2 #
3 # CDDL HEADER START
4 #
5 # The contents of this file are subject to the terms of the
6 # Common Development and Distribution License (the "License").
7 # You may not use this file except in compliance with the License.
8 #
9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 # or http://www.opensolaris.org/os/licensing.
11 # See the License for the specific language governing permissions
12 # and limitations under the License.
13 #
14 # When distributing Covered Code, include this CDDL HEADER in each
15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 # If applicable, add the following below this CDDL HEADER, with the
17 # fields enclosed by brackets "[]" replaced with your own identifying
18 # information: Portions Copyright [yyyy] [name of copyright owner]
19 #
20 # CDDL HEADER END
21 #
22
23 # Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 # Use is subject to license terms.
25
26 format=ufs
27 ALT_ROOT=
28 EXTRACT_ARGS=
29 compress=yes
30 SPLIT=unknown
31 ERROR=0
32 dirsize32=0
33 dirsize64=0
34
35 usage() {
36 echo "This utility is a component of the bootadm(1M) implementation"
37 echo "and it is not recommended for stand-alone use."
38 echo "Please use bootadm(1M) instead."
39 echo ""
40 echo "Usage: ${0##*/}: [-R \<root\>] [-p \<platform\>] [--nocompress]"
41 echo "where \<platform\> is one of i86pc, sun4u or sun4v"
42 exit
43 }
44
45 # default platform is what we're running on
46 PLATFORM=`uname -m`
47
48 #
49 # set path, but inherit /tmp/bfubin if owned by
50 # same uid executing this process, which must be root.
51 #
52 if [ "`echo $PATH | cut -f 1 -d :`" = /tmp/bfubin ] && \
53 [ -O /tmp/bfubin ] ; then
54 export PATH=/tmp/bfubin
55 export GZIP_CMD=/tmp/bfubin/gzip
56 else
57 export PATH=/usr/sbin:/usr/bin:/sbin
58 export GZIP_CMD=/usr/bin/gzip
59 fi
60
61 EXTRACT_FILELIST="/boot/solaris/bin/extract_boot_filelist"
62
63 #
64 # Parse options
65 #
66 while [ "$1" != "" ]
67 do
68 case $1 in
69 -R) shift
70 ALT_ROOT="$1"
71 if [ "$ALT_ROOT" != "/" ]; then
72 echo "Creating boot_archive for $ALT_ROOT"
73 EXTRACT_ARGS="${EXTRACT_ARGS} -R ${ALT_ROOT}"
74 EXTRACT_FILELIST="${ALT_ROOT}${EXTRACT_FILELIST}"
75 fi
76 ;;
77 -n|--nocompress) compress=no
78 ;;
79 -p) shift
80 PLATFORM="$1"
81 EXTRACT_ARGS="${EXTRACT_ARGS} -p ${PLATFORM}"
82 ;;
83 *) usage
84 ;;
85 esac
86 shift
87 done
88
89 if [ -x /usr/bin/mkisofs -o -x /tmp/bfubin/mkisofs ] ; then
90 format=isofs
91 fi
92
93 #
94 # mkisofs on s8 doesn't support functionality used by GRUB boot.
95 # Use ufs format for boot archive instead.
96 #
97 release=`uname -r`
98 if [ "$release" = "5.8" ]; then
99 format=ufs
100 fi
101
102 shift `expr $OPTIND - 1`
103
104 if [ $# -eq 1 ]; then
105 ALT_ROOT="$1"
106 echo "Creating boot_archive for $ALT_ROOT"
107 fi
108
109 case $PLATFORM in
110 i386) PLATFORM=i86pc
111 ISA=i386
112 ARCH64=amd64
113 ;;
114 i86pc) ISA=i386
115 ARCH64=amd64
116 ;;
117 sun4u) ISA=sparc
118 ARCH64=sparcv9
119 ;;
120 sun4v) ISA=sparc
121 ARCH64=sparcv9
122 ;;
123 *) usage
124 ;;
125 esac
126
127 BOOT_ARCHIVE=platform/$PLATFORM/boot_archive
128 BOOT_ARCHIVE_64=platform/$PLATFORM/$ARCH64/boot_archive
129
130 if [ $PLATFORM = i86pc ] ; then
131 if [ ! -x "$ALT_ROOT"/boot/solaris/bin/symdef ]; then
132 # no dboot implies combined archives for example
133 # live-upgrade from s9 to s10u6 is multiboot-only
134 echo "Creating single archive at $ALT_ROOT/$BOOT_ARCHIVE"
135 SPLIT=no
136 compress=no
137 else
138 SPLIT=yes
139 fi
140 else # must be sparc
141 SPLIT=no # there's only 64-bit (sparcv9), so don't split
142 compress=no
143 fi
144
145 [ -x $GZIP_CMD ] || compress=no
146
147 function cleanup
148 {
149 umount -f "$rdmnt32" 2>/dev/null
150 umount -f "$rdmnt64" 2>/dev/null
151 lofiadm -d "$rdfile32" 2>/dev/null
152 lofiadm -d "$rdfile64" 2>/dev/null
153 [ -n "$rddir" ] && rm -fr "$rddir" 2> /dev/null
154 [ -n "$new_rddir" ] && rm -fr "$new_rddir" 2>/dev/null
155 }
156
157 function getsize
158 {
159 # Estimate image size and add 10% overhead for ufs stuff.
160 # Note, we can't use du here in case we're on a filesystem, e.g. zfs,
161 # in which the disk usage is less than the sum of the file sizes.
162 # The nawk code
163 #
164 # {t += ($5 % 1024) ? (int($5 / 1024) + 1) * 1024 : $5}
165 #
166 # below rounds up the size of a file/directory, in bytes, to the
167 # next multiple of 1024. This mimics the behavior of ufs especially
168 # with directories. This results in a total size that's slightly
169 # bigger than if du was called on a ufs directory.
170 size32=$(cat "$list32" | xargs -I {} ls -lLd "{}" 2> /dev/null |
171 nawk '{t += ($5 % 1024) ? (int($5 / 1024) + 1) * 1024 : $5}
172 END {print int(t * 1.10 / 1024)}')
173 (( size32 += dirsize32 ))
174 size64=$(cat "$list64" | xargs -I {} ls -lLd "{}" 2> /dev/null |
175 nawk '{t += ($5 % 1024) ? (int($5 / 1024) + 1) * 1024 : $5}
176 END {print int(t * 1.10 / 1024)}')
177 (( size64 += dirsize64 ))
178 (( total_size = size32 + size64 ))
179
180 if [ $compress = yes ] ; then
181 total_size=`echo $total_size | nawk '{print int($1 / 2)}'`
182 fi
183 }
184
185 #
186 # Copies all desired files to a target directory. One argument should be
187 # passed: the file containing the list of files to copy. This function also
188 # depends on several variables that must be set before calling:
189 #
190 # $ALT_ROOT - the target directory
191 # $compress - whether or not the files in the archives should be compressed
192 # $rdmnt - the target directory
193 #
194 function copy_files
195 {
196 list="$1"
197
198 #
199 # If compress is set, the files are gzip'd and put in the correct
200 # location in the loop. Nothing is printed, so the pipe and cpio
201 # at the end is a nop.
202 #
203 # If compress is not set, the file names are printed, which causes
204 # the cpio at the end to do the copy.
205 #
206 while read path
207 do
208 if [ $compress = yes ]; then
209 dir="${path%/*}"
210 [ -d "$rdmnt/$dir" ] || mkdir -p "$rdmnt/$dir"
211 $GZIP_CMD -c "$path" > "$rdmnt/$path"
212 else
213 print "$path"
214 fi
215 done <"$list" | cpio -pdum "$rdmnt" 2>/dev/null
216
217 if [ $ISA = sparc ] ; then
218 # copy links
219 find $filelist -type l -print 2>/dev/null |\
220 cpio -pdum "$rdmnt" 2>/dev/null
221 if [ $compress = yes ] ; then
222 # always copy unix uncompressed
223 find $filelist -name unix -type f -print 2>/dev/null |\
224 cpio -pdum "$rdmnt" 2>/dev/null
225 fi
226 fi
227
228 }
229
230 #
231 # The first argument can be:
232 #
233 # "both" - create an archive with both 32-bit and 64-bit binaries
234 # "32-bit" - create an archive with only 32-bit binaries
235 # "64-bit" - create an archive with only 64-bit binaries
236 #
237 function create_ufs
238 {
239 which=$1
240 archive=$2
241 lofidev=$3
242
243 # should we exclude amd64 binaries?
244 if [ "$which" = "32-bit" ]; then
245 rdfile="$rdfile32"
246 rdmnt="$rdmnt32"
247 list="$list32"
248 elif [ "$which" = "64-bit" ]; then
249 rdfile="$rdfile64"
250 rdmnt="$rdmnt64"
251 list="$list64"
252 else
253 rdfile="$rdfile32"
254 rdmnt="$rdmnt32"
255 list="$list32"
256 fi
257
258 newfs $lofidev < /dev/null 2> /dev/null
259 mkdir "$rdmnt"
260 mount -F mntfs mnttab /etc/mnttab > /dev/null 2>&1
261 mount -F ufs -o nologging $lofidev "$rdmnt"
262 files=
263
264 # do the actual copy
265 copy_files "$list"
266 umount -f "$rdmnt"
267 rmdir "$rdmnt"
268
269 if [ $ISA = sparc ] ; then
270 rlofidev=`echo "$lofidev" | sed -e "s/dev\/lofi/dev\/rlofi/"`
271 bb="$ALT_ROOT/platform/$PLATFORM/lib/fs/ufs/bootblk"
272 # installboot is not available on all platforms
273 dd if=$bb of=$rlofidev bs=1b oseek=1 count=15 conv=sync 2>&1
274 fi
275
276 #
277 # Check if gzip exists in /usr/bin, so we only try to run gzip
278 # on systems that have gzip. Then run gzip out of the patch to
279 # pick it up from bfubin or something like that if needed.
280 #
281 # If compress is set, the individual files in the archive are
282 # compressed, and the final compression will accomplish very
283 # little. To save time, we skip the gzip in this case.
284 #
285 if [ $ISA = i386 ] && [ $compress = no ] && \
286 [ -x $GZIP_CMD ] ; then
287 gzip -c "$rdfile" > "${archive}-new"
288 else
289 cat "$rdfile" > "${archive}-new"
290 fi
291
292 if [ $? -ne 0 ] ; then
293 rm -f "${archive}-new"
294 fi
295 }
296
297 #
298 # The first argument can be:
299 #
300 # "both" - create an archive with both 32-bit and 64-bit binaries
301 # "32-bit" - create an archive with only 32-bit binaries
302 # "64-bit" - create an archive with only 64-bit binaries
303 #
304 function create_isofs
305 {
306 which=$1
307 archive=$2
308
309 # should we exclude amd64 binaries?
310 if [ "$which" = "32-bit" ]; then
311 rdmnt="$rdmnt32"
312 errlog="$errlog32"
313 list="$list32"
314 elif [ "$which" = "64-bit" ]; then
315 rdmnt="$rdmnt64"
316 errlog="$errlog64"
317 list="$list64"
318 else
319 rdmnt="$rdmnt32"
320 errlog="$errlog32"
321 list="$list32"
322 fi
323
324 # create image directory seed with graft points
325 mkdir "$rdmnt"
326 files=
327 isocmd="mkisofs -quiet -graft-points -dlrDJN -relaxed-filenames"
328
329 if [ $ISA = sparc ] ; then
330 bb="$ALT_ROOT/platform/$PLATFORM/lib/fs/hsfs/bootblk"
331 isocmd="$isocmd -G \"$bb\""
332 fi
333
334 copy_files "$list"
335 isocmd="$isocmd \"$rdmnt\""
336 rm -f "$errlog"
337
338 #
339 # Check if gzip exists in /usr/bin, so we only try to run gzip
340 # on systems that have gzip. Then run gzip out of the patch to
341 # pick it up from bfubin or something like that if needed.
342 #
343 # If compress is set, the individual files in the archive are
344 # compressed, and the final compression will accomplish very
345 # little. To save time, we skip the gzip in this case.
346 #
347 mkiso_ret=0
348
349 if [ $ISA = i386 ] &&[ $compress = no ] && [ -x $GZIP_CMD ]
350 then
351 ksh -c "$isocmd" 2> "$errlog" | \
352 gzip > "${archive}-new"
353 else
354 ksh -c "$isocmd" 2> "$errlog" > "${archive}-new"
355 fi
356
357 if [ $? -ne 0 ]; then
358 cat "$errlog"
359 rm -f "${archive}-new" 2> /dev/null
360 rm -f "$errlog" 2> /dev/null
361 return
362 fi
363
364 dd_ret=0
365 if [ $ISA = sparc ] ; then
366 bb="$ALT_ROOT/platform/$PLATFORM/lib/fs/hsfs/bootblk"
367 dd if="$bb" of="${archive}-new" bs=1b oseek=1 count=15 \
368 conv=notrunc conv=sync >> "$errlog" 2>&1
369 dd_ret=$?
370 fi
371
372 if [ -s "$errlog" ] || [ $dd_ret -ne 0 ] ; then
373 grep Error: "$errlog" >/dev/null 2>&1
374 if [ $? -eq 0 ] || [ $dd_ret -ne 0 ] ; then
375 cat "$errlog"
376 rm -f "${archive}-new"
377 fi
378 fi
379 rm -f "$errlog"
380 }
381
382 function create_archive
383 {
384 which=$1
385 archive=$2
386 lofidev=$3
387
388 echo "updating $archive"
389
390 if [ "$format" = "ufs" ]; then
391 create_ufs "$which" "$archive" "$lofidev"
392 else
393 create_isofs "$which" "$archive"
394 fi
395
396 # sanity check the archive before moving it into place
397 #
398 ARCHIVE_SIZE=`ls -l "${archive}-new" 2> /dev/null | nawk '{ print $5 }'`
399 if [ $compress = yes ] || [ $ISA = sparc ] ; then
400 #
401 # 'file' will report "English text" for uncompressed
402 # boot_archives. Checking for that doesn't seem stable,
403 # so we just check that the file exists.
404 #
405 ls "${archive}-new" >/dev/null 2>&1
406 else
407 #
408 # the file type check also establishes that the
409 # file exists at all
410 #
411 LC_MESSAGES=C file "${archive}-new" | grep gzip > /dev/null
412 fi
413
414 if [ $? = 1 ] && [ -x $GZIP_CMD ] || [ "$ARCHIVE_SIZE" -lt 10000 ]
415 then
416 #
417 # Two of these functions may be run in parallel. We
418 # need to allow the other to clean up, so we can't
419 # exit immediately. Instead, we set a flag.
420 #
421 echo "update of $archive failed"
422 ERROR=1
423 else
424 lockfs -f "/$ALT_ROOT" 2>/dev/null
425 mv "${archive}-new" "$archive"
426 lockfs -f "/$ALT_ROOT" 2>/dev/null
427 fi
428
429 }
430
431 function fatal_error
432 {
433 print -u2 $*
434 exit 1
435 }
436
437 #
438 # get filelist
439 #
440 if [ ! -f "$ALT_ROOT/boot/solaris/filelist.ramdisk" ] &&
441 [ ! -f "$ALT_ROOT/etc/boot/solaris/filelist.ramdisk" ]
442 then
443 print -u2 "Can't find filelist.ramdisk"
444 exit 1
445 fi
446 filelist=$($EXTRACT_FILELIST $EXTRACT_ARGS \
447 /boot/solaris/filelist.ramdisk \
448 /etc/boot/solaris/filelist.ramdisk \
449 2>/dev/null | sort -u)
450
451 #
452 # We use /tmp/ for scratch space now. This may be changed later if there
453 # is insufficient space in /tmp/.
454 #
455 rddir="/tmp/create_ramdisk.$$.tmp"
456 new_rddir=
457 rm -rf "$rddir"
458 mkdir "$rddir" || fatal_error "Could not create temporary directory $rddir"
459
460 # Clean up upon exit.
461 trap 'cleanup' EXIT
462
463 list32="$rddir/filelist.32"
464 list64="$rddir/filelist.64"
465
466 touch $list32 $list64
467
468 #
469 # This loop creates the 32-bit and 64-bit lists of files. The 32-bit list
470 # is written to stdout, which is redirected at the end of the loop. The
471 # 64-bit list is appended with each write.
472 #
473 cd "/$ALT_ROOT"
474 find $filelist -print 2>/dev/null | while read path
475 do
476 if [ $SPLIT = no ]; then
477 print "$path"
478 elif [ -d "$path" ]; then
479 if [ $format = ufs ]; then
480 size=`ls -lLd "$path" | nawk '
481 {print ($5 % 1024) ? (int($5 / 1024) + 1) * 1024 : $5}'`
482 if [ `basename "$path"` != "amd64" ]; then
483 (( dirsize32 += size ))
484 fi
485 (( dirsize64 += size ))
486 fi
487 else
488 case `LC_MESSAGES=C /usr/bin/file -m /dev/null "$path" 2>/dev/null` in
489 *ELF\ 64-bit*)
490 print "$path" >> "$list64"
491 ;;
492 *ELF\ 32-bit*)
493 print "$path"
494 ;;
495 *)
496 # put in both lists
497 print "$path"
498 print "$path" >> "$list64"
499 esac
500 fi
501 done >"$list32"
502
503 if [ $format = ufs ] ; then
504 # calculate image size
505 getsize
506
507 # check to see if there is sufficient space in tmpfs
508 #
509 tmp_free=`df -b /tmp | tail -1 | awk '{ printf ($2) }'`
510 (( tmp_free = tmp_free / 3 ))
511 if [ $SPLIT = yes ]; then
512 (( tmp_free = tmp_free / 2 ))
513 fi
514
515 if [ $total_size -gt $tmp_free ] ; then
516 # assumes we have enough scratch space on $ALT_ROOT
517 new_rddir="/$ALT_ROOT/var/tmp/create_ramdisk.$$.tmp"
518 rm -rf "$new_rddir"
519 mkdir "$new_rddir" || fatal_error \
520 "Could not create temporary directory $new_rddir"
521
522 # Save the file lists
523 mv "$list32" "$new_rddir"/
524 mv "$list64" "$new_rddir"/
525 list32="/$new_rddir/filelist.32"
526 list64="/$new_rddir/filelist.64"
527
528 # Remove the old $rddir and set the new value of rddir
529 rm -rf "$rddir"
530 rddir="$new_rddir"
531 new_rddir=
532 fi
533 fi
534
535 rdfile32="$rddir/rd.file.32"
536 rdfile64="$rddir/rd.file.64"
537 rdmnt32="$rddir/rd.mount.32"
538 rdmnt64="$rddir/rd.mount.64"
539 errlog32="$rddir/rd.errlog.32"
540 errlog64="$rddir/rd.errlog.64"
541 lofidev32=""
542 lofidev64=""
543
544 if [ $SPLIT = yes ]; then
545 #
546 # We can't run lofiadm commands in parallel, so we have to do
547 # them here.
548 #
549 if [ "$format" = "ufs" ]; then
550 mkfile ${size32}k "$rdfile32"
551 lofidev32=`lofiadm -a "$rdfile32"`
552 mkfile ${size64}k "$rdfile64"
553 lofidev64=`lofiadm -a "$rdfile64"`
554 fi
555 create_archive "32-bit" "$ALT_ROOT/$BOOT_ARCHIVE" $lofidev32 &
556 create_archive "64-bit" "$ALT_ROOT/$BOOT_ARCHIVE_64" $lofidev64
557 wait
558 if [ "$format" = "ufs" ]; then
559 lofiadm -d "$rdfile32"
560 lofiadm -d "$rdfile64"
561 fi
562 else
563 if [ "$format" = "ufs" ]; then
564 mkfile ${total_size}k "$rdfile32"
565 lofidev32=`lofiadm -a "$rdfile32"`
566 fi
567 create_archive "both" "$ALT_ROOT/$BOOT_ARCHIVE" $lofidev32
568 [ "$format" = "ufs" ] && lofiadm -d "$rdfile32"
569 fi
570 if [ $ERROR = 1 ]; then
571 cleanup
572 exit 1
573 fi
574
575 #
576 # For the diskless case, hardlink archive to /boot to make it
577 # visible via tftp. /boot is lofs mounted under /tftpboot/<hostname>.
578 # NOTE: this script must work on both client and server.
579 #
580 grep "[ ]/[ ]*nfs[ ]" "$ALT_ROOT/etc/vfstab" > /dev/null
581 if [ $? = 0 ]; then
582 rm -f "$ALT_ROOT/boot/boot_archive" "$ALT_ROOT/boot/amd64/boot_archive"
583 ln "$ALT_ROOT/$BOOT_ARCHIVE" "$ALT_ROOT/boot/boot_archive"
584 if [ $SPLIT = yes ]; then
585 ln "$ALT_ROOT/$BOOT_ARCHIVE_64" \
586 "$ALT_ROOT/boot/amd64/boot_archive"
587 fi
588 fi
589 [ -n "$rddir" ] && rm -rf "$rddir"