1#!/bin/sh 2# 3# 4# 5# Common functions for virtual machine image build scripts. 6# 7 8scriptdir=$(dirname $(realpath $0)) 9. ${scriptdir}/../scripts/tools.subr 10. ${scriptdir}/../../tools/boot/install-boot.sh 11 12export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" 13trap "cleanup" INT QUIT TRAP ABRT TERM 14 15# Platform-specific large-scale setup 16# Most platforms use GPT, so put that as default, then special cases 17PARTSCHEME=gpt 18ROOTLABEL="gpt" 19case "${TARGET}:${TARGET_ARCH}" in 20 powerpc:powerpc*) 21 PARTSCHEME=mbr 22 ROOTLABEL="ufs" 23 NOSWAP=yes # Can't label swap partition with MBR, so no swap 24 ;; 25esac 26 27err() { 28 printf "${@}\n" 29 cleanup 30 return 1 31} 32 33cleanup() { 34 if [ -c "${DESTDIR}/dev/null" ]; then 35 umount_loop ${DESTDIR}/dev 2>/dev/null 36 fi 37 38 return 0 39} 40 41metalog_add_data() { 42 local file mode type 43 44 if [ -n "${NO_ROOT}" ]; then 45 file=$1 46 if [ -f ${DESTDIR}/${file} ]; then 47 type=file 48 mode=${2:-0644} 49 elif [ -d ${DESTDIR}/${file} ]; then 50 type=dir 51 mode=${2:-0755} 52 else 53 echo "metalog_add_data: ${file} not found" >&2 54 return 1 55 fi 56 echo "${file} type=${type} uname=root gname=wheel mode=${mode}" >> \ 57 ${DESTDIR}/METALOG 58 fi 59} 60 61vm_create_base() { 62 63 mkdir -p ${DESTDIR} 64 65 return 0 66} 67 68vm_copy_base() { 69 # Defunct 70 return 0 71} 72 73vm_base_packages_list() { 74 # Output a list of package sets equivalent to what we get from 75 # "installworld installkernel distribution", aka. the full base 76 # system. 77 for S in base kernels; do 78 echo FreeBSD-set-$S 79 echo FreeBSD-set-$S-dbg 80 done 81 case ${TARGET_ARCH} in 82 amd64 | aarch64 | powerpc64) 83 echo FreeBSD-set-lib32 84 echo FreeBSD-set-lib32-dbg 85 esac 86 echo FreeBSD-set-tests 87} 88 89vm_extra_filter_base_packages() { 90 # Prototype. When overridden, allows further filtering of base system 91 # packages, reading package names from stdin and writing to stdout. 92 cat 93} 94 95vm_install_base() { 96 # Installs the FreeBSD userland/kernel to the virtual machine disk. 97 98 if [ -z "${NOPKGBASE}" ]; then 99 local pkg_cmd 100 pkg_cmd="pkg --rootdir ${DESTDIR} --repo-conf-dir ${PKGBASE_REPO_DIR} 101 -o ASSUME_ALWAYS_YES=yes -o IGNORE_OSVERSION=yes 102 -o ABI=${PKG_ABI} -o INSTALL_AS_USER=yes " 103 if [ -n "${NO_ROOT}" ]; then 104 pkg_cmd="$pkg_cmd -o METALOG=METALOG" 105 fi 106 $pkg_cmd update 107 selected=$(vm_base_packages_list | vm_extra_filter_base_packages) 108 $pkg_cmd install -U -r FreeBSD-base $selected 109 else 110 cd ${WORLDDIR} && \ 111 make DESTDIR=${DESTDIR} ${INSTALLOPTS} \ 112 installworld installkernel distribution || \ 113 err "\n\nCannot install the base system to ${DESTDIR}." 114 fi 115 116 # Bootstrap etcupdate(8) database. 117 mkdir -p ${DESTDIR}/var/db/etcupdate 118 etcupdate extract -B \ 119 -M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \ 120 -s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate \ 121 -L /dev/stdout ${NO_ROOT:+-N} 122 if [ -n "${NO_ROOT}" ]; then 123 # Reroot etcupdate's internal METALOG to the whole tree 124 sed -n 's,^\.,./var/db/etcupdate/current,p' \ 125 ${DESTDIR}/var/db/etcupdate/current/METALOG | \ 126 env -i LC_COLLATE=C sort >> ${DESTDIR}/METALOG 127 rm ${DESTDIR}/var/db/etcupdate/current/METALOG 128 fi 129 130 echo '# Custom /etc/fstab for FreeBSD VM images' \ 131 > ${DESTDIR}/etc/fstab 132 if [ "${VMFS}" != zfs ]; then 133 echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw,noatime 1 1" \ 134 >> ${DESTDIR}/etc/fstab 135 fi 136 if [ -z "${NOSWAP}" ]; then 137 echo '/dev/gpt/swapfs none swap sw 0 0' \ 138 >> ${DESTDIR}/etc/fstab 139 fi 140 metalog_add_data ./etc/fstab 141 142 local hostname 143 hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')" 144 echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf 145 metalog_add_data ./etc/rc.conf 146 if [ "${VMFS}" = zfs ]; then 147 echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf 148 echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 149 echo "zpool_upgrade=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 150 echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf 151 echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf 152 metalog_add_data ./boot/loader.conf 153 fi 154 155 return 0 156} 157 158vm_emulation_setup() { 159 if [ -n "${WITHOUT_QEMU}" ]; then 160 return 0 161 fi 162 if [ -n "${QEMUSTATIC}" ]; then 163 export EMULATOR=/qemu 164 cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR} 165 fi 166 167 mkdir -p ${DESTDIR}/dev 168 mount -t devfs devfs ${DESTDIR}/dev 169 chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart 170 cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf 171 172 return 0 173} 174 175vm_extra_install_base() { 176 # Prototype. When overridden, runs extra post-installworld commands 177 # as needed, based on the target virtual machine image or cloud 178 # provider image target. 179 180 return 0 181} 182 183vm_extra_enable_services() { 184 if [ -n "${VM_RC_LIST}" ]; then 185 for _rcvar in ${VM_RC_LIST}; do 186 echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf 187 done 188 fi 189 190 if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then 191 echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \ 192 ${DESTDIR}/etc/rc.conf 193 # Expand the filesystem to fill the disk. 194 echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf 195 fi 196 197 return 0 198} 199 200vm_extra_install_packages() { 201 if [ -z "${VM_EXTRA_PACKAGES}" ]; then 202 return 0 203 fi 204 if [ -n "${NO_ROOT}" ]; then 205 for pkg in ${VM_EXTRA_PACKAGES}; do 206 INSTALL_AS_USER=yes \ 207 ${PKG_CMD} \ 208 -o ABI=${PKG_ABI} \ 209 -o METALOG=${DESTDIR}/METALOG.pkg \ 210 -o REPOS_DIR=${PKG_REPOS_DIR} \ 211 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 212 -r ${DESTDIR} \ 213 install -y -r ${PKG_REPO_NAME} $pkg 214 done 215 metalog_add_data ./var/db/pkg/local.sqlite 216 217 # Add some database files which are created by pkg triggers; 218 # at some point in the future the tools which create these 219 # files should probably learn how to record them in METALOG 220 # (which would simplify no-root installworld as well). 221 metalog_add_data ./etc/login.conf.db 222 metalog_add_data ./etc/passwd 223 metalog_add_data ./etc/pwd.db 224 metalog_add_data ./etc/spwd.db 600 225 metalog_add_data ./var/db/services.db 226 else 227 if [ -n "${WITHOUT_QEMU}" ]; then 228 return 0 229 fi 230 231 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 232 /usr/sbin/pkg bootstrap -y 233 for p in ${VM_EXTRA_PACKAGES}; do 234 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 235 /usr/sbin/pkg install -y ${p} 236 done 237 fi 238 239 return 0 240} 241 242vm_extra_install_ports() { 243 # Prototype. When overridden, installs additional ports within the 244 # virtual machine environment. 245 246 return 0 247} 248 249vm_extra_pre_umount() { 250 # Prototype. When overridden, performs additional tasks within the 251 # virtual machine environment prior to unmounting the filesystem. 252 253 return 0 254} 255 256vm_emulation_cleanup() { 257 if [ -n "${WITHOUT_QEMU}" ]; then 258 return 0 259 fi 260 261 if ! [ -z "${QEMUSTATIC}" ]; then 262 rm -f ${DESTDIR}/${EMULATOR} 263 fi 264 rm -f ${DESTDIR}/etc/resolv.conf 265 umount_loop ${DESTDIR}/dev 266 return 0 267} 268 269vm_extra_pkg_rmcache() { 270 if [ -n "${NO_ROOT}" ]; then 271 ${PKG_CMD} \ 272 -o ASSUME_ALWAYS_YES=yes \ 273 -o INSTALL_AS_USER=yes \ 274 -r ${DESTDIR} \ 275 clean -y -a 276 else 277 if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then 278 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 279 /usr/local/sbin/pkg clean -y -a 280 fi 281 fi 282 283 return 0 284} 285 286buildfs() { 287 local md tmppool 288 289 if [ -f ${DESTDIR}/METALOG.pkg ]; then 290 cat ${DESTDIR}/METALOG.pkg >> ${DESTDIR}/METALOG 291 fi 292 293 case "${VMFS}" in 294 ufs) 295 cd ${DESTDIR} && ${MAKEFS} ${MAKEFSARGS} -o label=rootfs -o version=2 -o softupdates=1 \ 296 ${VMBASE} .${NO_ROOT:+/METALOG} 297 ;; 298 zfs) 299 cd ${DESTDIR} && ${MAKEFS} -t zfs ${MAKEFSARGS} \ 300 -o poolname=zroot -o bootfs=zroot/ROOT/default -o rootpath=/ \ 301 -o fs=zroot\;mountpoint=none \ 302 -o fs=zroot/ROOT\;mountpoint=none \ 303 -o fs=zroot/ROOT/default\;mountpoint=/\;canmount=noauto \ 304 -o fs=zroot/home\;mountpoint=/home \ 305 -o fs=zroot/tmp\;mountpoint=/tmp\;exec=on\;setuid=off \ 306 -o fs=zroot/usr\;mountpoint=/usr\;canmount=off \ 307 -o fs=zroot/usr/ports\;setuid=off \ 308 -o fs=zroot/usr/src \ 309 -o fs=zroot/usr/obj \ 310 -o fs=zroot/var\;mountpoint=/var\;canmount=off \ 311 -o fs=zroot/var/audit\;setuid=off\;exec=off \ 312 -o fs=zroot/var/crash\;setuid=off\;exec=off \ 313 -o fs=zroot/var/log\;setuid=off\;exec=off \ 314 -o fs=zroot/var/mail\;atime=on \ 315 -o fs=zroot/var/tmp\;setuid=off \ 316 ${VMBASE} .${NO_ROOT:+/METALOG} 317 ;; 318 *) 319 echo "Unexpected VMFS value '${VMFS}'" 320 exit 1 321 ;; 322 esac 323} 324 325umount_loop() { 326 DIR=$1 327 i=0 328 sync 329 while ! umount ${DIR}; do 330 i=$(( $i + 1 )) 331 if [ $i -ge 10 ]; then 332 # This should never happen. But, it has happened. 333 echo "Cannot umount(8) ${DIR}" 334 echo "Something has gone horribly wrong." 335 return 1 336 fi 337 sleep 1 338 done 339 340 return 0 341} 342 343vm_create_disk() { 344 local BOOTFILES BOOTPARTSOFFSET FSPARTTYPE X86GPTBOOTFILE 345 346 if [ -z "${NOSWAP}" ]; then 347 SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}" 348 fi 349 350 if [ -n "${VM_BOOTPARTSOFFSET}" ]; then 351 BOOTPARTSOFFSET=":${VM_BOOTPARTSOFFSET}" 352 fi 353 354 if [ -n "${CONFIG_DRIVE}" ]; then 355 CONFIG_DRIVE="-p freebsd/config-drive::${CONFIG_DRIVE_SIZE}" 356 fi 357 358 case "${VMFS}" in 359 ufs) 360 FSPARTTYPE=freebsd-ufs 361 X86GPTBOOTFILE=i386/gptboot/gptboot 362 ;; 363 zfs) 364 FSPARTTYPE=freebsd-zfs 365 X86GPTBOOTFILE=i386/gptzfsboot/gptzfsboot 366 ;; 367 *) 368 echo "Unexpected VMFS value '${VMFS}'" 369 return 1 370 ;; 371 esac 372 373 echo "Creating image... Please wait." 374 echo 375 376 BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ 377 WITH_UNIFIED_OBJDIR=yes \ 378 make -C ${WORLDDIR}/stand -V .OBJDIR)" 379 BOOTFILES="$(realpath ${BOOTFILES})" 380 MAKEFSARGS="-s ${VMSIZE} -D" 381 382 case "${TARGET}:${TARGET_ARCH}" in 383 amd64:amd64 | i386:i386) 384 ESP=yes 385 BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \ 386 -p freebsd-boot/bootfs:=${BOOTFILES}/${X86GPTBOOTFILE}${BOOTPARTSOFFSET}" 387 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 388 MAKEFSARGS="$MAKEFSARGS -B little" 389 ;; 390 arm:armv7 | arm64:aarch64 | riscv:riscv64*) 391 ESP=yes 392 BOOTPARTS= 393 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 394 MAKEFSARGS="$MAKEFSARGS -B little" 395 ;; 396 powerpc:powerpc*) 397 ESP=no 398 BOOTPARTS="-p prepboot:=${BOOTFILES}/powerpc/boot1.chrp/boot1.elf -a 1" 399 ROOTFSPART="-p freebsd:=${VMBASE}" 400 if [ ${TARGET_ARCH} = powerpc64le ]; then 401 MAKEFSARGS="$MAKEFSARGS -B little" 402 else 403 MAKEFSARGS="$MAKEFSARGS -B big" 404 fi 405 ;; 406 *) 407 echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2 408 exit 1 409 ;; 410 esac 411 412 if [ ${ESP} = "yes" ]; then 413 # Create an ESP 414 espfilename=$(mktemp /tmp/efiboot.XXXXXX) 415 make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi 416 BOOTPARTS="${BOOTPARTS} -p efi/efiboot0:=${espfilename}" 417 418 # Add this to fstab 419 mkdir -p ${DESTDIR}/boot/efi 420 echo "/dev/${ROOTLABEL}/efiboot0 /boot/efi msdosfs rw 2 2" \ 421 >> ${DESTDIR}/etc/fstab 422 fi 423 424 # Add a marker file which indicates that this image has never 425 # been booted. Some services run only upon the first boot. 426 touch ${DESTDIR}/firstboot 427 metalog_add_data ./firstboot 428 429 echo "Building filesystem... Please wait." 430 buildfs 431 432 echo "Building final disk image... Please wait." 433 ${MKIMG} -s ${PARTSCHEME} -f ${VMFORMAT} \ 434 ${BOOTPARTS} \ 435 ${SWAPOPT} \ 436 ${CONFIG_DRIVE} \ 437 ${ROOTFSPART} \ 438 -o ${VMIMAGE} 439 440 echo "Disk image ${VMIMAGE} created." 441 442 if [ ${ESP} = "yes" ]; then 443 rm ${espfilename} 444 fi 445 446 return 0 447} 448 449vm_extra_create_disk() { 450 451 return 0 452} 453