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