1#!/bin/sh
2#
3# $FreeBSD: stable/11/release/tools/vmimage.subr 347037 2019-05-03 00:45:31Z gjb $
4#
5#
6# Common functions for virtual machine image build scripts.
7#
8
9export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
10trap "cleanup" INT QUIT TRAP ABRT TERM
11
12write_partition_layout() {
13	if [ -z "${NOSWAP}" ]; then
14		SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}"
15	fi
16
17	_OBJDIR="$(make -C ${WORLDDIR} -V .OBJDIR)"
18	_OBJDIR="$(realpath ${_OBJDIR})"
19	_WORLDDIR="$(realpath ${WORLDDIR})"
20	if [ -d "${_OBJDIR%%${_WORLDDIR}}/${TARGET}.${TARGET_ARCH}" ]; then
21		BOOTFILES="/${_OBJDIR%%${_WORLDDIR}}/${TARGET}.${TARGET_ARCH}${_WORLDDIR}/stand"
22	else
23		BOOTFILES="/${_OBJDIR}/stand"
24	fi
25
26	case "${TARGET}:${TARGET_ARCH}" in
27		amd64:amd64 | i386:i386)
28			mkimg -s gpt -f ${VMFORMAT} \
29				-b ${BOOTFILES}/i386/pmbr/pmbr \
30				-p freebsd-boot/bootfs:=${BOOTFILES}/i386/gptboot/gptboot \
31				${SWAPOPT} \
32				-p freebsd-ufs/rootfs:=${VMBASE} \
33				-o ${VMIMAGE}
34			;;
35		arm64:aarch64)
36			mkimg -s mbr -f ${VMFORMAT} \
37				-p efi:=${BOOTFILES}/efi/boot1/boot1.efifat \
38				-p freebsd:=${VMBASE} \
39				-o ${VMIMAGE}
40			;;
41		powerpc:powerpc*)
42			mkimg -s apm -f ${VMFORMAT} \
43				-p apple-boot/bootfs:=${BOOTFILES}/powerpc/boot1.chrp/boot1.hfs \
44				${SWAPOPT} \
45				-p freebsd-ufs/rootfs:=${VMBASE} \
46				-o ${VMIMAGE}
47			;;
48		*)
49			# ENOTSUPP
50			return 1
51			;;
52	esac
53
54	return 0
55}
56
57err() {
58	printf "${@}\n"
59	cleanup
60	return 1
61}
62
63cleanup() {
64	if [ -c "${DESTDIR}/dev/null" ]; then
65		umount_loop ${DESTDIR}/dev 2>/dev/null
66	fi
67	umount_loop ${DESTDIR}
68	if [ ! -z "${mddev}" ]; then
69		mdconfig -d -u ${mddev}
70	fi
71
72	return 0
73}
74
75vm_create_base() {
76	# Creates the UFS root filesystem for the virtual machine disk,
77	# written to the formatted disk image with mkimg(1).
78
79	mkdir -p ${DESTDIR}
80	truncate -s ${VMSIZE} ${VMBASE}
81	mddev=$(mdconfig -f ${VMBASE})
82	newfs -L rootfs /dev/${mddev}
83	mount /dev/${mddev} ${DESTDIR}
84
85	return 0
86}
87
88vm_copy_base() {
89	# Creates a new UFS root filesystem and copies the contents of the
90	# current root filesystem into it.  This produces a "clean" disk
91	# image without any remnants of files which were created temporarily
92	# during image-creation and have since been deleted (e.g., downloaded
93	# package archives).
94
95	mkdir -p ${DESTDIR}/old
96	mdold=$(mdconfig -f ${VMBASE})
97	mount /dev/${mdold} ${DESTDIR}/old
98
99	truncate -s ${VMSIZE} ${VMBASE}.tmp
100	mkdir -p ${DESTDIR}/new
101	mdnew=$(mdconfig -f ${VMBASE}.tmp)
102	newfs -L rootfs /dev/${mdnew}
103	mount /dev/${mdnew} ${DESTDIR}/new
104
105	tar -cf- -C ${DESTDIR}/old . | tar -xUf- -C ${DESTDIR}/new
106
107	umount_loop /dev/${mdold}
108	rmdir ${DESTDIR}/old
109	mdconfig -d -u ${mdold}
110
111	umount_loop /dev/${mdnew}
112	rmdir ${DESTDIR}/new
113	tunefs -n enable /dev/${mdnew}
114	mdconfig -d -u ${mdnew}
115	mv ${VMBASE}.tmp ${VMBASE}
116}
117
118vm_install_base() {
119	# Installs the FreeBSD userland/kernel to the virtual machine disk.
120
121	cd ${WORLDDIR} && \
122		make DESTDIR=${DESTDIR} \
123		installworld installkernel distribution || \
124		err "\n\nCannot install the base system to ${DESTDIR}."
125
126	# Bootstrap etcupdate(8) and mergemaster(8) databases.
127	mkdir -p ${DESTDIR}/var/db/etcupdate
128	etcupdate extract -B \
129		-M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
130		-s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate
131	sh ${WORLDDIR}/release/scripts/mm-mtree.sh -m ${WORLDDIR} \
132		-F "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
133		-D ${DESTDIR}
134
135	echo '# Custom /etc/fstab for FreeBSD VM images' \
136		> ${DESTDIR}/etc/fstab
137	echo "/dev/${ROOTLABEL}/rootfs   /       ufs     rw      1       1" \
138		>> ${DESTDIR}/etc/fstab
139	if [ -z "${NOSWAP}" ]; then
140		echo '/dev/gpt/swapfs  none    swap    sw      0       0' \
141			>> ${DESTDIR}/etc/fstab
142	fi
143
144	local hostname
145	hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')"
146	echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf
147
148	if ! [ -z "${QEMUSTATIC}" ]; then
149		export EMULATOR=/qemu
150		cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR}
151	fi
152
153	mkdir -p ${DESTDIR}/dev
154	mount -t devfs devfs ${DESTDIR}/dev
155	chroot ${DESTDIR} ${EMULATOR} /usr/bin/newaliases
156	chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart
157	umount_loop ${DESTDIR}/dev
158
159	cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf
160
161	return 0
162}
163
164vm_extra_install_base() {
165	# Prototype.  When overridden, runs extra post-installworld commands
166	# as needed, based on the target virtual machine image or cloud
167	# provider image target.
168
169	return 0
170}
171
172vm_extra_enable_services() {
173	if [ ! -z "${VM_RC_LIST}" ]; then
174		for _rcvar in ${VM_RC_LIST}; do
175			echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf
176		done
177	fi
178
179	if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then
180		echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \
181			${DESTDIR}/etc/rc.conf
182		# Expand the filesystem to fill the disk.
183		echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf
184		touch ${DESTDIR}/firstboot
185	fi
186
187	return 0
188}
189
190vm_extra_install_packages() {
191	if [ -z "${VM_EXTRA_PACKAGES}" ]; then
192		return 0
193	fi
194	mkdir -p ${DESTDIR}/dev
195	mount -t devfs devfs ${DESTDIR}/dev
196	chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
197		/usr/sbin/pkg bootstrap -y
198	chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
199		/usr/sbin/pkg install -y ${VM_EXTRA_PACKAGES}
200	umount_loop ${DESTDIR}/dev
201
202	return 0
203}
204
205vm_extra_install_ports() {
206	# Prototype.  When overridden, installs additional ports within the
207	# virtual machine environment.
208
209	return 0
210}
211
212vm_extra_pre_umount() {
213	# Prototype.  When overridden, performs additional tasks within the
214	# virtual machine environment prior to unmounting the filesystem.
215	# Note: When overriding this function, removing resolv.conf in the
216	# disk image must be included.
217
218	if ! [ -z "${QEMUSTATIC}" ]; then
219		rm -f ${DESTDIR}/${EMULATOR}
220	fi
221	rm -f ${DESTDIR}/etc/resolv.conf
222	return 0
223}
224
225vm_extra_pkg_rmcache() {
226	if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then
227		chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
228			/usr/local/sbin/pkg clean -y -a
229	fi
230
231	return 0
232}
233
234umount_loop() {
235	DIR=$1
236	i=0
237	sync
238	while ! umount ${DIR}; do
239		i=$(( $i + 1 ))
240		if [ $i -ge 10 ]; then
241			# This should never happen.  But, it has happened.
242			echo "Cannot umount(8) ${DIR}"
243			echo "Something has gone horribly wrong."
244			return 1
245		fi
246		sleep 1
247	done
248
249	return 0
250}
251
252vm_create_disk() {
253	echo "Creating image...  Please wait."
254	echo
255
256	write_partition_layout || return 1
257
258	return 0
259}
260
261vm_extra_create_disk() {
262
263	return 0
264}
265
266