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