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