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