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