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