xref: /illumos-gate/usr/src/cmd/boot/scripts/create_ramdisk.ksh (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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
26format=ufs
27ALT_ROOT=
28EXTRACT_ARGS=
29compress=yes
30SPLIT=unknown
31ERROR=0
32dirsize32=0
33dirsize64=0
34
35usage() {
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
46PLATFORM=`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#
52if [ "`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
56else
57	export PATH=/usr/sbin:/usr/bin:/sbin
58	export GZIP_CMD=/usr/bin/gzip
59fi
60
61EXTRACT_FILELIST="/boot/solaris/bin/extract_boot_filelist"
62
63#
64# Parse options
65#
66while [ "$1" != "" ]
67do
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
87done
88
89if [ -x /usr/bin/mkisofs -o -x /tmp/bfubin/mkisofs ] ; then
90	format=isofs
91fi
92
93#
94# mkisofs on s8 doesn't support functionality used by GRUB boot.
95# Use ufs format for boot archive instead.
96#
97release=`uname -r`
98if [ "$release" = "5.8" ]; then
99	format=ufs
100fi
101
102shift `expr $OPTIND - 1`
103
104if [ $# -eq 1 ]; then
105	ALT_ROOT="$1"
106	echo "Creating boot_archive for $ALT_ROOT"
107fi
108
109case $PLATFORM in
110i386)	PLATFORM=i86pc
111	ISA=i386
112	ARCH64=amd64
113	;;
114i86pc)	ISA=i386
115	ARCH64=amd64
116	;;
117sun4u)	ISA=sparc
118	ARCH64=sparcv9
119	;;
120sun4v)	ISA=sparc
121	ARCH64=sparcv9
122	;;
123*)	usage
124	;;
125esac
126
127BOOT_ARCHIVE=platform/$PLATFORM/boot_archive
128BOOT_ARCHIVE_64=platform/$PLATFORM/$ARCH64/boot_archive
129
130if [ $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
140else			# must be sparc
141	SPLIT=no	# there's only 64-bit (sparcv9), so don't split
142	compress=no
143fi
144
145[ -x $GZIP_CMD ] || compress=no
146
147function 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
157function 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#
194function 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#
237function 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#
304function 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
382function 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
431function fatal_error
432{
433	print -u2 $*
434	exit 1
435}
436
437#
438# get filelist
439#
440if [ ! -f "$ALT_ROOT/boot/solaris/filelist.ramdisk" ] &&
441    [ ! -f "$ALT_ROOT/etc/boot/solaris/filelist.ramdisk" ]
442then
443	print -u2 "Can't find filelist.ramdisk"
444	exit 1
445fi
446filelist=$($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#
455rddir="/tmp/create_ramdisk.$$.tmp"
456new_rddir=
457rm -rf "$rddir"
458mkdir "$rddir" || fatal_error "Could not create temporary directory $rddir"
459
460# Clean up upon exit.
461trap 'cleanup' EXIT
462
463list32="$rddir/filelist.32"
464list64="$rddir/filelist.64"
465
466touch $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#
473cd "/$ALT_ROOT"
474find $filelist -print 2>/dev/null | while read path
475do
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
501done >"$list32"
502
503if [ $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
533fi
534
535rdfile32="$rddir/rd.file.32"
536rdfile64="$rddir/rd.file.64"
537rdmnt32="$rddir/rd.mount.32"
538rdmnt64="$rddir/rd.mount.64"
539errlog32="$rddir/rd.errlog.32"
540errlog64="$rddir/rd.errlog.64"
541lofidev32=""
542lofidev64=""
543
544if [ $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
562else
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"
569fi
570if [ $ERROR = 1 ]; then
571	cleanup
572	exit 1
573fi
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#
580grep "[	 ]/[	 ]*nfs[	 ]" "$ALT_ROOT/etc/vfstab" > /dev/null
581if [ $? = 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
588fi
589[ -n "$rddir" ] && rm -rf "$rddir"
590