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_CMD} --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 metalog_add_data ./var/db/pkg/local.sqlite 110 mkdir -p ${DESTDIR}/usr/local/etc/pkg/repos 111 echo 'FreeBSD-base: { enabled: yes }' > ${DESTDIR}/usr/local/etc/pkg/repos/FreeBSD.conf 112 metalog_add_data ./usr/local/etc/pkg/repos 113 metalog_add_data ./usr/local/etc/pkg/repos/FreeBSD.conf 114 else 115 cd ${WORLDDIR} && \ 116 make DESTDIR=${DESTDIR} ${INSTALLOPTS} \ 117 installworld installkernel distribution || \ 118 err "\n\nCannot install the base system to ${DESTDIR}." 119 fi 120 121 # Bootstrap etcupdate(8) database. 122 mkdir -p ${DESTDIR}/var/db/etcupdate 123 etcupdate extract -B \ 124 -M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \ 125 -s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate \ 126 -L /dev/stdout ${NO_ROOT:+-N} 127 if [ -n "${NO_ROOT}" ]; then 128 # Reroot etcupdate's internal METALOG to the whole tree 129 sed -n 's,^\.,./var/db/etcupdate/current,p' \ 130 ${DESTDIR}/var/db/etcupdate/current/METALOG | \ 131 env -i LC_COLLATE=C sort >> ${DESTDIR}/METALOG 132 rm ${DESTDIR}/var/db/etcupdate/current/METALOG 133 fi 134 135 echo '# Custom /etc/fstab for FreeBSD VM images' \ 136 > ${DESTDIR}/etc/fstab 137 if [ "${VMFS}" != zfs ]; then 138 echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw,noatime 1 1" \ 139 >> ${DESTDIR}/etc/fstab 140 fi 141 if [ -z "${NOSWAP}" ]; then 142 echo '/dev/gpt/swapfs none swap sw 0 0' \ 143 >> ${DESTDIR}/etc/fstab 144 fi 145 metalog_add_data ./etc/fstab 146 147 local hostname 148 hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')" 149 echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf 150 metalog_add_data ./etc/rc.conf 151 if [ "${VMFS}" = zfs ]; then 152 echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf 153 echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 154 echo "zpool_upgrade=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 155 echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf 156 echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf 157 metalog_add_data ./boot/loader.conf 158 fi 159 160 return 0 161} 162 163vm_emulation_setup() { 164 if [ -n "${WITHOUT_QEMU}" ]; then 165 return 0 166 fi 167 if [ -n "${QEMUSTATIC}" ]; then 168 export EMULATOR=/qemu 169 cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR} 170 fi 171 172 mkdir -p ${DESTDIR}/dev 173 mount -t devfs devfs ${DESTDIR}/dev 174 chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart 175 cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf 176 177 return 0 178} 179 180vm_extra_install_base() { 181 # Prototype. When overridden, runs extra post-installworld commands 182 # as needed, based on the target virtual machine image or cloud 183 # provider image target. 184 185 return 0 186} 187 188vm_extra_enable_services() { 189 if [ -n "${VM_RC_LIST}" ]; then 190 for _rcvar in ${VM_RC_LIST}; do 191 echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf 192 done 193 fi 194 195 if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then 196 echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \ 197 ${DESTDIR}/etc/rc.conf 198 # Expand the filesystem to fill the disk. 199 echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf 200 fi 201 202 return 0 203} 204 205vm_extra_install_packages() { 206 if [ -z "${VM_EXTRA_PACKAGES}" ]; then 207 return 0 208 fi 209 if [ -n "${NO_ROOT}" ]; then 210 for pkg in ${VM_EXTRA_PACKAGES}; do 211 INSTALL_AS_USER=yes \ 212 ${PKG_CMD} \ 213 -o ABI=${PKG_ABI} \ 214 -o METALOG=${DESTDIR}/METALOG.pkg \ 215 -o REPOS_DIR=${PKG_REPOS_DIR} \ 216 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 217 -r ${DESTDIR} \ 218 install -y -r ${PKG_REPO_NAME} $pkg 219 done 220 INSTALL_AS_USER=yes \ 221 ${PKG_CMD} \ 222 -o ABI=${PKG_ABI} \ 223 -o REPOS_DIR=${PKG_REPOS_DIR} \ 224 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 225 -r ${DESTDIR} \ 226 autoremove -y 227 if [ -n "${NOPKGBASE}" ]; then 228 metalog_add_data ./var/db/pkg/local.sqlite 229 fi 230 else 231 if [ -n "${WITHOUT_QEMU}" ]; then 232 return 0 233 fi 234 235 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 236 /usr/sbin/pkg bootstrap -y 237 for p in ${VM_EXTRA_PACKAGES}; do 238 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 239 /usr/sbin/pkg install -y ${p} 240 done 241 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 242 /usr/sbin/pkg autoremove -y 243 fi 244 245 return 0 246} 247 248vm_extra_install_ports() { 249 # Prototype. When overridden, installs additional ports within the 250 # virtual machine environment. 251 252 return 0 253} 254 255vm_extra_pre_umount() { 256 # Prototype. When overridden, performs additional tasks within the 257 # virtual machine environment prior to unmounting the filesystem. 258 259 return 0 260} 261 262vm_emulation_cleanup() { 263 if [ -n "${WITHOUT_QEMU}" ]; then 264 return 0 265 fi 266 267 if ! [ -z "${QEMUSTATIC}" ]; then 268 rm -f ${DESTDIR}/${EMULATOR} 269 fi 270 rm -f ${DESTDIR}/etc/resolv.conf 271 umount_loop ${DESTDIR}/dev 272 return 0 273} 274 275vm_extra_pkg_rmcache() { 276 if [ -n "${NO_ROOT}" ]; then 277 ${PKG_CMD} \ 278 -o ASSUME_ALWAYS_YES=yes \ 279 -o INSTALL_AS_USER=yes \ 280 -r ${DESTDIR} \ 281 clean -y -a 282 else 283 if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then 284 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 285 /usr/local/sbin/pkg clean -y -a 286 fi 287 fi 288 289 return 0 290} 291 292buildfs() { 293 local md tmppool 294 295 # Copy entries from METALOG.pkg into METALOG, but first check to 296 # make sure that filesystem objects still exist; some things may 297 # have been logged which no longer exist if a package was removed. 298 if [ -f ${DESTDIR}/METALOG.pkg ]; then 299 while read F REST; do 300 if [ -e ${DESTDIR}/${F} ]; then 301 echo "${F} ${REST}" >> ${DESTDIR}/METALOG 302 fi 303 done < ${DESTDIR}/METALOG.pkg 304 fi 305 306 if [ -n "${NO_ROOT}" ]; then 307 # Check for any directories in the staging tree which weren't 308 # recorded in METALOG, and record them now. This is a quick hack 309 # to avoid creating unusable VM images and should go away once 310 # the bugs which produce such unlogged directories are gone. 311 grep type=dir ${DESTDIR}/METALOG | 312 cut -f 1 -d ' ' | 313 sort -u > ${DESTDIR}/METALOG.dirs 314 ( cd ${DESTDIR} && find . -type d ) | 315 sort | 316 comm -23 - ${DESTDIR}/METALOG.dirs > ${DESTDIR}/METALOG.missingdirs 317 if [ -s ${DESTDIR}/METALOG.missingdirs ]; then 318 echo "WARNING: Directories exist but were not in METALOG" 319 cat ${DESTDIR}/METALOG.missingdirs 320 fi 321 while read DIR; do 322 metalog_add_data ${DIR} 323 done < ${DESTDIR}/METALOG.missingdirs 324 325 if [ -z "${NOPKGBASE}" ]; then 326 # Add some database files which are created by pkg triggers; 327 # at some point in the future the tools which create these 328 # files should probably learn how to record them in METALOG 329 # (which would simplify no-root installworld as well). 330 metalog_add_data ./etc/login.conf.db 331 metalog_add_data ./etc/passwd 332 metalog_add_data ./etc/pwd.db 333 metalog_add_data ./etc/spwd.db 600 334 metalog_add_data ./var/db/services.db 335 fi 336 337 if [ -n "${MISSING_METALOGS}" ]; then 338 # Hack to allow VM configurations to add files which 339 # weren't being added to METALOG appropriately. This 340 # is mainly a workaround for the @sample bug and it 341 # should go away before FreeBSD 15.1 ships. 342 for P in ${MISSING_METALOGS}; do 343 metalog_add_data ${P} 344 done 345 fi 346 347 # Sort METALOG file; makefs produces directories with 000 permissions 348 # if their contents are seen before the directories themselves. 349 env -i LC_COLLATE=C sort -u ${DESTDIR}/METALOG > ${DESTDIR}/METALOG.sorted 350 mv ${DESTDIR}/METALOG.sorted ${DESTDIR}/METALOG 351 fi 352 353 case "${VMFS}" in 354 ufs) 355 cd ${DESTDIR} && ${MAKEFS} ${MAKEFSARGS} -o label=rootfs -o version=2 -o softupdates=1 \ 356 ${VMBASE} .${NO_ROOT:+/METALOG} 357 ;; 358 zfs) 359 cd ${DESTDIR} && ${MAKEFS} -t zfs ${MAKEFSARGS} \ 360 -o poolname=zroot -o bootfs=zroot/ROOT/default -o rootpath=/ \ 361 -o fs=zroot\;mountpoint=none \ 362 -o fs=zroot/ROOT\;mountpoint=none \ 363 -o fs=zroot/ROOT/default\;mountpoint=/\;canmount=noauto \ 364 -o fs=zroot/home\;mountpoint=/home \ 365 -o fs=zroot/tmp\;mountpoint=/tmp\;exec=on\;setuid=off \ 366 -o fs=zroot/usr\;mountpoint=/usr\;canmount=off \ 367 -o fs=zroot/usr/ports\;setuid=off \ 368 -o fs=zroot/usr/src \ 369 -o fs=zroot/usr/obj \ 370 -o fs=zroot/var\;mountpoint=/var\;canmount=off \ 371 -o fs=zroot/var/audit\;setuid=off\;exec=off \ 372 -o fs=zroot/var/crash\;setuid=off\;exec=off \ 373 -o fs=zroot/var/log\;setuid=off\;exec=off \ 374 -o fs=zroot/var/mail\;atime=on \ 375 -o fs=zroot/var/tmp\;setuid=off \ 376 ${VMBASE} .${NO_ROOT:+/METALOG} 377 ;; 378 *) 379 echo "Unexpected VMFS value '${VMFS}'" 380 exit 1 381 ;; 382 esac 383} 384 385umount_loop() { 386 DIR=$1 387 i=0 388 sync 389 while ! umount ${DIR}; do 390 i=$(( $i + 1 )) 391 if [ $i -ge 10 ]; then 392 # This should never happen. But, it has happened. 393 echo "Cannot umount(8) ${DIR}" 394 echo "Something has gone horribly wrong." 395 return 1 396 fi 397 sleep 1 398 done 399 400 return 0 401} 402 403vm_create_disk() { 404 local BOOTFILES BOOTPARTSOFFSET FSPARTTYPE X86GPTBOOTFILE 405 406 if [ -z "${NOSWAP}" ]; then 407 SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}" 408 fi 409 410 if [ -n "${VM_BOOTPARTSOFFSET}" ]; then 411 BOOTPARTSOFFSET=":${VM_BOOTPARTSOFFSET}" 412 fi 413 414 if [ -n "${CONFIG_DRIVE}" ]; then 415 CONFIG_DRIVE="-p freebsd/config-drive::${CONFIG_DRIVE_SIZE}" 416 fi 417 418 case "${VMFS}" in 419 ufs) 420 FSPARTTYPE=freebsd-ufs 421 X86GPTBOOTFILE=i386/gptboot/gptboot 422 ;; 423 zfs) 424 FSPARTTYPE=freebsd-zfs 425 X86GPTBOOTFILE=i386/gptzfsboot/gptzfsboot 426 ;; 427 *) 428 echo "Unexpected VMFS value '${VMFS}'" 429 return 1 430 ;; 431 esac 432 433 echo "Creating image... Please wait." 434 echo 435 436 BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ 437 WITH_UNIFIED_OBJDIR=yes \ 438 make -C ${WORLDDIR}/stand -V .OBJDIR)" 439 BOOTFILES="$(realpath ${BOOTFILES})" 440 MAKEFSARGS="-s ${VMSIZE} -D" 441 442 case "${TARGET}:${TARGET_ARCH}" in 443 amd64:amd64 | i386:i386) 444 ESP=yes 445 BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \ 446 -p freebsd-boot/bootfs:=${BOOTFILES}/${X86GPTBOOTFILE}${BOOTPARTSOFFSET}" 447 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 448 MAKEFSARGS="$MAKEFSARGS -B little" 449 ;; 450 arm:armv7 | arm64:aarch64 | riscv:riscv64*) 451 ESP=yes 452 BOOTPARTS= 453 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 454 MAKEFSARGS="$MAKEFSARGS -B little" 455 ;; 456 powerpc:powerpc*) 457 ESP=no 458 BOOTPARTS="-p prepboot:=${BOOTFILES}/powerpc/boot1.chrp/boot1.elf -a 1" 459 ROOTFSPART="-p freebsd:=${VMBASE}" 460 if [ ${TARGET_ARCH} = powerpc64le ]; then 461 MAKEFSARGS="$MAKEFSARGS -B little" 462 else 463 MAKEFSARGS="$MAKEFSARGS -B big" 464 fi 465 ;; 466 *) 467 echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2 468 exit 1 469 ;; 470 esac 471 472 if [ ${ESP} = "yes" ]; then 473 # Create an ESP 474 espfilename=$(mktemp /tmp/efiboot.XXXXXX) 475 make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi 476 espsuffix="" 477 if [ -z "${BOOTPARTS}" ]; then 478 espsuffix="${BOOTPARTSOFFSET}" 479 fi 480 BOOTPARTS="${BOOTPARTS} -p efi/efiboot0:=${espfilename}${espsuffix}" 481 482 # Add this to fstab 483 mkdir -p ${DESTDIR}/boot/efi 484 echo "/dev/${ROOTLABEL}/efiboot0 /boot/efi msdosfs rw 2 2" \ 485 >> ${DESTDIR}/etc/fstab 486 fi 487 488 # Add a marker file which indicates that this image has never 489 # been booted. Some services run only upon the first boot. 490 touch ${DESTDIR}/firstboot 491 metalog_add_data ./firstboot 492 493 echo "Building filesystem... Please wait." 494 buildfs 495 496 echo "Building final disk image... Please wait." 497 ${MKIMG} -s ${PARTSCHEME} -f ${VMFORMAT} \ 498 ${BOOTPARTS} \ 499 ${SWAPOPT} \ 500 ${CONFIG_DRIVE} \ 501 ${ROOTFSPART} \ 502 -o ${VMIMAGE} 503 504 echo "Disk image ${VMIMAGE} created." 505 506 if [ ${ESP} = "yes" ]; then 507 rm ${espfilename} 508 fi 509 510 return 0 511} 512 513vm_extra_create_disk() { 514 515 return 0 516} 517