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(8) implementation and it is not
43recommended for stand-alone use. Please use bootadm(8) 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 calculate_sizes_and_locations
225{
226	find $filelist -print 2>/dev/null | while read path; do
227		if [ -d "$path" ]; then
228			size=`ls -lLd "$path" | nawk '
229		    {print ($5 % 1024) ? (int($5 / 1024) + 1) * 1024 : $5}'`
230			(( dirsize += size / 1024 ))
231		else
232			print "$path"
233		fi
234	done >"$list"
235
236	# calculate image size
237	ufs_getsize
238
239	# check to see if there is sufficient space in tmpfs
240	#
241	tmp_free=`df -b /tmp | tail -1 | awk '{ print $2 }'`
242	(( tmp_free = tmp_free / 3 ))
243
244	if [ $total_size -gt $tmp_free ] ; then
245		echo "Insufficient space in /tmp, using $ALT_ROOT/var/tmp"
246		# assumes we have enough scratch space on $ALT_ROOT
247		new_rddir="/$ALT_ROOT/var/tmp/create_ramdisk.$$.tmp"
248		rm -rf "$new_rddir"
249		mkdir "$new_rddir" || fatal_error \
250		    "Could not create temporary directory $new_rddir"
251
252		# Save the file lists
253		mv "$list" "$new_rddir"/
254		list="/$new_rddir/filelist"
255
256		# Remove the old $rddir and set the new value of rddir
257		rm -rf "$rddir"
258		rddir="$new_rddir"
259		new_rddir=
260	fi
261}
262
263function create_ufs_archive
264{
265	typeset archive="$ALT_ROOT/$BOOT_ARCHIVE"
266
267	[ "$compress" = yes ] && \
268	    echo "updating $archive (UFS)" || \
269	    echo "updating $archive (UFS-nocompress)"
270
271	#
272	# We use /tmp/ for scratch space now.  This will be changed later to
273	# $ALT_ROOT/var/tmp if there is insufficient space in /tmp/.
274	#
275	rddir="/tmp/create_ramdisk.$$.tmp"
276	new_rddir=
277	rm -rf "$rddir"
278	mkdir "$rddir" || fatal_error "Could not create directory $rddir"
279
280	# Clean up upon exit.
281	trap 'ufs_cleanup' EXIT
282
283	list="$rddir/filelist"
284
285	cd "/$ALT_ROOT" || fatal_error "Cannot chdir to $ALT_ROOT"
286	calculate_sizes_and_locations
287
288	rdfile="$rddir/rd.file"
289	rdmnt="$rddir/rd.mount"
290	errlog="$rddir/rd.errlog"
291	lofidev=""
292
293	mkfile ${total_size}k "$rdfile" || \
294	    fatal_error "Could not create backing file"
295	lofidev=`lofiadm -a "$rdfile"` || \
296	    fatal_error "Could not create lofi device"
297
298	NOINUSE_CHECK=1 newfs -m 0 $lofidev < /dev/null 2> /dev/null
299	mkdir "$rdmnt"
300	mount -F mntfs mnttab /etc/mnttab > /dev/null 2>&1
301	mount -F ufs -o nologging $lofidev "$rdmnt"
302	rm -rf "$rdmnt/lost+found"
303
304	# do the actual copy
305	copy_files "$list"
306	umount -f "$rdmnt"
307	rmdir "$rdmnt"
308
309	if [ $ISA = sparc ] ; then
310		rlofidev="${lofidev/lofi/rlofi}"
311		bb="/$ALT_ROOT/platform/$PLATFORM/lib/fs/ufs/bootblk"
312		# installboot is not available on all platforms
313		dd if=$bb of=$rlofidev bs=1b oseek=1 count=15 conv=sync 2>&1
314	fi
315
316	lofiadm -d "$rdfile"
317
318	#
319	# Check if gzip exists in /usr/bin, so we only try to run gzip
320	# on systems that have gzip. Then run gzip out of the patch to
321	# pick it up from bfubin or something like that if needed.
322	#
323	# If compress is set, the individual files in the archive are
324	# compressed, and the final compression will accomplish very
325	# little.  To save time, we skip the gzip in this case.
326	#
327	if [ $ISA = i386 ] && [ $compress = no ] && [ -x $GZIP_CMD ] ; then
328		$GZIP_CMD -c "$rdfile" > "${archive}-new"
329	else
330		cat "$rdfile" > "${archive}-new"
331	fi
332
333	if [ $? -ne 0 ] ; then
334		rm -f "${archive}-new"
335	fi
336
337	# sanity check the archive before moving it into place
338	#
339	ARCHIVE_SIZE=`ls -l "${archive}-new" 2> /dev/null | nawk '{ print $5 }'`
340	if [ $compress = yes ] || [ $ISA = sparc ] ; then
341		#
342		# 'file' will report "English text" for uncompressed
343		# boot_archives.  Checking for that doesn't seem stable,
344		# so we just check that the file exists.
345		#
346		ls "${archive}-new" >/dev/null 2>&1
347	else
348		#
349		# the file type check also establishes that the
350		# file exists at all
351		#
352		LC_MESSAGES=C file "${archive}-new" | grep gzip > /dev/null
353	fi
354
355	if [ $? = 1 ] && [ -x $GZIP_CMD ] || [ "$ARCHIVE_SIZE" -lt 10000 ]
356	then
357		fatal_error "update of $archive failed"
358	else
359		lockfs -f "/$ALT_ROOT" 2>/dev/null
360		rm -f "$archive.hash"
361		mv "${archive}-new" "$archive"
362		digest -a sha1 "$rdfile" > "$archive.hash"
363		lockfs -f "/$ALT_ROOT" 2>/dev/null
364	fi
365	[ -n "$rddir" ] && rm -rf "$rddir"
366}
367
368function cpio_cleanup
369{
370	rm -f "$tarchive" "$tarchive.cpio" "$tarchive.hash" "$tarchive.head"
371	[ -n "$rddir" ] && rm -fr "$rddir" 2> /dev/null
372}
373
374function create_hash
375{
376	[ -x /usr/bin/digest ] \
377	    && /usr/bin/digest -a sha1 "$tarchive.cpio" > "$tarchive.hash" \
378	    || print -u2 "Failed to create sha1 hash of $tarchive"
379}
380
381function create_cpio_archive
382{
383	typeset archive="$ALT_ROOT/$BOOT_ARCHIVE"
384
385	echo "updating $archive (CPIO)"
386
387	rddir="/tmp/create_ramdisk.$$.tmp"
388	tarchive="$archive.$$.new"
389	rm -rf "$rddir"
390	mkdir "$rddir" || fatal_error "Could not create directory $rddir"
391
392	# Clean up upon exit.
393	trap 'cpio_cleanup' EXIT
394
395	cd "/$ALT_ROOT" || fatal_error "Cannot chdir to $ALT_ROOT"
396
397	touch "$tarchive" \
398	    || fatal_error "Cannot create temporary archive $tarchive"
399
400	if [ $ISA = sparc ] ; then
401		# compression does not work (yet?).
402		# The krtld does not support gzip but fiocompress
403		# does not seem to work either.
404		compress="no"
405		list="$rddir/filelist"
406
407		calculate_sizes_and_locations
408
409		rdmnt="$rddir/rd.mount"
410		mkdir "$rdmnt"
411
412		copy_files "$list"
413
414		cd "$rdmnt"
415		find . 2>/dev/null | \
416		    cpio -qo -H odc > "$tarchive.cpio" \
417		    || fatal_error "Problem creating archive"
418		cd "/$ALT_ROOT" || fatal_error "Cannot chdir to $ALT_ROOT"
419
420		bb="/$ALT_ROOT/platform/$PLATFORM/lib/fs/cpio/bootblk"
421
422		# The SPARC boot code is assuming 8KB of boot data.
423		# This is originating from disk layout and UFS limits.
424		# Therefore we have 512B reserved space for disk label,
425		# and 7.5KB for boot program. With 512B blocks, this is
426		# 1 + 15 blocks.
427		dd if=/dev/zero of="$tarchive.head" bs=512 count=16 2>&1 \
428		    || fatal_error "Cannot create header"
429		dd if=$bb of="$tarchive.head" bs=512 oseek=1 count=15 \
430		    conv=sync 2>&1 \
431		    || fatal_error "Cannot install boot block"
432		cat "$tarchive.head" "$tarchive.cpio" > "$tarchive" \
433		    || fatal_error "Cannot update boot archive"
434		rm -f "$tarchive.head" "$tarchive.cpio"
435	else
436		find $filelist 2>/dev/null | \
437		    cpio -qo -H odc > "$tarchive.cpio" \
438		    || fatal_error "Problem creating archive"
439
440		# If hash is supported, it must be created before gzipping the archive.
441		# The boot loader will uncompress the archive, and the hash
442		# will be verified against the uncompressed data.
443		create_hash
444
445		if [ -x "$GZIP_CMD" ]; then
446			$GZIP_CMD -c "$tarchive.cpio" > "$tarchive"
447			rm -f "$tarchive.cpio"
448		else
449			mv "$tarchive.cpio" "$tarchive"
450		fi
451	fi
452
453	# Move new archive into place
454	[ -f "$archive.hash" ] && rm -f "$archive.hash"
455	mv "$tarchive" "$archive"
456	[ $? -eq 0 -a  -f "$tarchive.hash" ] \
457	    && mv "$tarchive.hash" "$archive.hash"
458}
459
460#
461# get filelist
462#
463if [ ! -f "$ALT_ROOT/boot/solaris/filelist.ramdisk" ] &&
464    [ ! -f "$ALT_ROOT/etc/boot/solaris/filelist.ramdisk" ]
465then
466	print -u2 "Can't find filelist.ramdisk"
467	exit 1
468fi
469filelist=$($EXTRACT_FILELIST $EXTRACT_ARGS \
470	/boot/solaris/filelist.ramdisk \
471	/etc/boot/solaris/filelist.ramdisk \
472		2>/dev/null | sort -u)
473
474# Now that we have the list of files, we can create the archive.
475
476case "$FORMAT" in
477	cpio)	create_cpio_archive ;;
478	ufs)	create_ufs_archive ;;
479	*)	print -u2 "Unknown boot archive format, $FORMAT"
480		exit 1
481		;;
482esac
483
484#
485# For the diskless case, hardlink archive to /boot to make it
486# visible via tftp. /boot is lofs mounted under /tftpboot/<hostname>.
487# NOTE: this script must work on both client and server.
488#
489grep "[	 ]/[	 ]*nfs[	 ]" "$ALT_ROOT/etc/vfstab" > /dev/null
490if [ $? = 0 ]; then
491	rm -f "$ALT_ROOT/boot/$BOOT_ARCHIVE_SUFFIX"
492	mkdir -p "$ALT_ROOT/boot/`dirname $BOOT_ARCHIVE_SUFFIX`"
493	ln "$ALT_ROOT/$BOOT_ARCHIVE" "$ALT_ROOT/boot/$BOOT_ARCHIVE_SUFFIX"
494fi
495