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