├── .dir-locals.el ├── INSTALL ├── README.md ├── MANIFEST.in ├── .gitignore ├── setup.py ├── COPYRIGHT ├── testsuite ├── anita ├── anita.1 ├── CHANGES └── anita.py /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((indent-tabs-mode . nil)))) 2 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | To install anita, run the following command as root: 2 | 3 | python setup.py install 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | For more information, see the anita home page at https://www.gson.org/netbsd/anita/. 2 | 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include COPYRIGHT 2 | include CHANGES 3 | include anita 4 | include anita.py 5 | include setup.py 6 | include anita.1 7 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # CVS default ignores begin 2 | tags 3 | TAGS 4 | .make.state 5 | .nse_depinfo 6 | *~ 7 | #* 8 | .#* 9 | ,* 10 | _$* 11 | *$ 12 | *.old 13 | *.bak 14 | *.BAK 15 | *.orig 16 | *.rej 17 | .del-* 18 | *.a 19 | *.olb 20 | *.o 21 | *.obj 22 | *.so 23 | *.exe 24 | *.Z 25 | *.elc 26 | *.ln 27 | core 28 | # CVS default ignores end 29 | build 30 | log 31 | work-* 32 | MANIFEST 33 | anita.pyc 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup(name='anita', 4 | version='2.17', 5 | description='Automated NetBSD Installation and Test Application', 6 | author='Andreas Gustafsson', 7 | author_email='gson@gson.org', 8 | url='https://www.gson.org/netbsd/anita/', 9 | py_modules=['anita'], 10 | scripts=['anita'], 11 | data_files=[('man/man1', ['anita.1'])], 12 | ) 13 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2006-2020 Andreas Gustafsson. 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND ANDREAS GUSTAFSSON DISCLAIMS ALL 8 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 9 | WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ANDREAS 10 | GUSTAFSSON OR CONTRIBUTORS BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING 12 | FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 13 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 14 | WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /testsuite: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Anita test suite. This mainly tests that installation of historic 4 | # NetBSD versions still works. 5 | # 6 | 7 | python=python3.11 8 | 9 | # Start background jobs this many seconds apart 10 | spacing=30 11 | 12 | while [ $# -gt 0 ] 13 | do 14 | case $1 in 15 | --python) 16 | shift 17 | python="$1" 18 | ;; 19 | *) 20 | echo "unknown option $1" >&2 21 | exit 1 22 | ;; 23 | esac 24 | shift 25 | done 26 | 27 | set -e 28 | 29 | logdir=log/testsuite.log.`date +%Y%m%d-%H%M%S` 30 | echo "logging to $logdir" 31 | 32 | mkdir -p $logdir 33 | 34 | runtest() { 35 | echo "$@" 36 | mode=$1 37 | shift 38 | workdir=`$python ./anita print-workdir "$@"` 39 | logfile=$logdir/$workdir 40 | test ! -f $logfile || echo "warning: $logfile exists" 41 | rm -f $workdir/wd0.img 42 | ( 43 | set -x -e 44 | $python ./anita --structured-log $mode --image-format sparse "$@" 45 | rm -f $workdir/wd0.img 46 | echo "passed" 47 | ) >$logfile 2>&1 & 48 | sleep $spacing 49 | } 50 | 51 | # Test local builds, if available 52 | # disabled: 2020.10.07.19.06.50/alpha 53 | 54 | for b in \ 55 | 2017.06.20.13.51.54/evbarm-earmv7hf \ 56 | 2020.09.25.19.24.56/evbarm-aarch64 57 | do 58 | local_build=$(pwd)/test-data/local-builds/$b/ 59 | if test -d $local_build 60 | then 61 | case $b in 62 | *evbarm-earmv7hf*) dtb="--dtb /usr/pkg/share/dtb/arm/vexpress-v2p-ca15-tc1.dtb" ;; 63 | *) dtb="" 64 | esac 65 | runtest boot $local_build $dtb 66 | else 67 | echo "no $local_build" 68 | fi 69 | done 70 | 71 | # Ditto for ISOs 72 | # The sparc ISO is to test supplying entropy at install time 73 | 74 | for b in \ 75 | 2020.10.07.19.06.50/images/NetBSD-9.99.73-hppa.iso \ 76 | 2020.11.11.07.34.55/release/images/NetBSD-9.99.75-sparc.iso 77 | do 78 | local_iso=$(pwd)/test-data/local-builds/$b 79 | if test -f $local_iso 80 | then 81 | runtest boot $local_iso 82 | else 83 | echo "no $local_iso" 84 | fi 85 | done 86 | 87 | # Test an installation with debug symbols and partial source 88 | # With 32M of memory, we hit the "stalled" problem (PR 47030) 89 | debug_build=$(pwd)/test-data/local-builds/2017.06.20.13.51.54/i386/ 90 | if test -d $debug_build 91 | then 92 | runtest "--memory-size=128M --disk-size=2G --sets=kern-GENERIC,etc,base,tests,games,misc,syssrc boot" $debug_build 93 | fi 94 | 95 | ftpsite="http://ftp.netbsd.org/pub/NetBSD/" 96 | archive="https://archive.netbsd.org/pub/NetBSD-archive/" 97 | 98 | runtest install ${archive}NetBSD-2.1/i386/ 99 | runtest install ${archive}NetBSD-3.0.1/i386/ 100 | runtest boot ${archive}NetBSD-4.0/i386/ 101 | runtest boot ${archive}NetBSD-5.0.2/i386/ 102 | runtest boot ${archive}NetBSD-5.1.5/i386/ 103 | 104 | # These fail with "piixide0:0:0: lost interrupt" (tested with qemu 2.2.0, 10.1.2) 105 | # runtest boot ${archive}/NetBSD-5.2/i386/ 106 | # runtest boot ${archive}/NetBSD-5.2.3/i386/ 107 | 108 | runtest boot ${archive}NetBSD-6.0/amd64/ 109 | 110 | # Test CD boot 111 | runtest "--boot-from cdrom boot" ${archive}NetBSD-7.2/amd64/ 112 | 113 | runtest boot ${archive}NetBSD-8.2/amd64/ 114 | 115 | # This panics with qemu 2.0.0, and fails due to the message 116 | # "esp0: !TC on DATA XFER [intr 18, stat 82, step 4] prevphase 2, resid 0" 117 | # being printed during set extraction with qemu 2.9.0. 118 | #runtest boot http://ftp.fi.netbsd.org/pub/NetBSD/NetBSD-6.1.3/images/NetBSD-6.1.3-sparc.iso 119 | 120 | # disabled while qemu sparc emulation is broken 121 | #runtest boot ${ftpsite}iso/7.2/NetBSD-7.2-sparc.iso 122 | 123 | runtest boot ${archive}NetBSD-8.0/macppc/ 124 | 125 | if /usr/bin/which gxemul >/dev/null 126 | then 127 | runtest boot ${archive}NetBSD-9.0/pmax/ 128 | fi 129 | 130 | # Installing hpcmips worked with GXemul 0.6.2 but it fails with 0.7.0. 131 | #runtest boot ${ftpsite}NetBSD-9.0/hpcmips/ 132 | 133 | # 9.0 fails with assertion "(val & LASI_IRQ_BITS) == val", needs -current? 134 | # runtest boot ${ftpsite}NetBSD-9.0/images/NetBSD-9.0-hppa.iso 135 | 136 | # 9.0 hangs at "CTB: PRINTERPORT", need -current 137 | #runtest boot ${ftpsite}NetBSD-9.0/alpha/ 138 | 139 | # TODO: add sparc64 back now that PR 54310 and PR 54810 are fixed 140 | 141 | # evbarm-aarch64 lacks release images (there are dailies) 142 | 143 | if /usr/bin/which simh-vax >/dev/null 144 | then 145 | runtest boot ${archive}NetBSD-9.0/images/NetBSD-9.0-vax.iso 146 | else 147 | echo "no simh-vax" 148 | fi 149 | 150 | runtest boot ${ftpsite}NetBSD-9.3/amd64/ 151 | 152 | runtest boot ${ftpsite}NetBSD-10.1/amd64/ 153 | 154 | # Test selecting and installing the games set 155 | url=${archive}iso/5.0.2/sparccd-5.0.2.iso 156 | workdir=`$python ./anita print-workdir $url` 157 | logfile=$logdir/$workdir 158 | ( 159 | set -x -e 160 | rm -f $workdir/wd0.img 161 | $python ./anita --sets=kern-GENERIC,base,etc,games --run "/usr/games/factor 1234567" boot $url | tee stdout.games 162 | grep "127 9721" stdout.games 163 | rm -f stdout.games 164 | rm -f $workdir/wd0.img 165 | echo "passed" 166 | ) >$logfile 2>&1 & 167 | 168 | # Test running the ATF tests 169 | url=${archive}NetBSD-5.1/i386/ 170 | workdir=`$python ./anita print-workdir $url` 171 | logfile=$logdir/$workdir 172 | ( 173 | set -x -e 174 | rm -f $workdir/wd0.img 175 | $python ./anita --sets=kern-GENERIC,etc,base,tests,games,misc test $url | tee stdout.atf || true 176 | # This is not to check that tests pass, but only that they at least run to completion 177 | grep 'passed test cases' stdout.atf 178 | rm -f stdout.atf 179 | rm -f $workdir/wd0.img 180 | echo "passed" 181 | ) >$logfile 2>&1 & 182 | 183 | # Test anita 1.2 compatibility (in foreground) 184 | logfile=$logdir/7.0.1 185 | ( 186 | workdir=netbsd-7.0.1 187 | rm -f $workdir/wd0.img 188 | $python <$logfile 2>&1 & 196 | 197 | echo "waiting for tests to finish" 198 | wait 199 | 200 | pass=true 201 | for f in $logdir/* 202 | do 203 | if tail -1 $f | grep '^passed$' >/dev/null 204 | then 205 | : 206 | else 207 | pass=false 208 | echo "failed: $f" 209 | fi 210 | done 211 | 212 | if $pass 213 | then 214 | echo "passed" 215 | fi 216 | 217 | $pass 218 | -------------------------------------------------------------------------------- /anita: -------------------------------------------------------------------------------- 1 | #!/usr/pkg/bin/python2.4 2 | 3 | from __future__ import print_function 4 | import sys 5 | import re 6 | import anita 7 | import os 8 | import optparse 9 | import pexpect 10 | 11 | class Usage(Exception): 12 | def __init__(self, msg): 13 | self.msg = msg 14 | 15 | def main(argv = None): 16 | if argv is None: 17 | argv = sys.argv 18 | 19 | dtb_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 20 | 'share', 'dtb', 'arm', 'vexpress-v2p-ca15-tc1.dtb') 21 | parser = optparse.OptionParser( 22 | usage = "usage: %prog [options] install|boot|interact distribution") 23 | parser.add_option("--workdir", 24 | help="store work files in DIR", metavar="DIR") 25 | parser.add_option("--vmm", 26 | help="use VMM (qemu/xl/xm/gxemul/simh) as the virtual machine monitor", 27 | metavar="VMM") 28 | parser.add_option("--vmm-args", 29 | help="pass ARGS as extra arguments to virtual machine monitor", 30 | metavar="ARGS", default="") 31 | # Deprecated 32 | parser.add_option("--qemu-args", 33 | help=optparse.SUPPRESS_HELP, default="") 34 | parser.add_option("--disk-size", 35 | help="use a virtual disk of SIZE bytes (k/M/G/T suffix accepted)", 36 | metavar="SIZE") 37 | parser.add_option("--memory-size", 38 | help="use a virtual RAM size of SIZE bytes (k/M/G/T suffix accepted)", 39 | metavar="SIZE") 40 | parser.add_option("--run", 41 | help="run COMMAND on the virtual machine after boot", metavar="COMMAND") 42 | parser.add_option("--sets", 43 | help="install the distribution SETS (e.g., base,games)", 44 | metavar="SETS") 45 | parser.add_option("--test-timeout", 46 | help="allow TIMEOUT seconds for each ATF test", metavar="TIMEOUT", 47 | type="int", default=10800) 48 | parser.add_option("--run-timeout", 49 | help="allow TIMEOUT seconds for command run using the --run option", metavar="TIMEOUT", 50 | type="int", default=3600) 51 | parser.add_option("--persist", 52 | help="make changes to disk contents persistent", action="store_true") 53 | parser.add_option("--boot-from", 54 | help="boot from MEDIA (floppy/cdrom)", metavar="MEDIA") 55 | parser.add_option("--structured-log", action="store_true", 56 | help="log console traffic in a structured format, to stdout") 57 | parser.add_option("--structured-log-file", metavar="FILE", 58 | help="log console traffic in a structured format, to FILE") 59 | parser.add_option("--no-install", action="store_true", 60 | help="in boot/test/interact mode, assume system is already installed", default=False) 61 | parser.add_option("--version", action="store_true", 62 | help="print the anita version number and exit") 63 | parser.add_option("--tests", help='select the tests to run in test mode: "atf" or "kyua"', 64 | type="string", default="atf") 65 | parser.add_option("--dtb", help='use the Device Tree Blob (.dtb) PATH_TO_DTB', 66 | type="string", metavar="PATH_TO_DTB", default=dtb_path) 67 | parser.add_option("--xen-type", help='select the Xen guest type: "pv", "pvshim", "hvm", or "pvh"', 68 | type="string", metavar="TYPE", default='pv') 69 | parser.add_option("--image-format", help='select the guest disk image format: "dense" or "sparse"', 70 | type="string", metavar='FORMAT', default='dense') 71 | parser.add_option("--machine", help='select the emulated machine type, e.g., ' \ 72 | '"vexpress-a15" or "virt"', 73 | type="string", metavar='MACHINE') 74 | parser.add_option("--network-config", help='load network configuration from FILE', 75 | type="string", metavar='FILE') 76 | parser.add_option("--partitioning-scheme", help='prefer partitioning scheme SCHEME', 77 | type="string", metavar='SCHEME') 78 | parser.add_option("--no-entropy", help='do not seed the installed system with entropy', 79 | action="store_true") 80 | 81 | (options, args) = parser.parse_args() 82 | 83 | if options.version: 84 | print(anita.__version__) 85 | sys.exit(0) 86 | 87 | if len(args) < 2: 88 | raise Usage("not enough arguments") 89 | 90 | distarg = args[1] 91 | 92 | vmm_args = options.vmm_args.split() + options.qemu_args.split() 93 | 94 | if options.sets: 95 | sets = options.sets.split(",") 96 | else: 97 | sets = None 98 | 99 | dist = anita.distribution(distarg, sets = sets) 100 | 101 | if dist.arch() == 'evbarm-earmv7hf': 102 | if not os.path.exists(options.dtb): 103 | raise IOError("The Device Tree Blob %s does not exist." % options.dtb) 104 | 105 | with anita.Anita(dist, 106 | workdir = options.workdir, 107 | vmm = options.vmm, 108 | vmm_args = vmm_args, 109 | disk_size = options.disk_size, 110 | memory_size = options.memory_size, 111 | persist = options.persist, 112 | boot_from = options.boot_from, 113 | structured_log = options.structured_log, 114 | structured_log_file = options.structured_log_file, 115 | no_install = options.no_install, 116 | tests = options.tests, 117 | dtb = options.dtb, 118 | xen_type = options.xen_type, 119 | image_format = options.image_format, 120 | machine = options.machine, 121 | network_config = options.network_config, 122 | partitioning_scheme = options.partitioning_scheme, 123 | no_entropy = options.no_entropy 124 | ) as a: 125 | 126 | status = 0 127 | mode = args[0] 128 | 129 | if mode != 'print-workdir': 130 | print("This is anita version", anita.__version__) 131 | print("Using pexpect version", pexpect.__version__) 132 | print(anita.quote_shell_command(sys.argv)) 133 | sys.stdout.flush() 134 | 135 | if mode == 'install': 136 | a.install() 137 | elif mode == 'boot': 138 | a.boot() 139 | if options.run: 140 | status = a.shell_cmd(options.run, options.run_timeout) 141 | a.halt() 142 | elif mode == 'interact': 143 | if options.run: 144 | a.boot() 145 | status = a.shell_cmd(options.run, options.run_timeout) 146 | if status != 0: 147 | a.halt() 148 | return status 149 | else: 150 | # Allow interaction with boot process 151 | a.start_boot() 152 | a.console_interaction() 153 | elif mode == 'test': 154 | status = a.run_tests(timeout = options.test_timeout) 155 | elif mode == 'print-workdir': 156 | print(a.workdir) 157 | else: 158 | raise Usage("unknown mode: " + mode) 159 | return status 160 | 161 | if __name__ == "__main__": 162 | try: 163 | status = main() 164 | sys.exit(status) 165 | except Usage as err: 166 | print("%s: %s" % (os.path.basename(sys.argv[0]), err.msg), file=sys.stderr) 167 | print("for help use --help", file=sys.stderr) 168 | sys.exit(1) 169 | -------------------------------------------------------------------------------- /anita.1: -------------------------------------------------------------------------------- 1 | .Dd Jul 7, 2022 2 | .Dt ANITA 1 3 | .Os 4 | .Sh NAME 5 | .Nm anita 6 | .Nd Automated NetBSD Installation and Test Application 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl -workdir Ar work_directory 10 | .Op Fl -vmm Ar qemu | xl | xm | gxemul | simh 11 | .Op Fl -vmm-args Ar vmm_arguments 12 | .Op Fl -disk-size Ar size 13 | .Op Fl -run Ar command 14 | .Op Fl -sets Ar sets 15 | .Op Fl -test-timeout Ar timeout 16 | .Op Fl -persist 17 | .Op Fl -boot-from Ar cdrom | floppy 18 | .Op Fl -structured-log 19 | .Op Fl -structured-log-file Ar file 20 | .Op Fl -tests Ar kyua | atf 21 | .Op Fl -dtb Ar dtb 22 | .Op Fl -image-format Ar format 23 | .Op Fl -machine Ar machine 24 | .Op Fl -partitioning-scheme Ar scheme 25 | .Op Fl -xen-type Ar pv | pvshim | hvm | pvh 26 | .Op Fl -no-entropy 27 | .Ar mode 28 | .Ar URL 29 | .Sh DESCRIPTION 30 | .Nm 31 | is a tool for automated testing of the NetBSD installation procedure 32 | and of NetBSD in general. Using 33 | .Nm , 34 | you can fully automate the process of downloading a NetBSD 35 | distribution, installing it in a virtual machine, booting the 36 | installed system, and running the NetBSD test suite. 37 | .Pp 38 | The NetBSD ports currently supported as targets (i.e., as the system 39 | to install and run under emulation) are i386, amd64, sparc, sparc64, 40 | evbarm-earmv7hf, evbarm-aarch64, pmax, hpcmips, vax, hppa, macppc, 41 | alpha, and riscv-riscv64. The host (the system running 42 | .Nm ) 43 | can be any NetBSD port or even a different Unix-like system such 44 | as Linux, FreeBSD, or macOS. 45 | .Pp 46 | i386 and amd64 targets can be run under either qemu 47 | or Xen. Other targets use either qemu, gxemul, or simh 48 | depending on the target architecture. 49 | .Sh EXAMPLES 50 | To install NetBSD 10.0/i386 from the master NetBSD FTP site, enter 51 | .Pp 52 | .Dl anita install http://ftp.netbsd.org/pub/NetBSD/NetBSD-10.0/i386/ 53 | .Pp 54 | Installing NetBSD/sparc, sparc64, vax, or hppa works a bit differently: 55 | they use an ISO image instead of a directory containing boot floppies and sets: 56 | .Pp 57 | .Dl anita install http://ftp.netbsd.org/pub/NetBSD/iso/10.0/NetBSD-10.0-sparc.iso 58 | .Pp 59 | The evbarm and riscv ports do not currently support installation 60 | using sysinst, so anita will instead install them by decompressing 61 | the pre-built disk image from the release onto the beginning 62 | of the virtual disk. The target will then be rebooted to trigger 63 | the automatic image resizing that happens on first boot, 64 | and after that, these ports work the same as the other ports. 65 | To download an evbarm or riscv image, use a command like the following: 66 | .Pp 67 | .Dl anita install http://nycdn.netbsd.org/pub/NetBSD-daily/HEAD/latest/evbarm-aarch64/ 68 | .Dl anita install http://nycdn.netbsd.org/pub/NetBSD-daily/HEAD/latest/riscv-riscv64/ 69 | .Pp 70 | To boot the newly installed hard disk image and get a login prompt, 71 | replace 72 | .Ar install 73 | with 74 | .Ar interact : 75 | .Pp 76 | .Dl anita interact http://ftp.netbsd.org/pub/NetBSD/NetBSD-10.0/i386/ 77 | .Pp 78 | When you are done interacting with the virtual machine, you can kill it by 79 | typing control-a x (qemu) or control-c (gxemul). 80 | .Pp 81 | To run the NetBSD test suite on the installed system, use 82 | .Pp 83 | .Dl anita test http://ftp.netbsd.org/pub/NetBSD/NetBSD-10.0/i386/ 84 | .Pp 85 | If you have a recent NetBSD host and a qemu version that supports 86 | NVMM (the NetBSD Virtual Machine Monitor), you can use it to run the 87 | tests several times faster than using qemu's built-in emulation: 88 | .Pp 89 | .Dl anita --vmm-args="-accel nvmm" test http://ftp.netbsd.org/pub/NetBSD/NetBSD-10.0/i386/ 90 | .Pp 91 | To install a snapshot, use something like the following (adjusting 92 | the version number in the sparc URL as needed): 93 | .Pp 94 | .Dl anita install http://nycdn.netbsd.org/pub/NetBSD-daily/HEAD/latest/i386/ 95 | .Dl anita install http://nyftp.netbsd.org/pub/NetBSD-daily/HEAD/latest/images/NetBSD-10.99.7-sparc.iso 96 | .Pp 97 | If you have built a NetBSD release locally using 98 | .Ic "build.sh -R" , 99 | you can point 100 | .Nm 101 | directly at the RELEASEDIR or ISO using a "file:" URL: 102 | .Pp 103 | .Dl anita install file:///path/to/releasedir/i386/ 104 | .Pp 105 | or simply use an absolute pathname as shorthand for the above: 106 | .Pp 107 | .Dl anita install /path/to/releasedir/i386/ 108 | .Sh HOW IT WORKS 109 | .Nm 110 | works by "screen scraping" the sysinst output over an emulated 111 | serial console, recognizing sysinst prompts and sending canned 112 | responses. 113 | .Pp 114 | .Nm 115 | will create a work directory and cache the distribution sets, boot 116 | floppies, and a hard disk image in it. By default, the work directory 117 | is a subdirectory of the current working directory, with a unique 118 | name automatically generated from the distribution URL, for example, 119 | .Pp 120 | .Dl work-http---ftp.netbsd.org-pub-NetBSD-NetBSD-10.0-i386-+a4c39 121 | .Pp 122 | You can also specify the name of the work directory explicitly using 123 | the 124 | .Fl -workdir 125 | option. 126 | .Pp 127 | When you rerun 128 | .Nm 129 | with the same URL, files that already exist in 130 | the work directory will not be re-downloaded or rebuilt, so if you 131 | run you 132 | .Ic "anita install" 133 | with the same URL twice in a row, the second 134 | run will effectively be a no-op, and if you rerun 135 | .Ic "anita interact" , 136 | the system will be booted from the existing disk image, skipping the 137 | installation stage. To force things to be redone, simply remove the 138 | work directory. If you remove just the hard disk image file 139 | .Pa wd0.img , 140 | .Ic "anita install" 141 | will recreate it from the cached distribution files. 142 | .Pp 143 | To ensure that the cached system state is always that of a 144 | freshly installed system, 145 | .Nm 146 | enables the 147 | .Ic qemu 148 | snapshotting feature for the system disk by default (but not for other disks). 149 | Therefore, if you log in and make changes to the system, they will not 150 | be saved to the disk image file. To override this behavior, specify the 151 | .Fl -persist 152 | option. 153 | .Pp 154 | .Sh MODES 155 | The operation performed by 156 | .Nm 157 | is determined by the 158 | .Ar mode 159 | argument, which takes the following values: 160 | .Bl -tag -width indent 161 | .It Ar install 162 | Install NetBSD if not already installed. 163 | .It Ar boot 164 | Install NetBSD if not already installed, then boot the 165 | installed system to the login prompt. 166 | .It Ar interact 167 | Install NetBSD if not already installed, boot it, 168 | and then connect the serial console to the terminal for 169 | interactive use. The 170 | .Cm qemu 171 | escape character control-a is in effect; for example, you can use 172 | control-a x to exit, control-a c to enter the 173 | .Cm qemu 174 | monitor, or control-a b to send a break (useful for entering DDB). 175 | Interacting with the boot blocks and other aspects of the boot process 176 | is possible unless the 177 | .Fl -run 178 | option has been specified. With 179 | .Fl -run , 180 | the interactive session only starts once the 181 | .Fl -run 182 | command has been successfully run. 183 | .Pp 184 | .It Ar test 185 | Install NetBSD if not already installed, then boot it and 186 | run the test suite from 187 | .Pa /usr/tests . 188 | By default, the test suite is run using ATF. If NetBSD was built with 189 | .Sq MKKYUA=yes , 190 | you can use Kyua instead by passing the option 191 | .Fl -tests 192 | .Ar kyua . 193 | The raw output of the test execution and various other test reports are 194 | stored in a 195 | .Pa tests/ 196 | subdirectory under the work directory. 197 | .Pp 198 | When Kyua is used to run the test suite, the 199 | .Pa tests/ 200 | subdirectory contains the raw output of the tests execution, a copy of 201 | the Kyua database in the 202 | .Pa store.db 203 | file with the results of the tests, and an itemized HTML report in the 204 | .Pa html/ 205 | subdirectory. 206 | .Pp 207 | When the ATF tools are used to run the test suite, the 208 | .Cm atf-report 209 | output will be displayed on standard output and the following output 210 | files are placed in the 211 | .Pa tests/ 212 | subdirectory: the raw 213 | .Cm atf-run 214 | output in 215 | .Pa test.tps , 216 | the output from 217 | .Cm "atf-report -o xml" 218 | in 219 | .Pa test.xml , 220 | and the output from 221 | .Cm "atf-report -o ticker" 222 | in 223 | .Pa test.txt . 224 | To facilitate the further processing of the XML output into HTML, 225 | the files 226 | .Pa tests-results.xsl , 227 | .Pa tests-results.dtd , 228 | and 229 | .Pa tests-results.css 230 | are also included. 231 | .It Ar print-workdir 232 | Print the pathname of the work directory on standard output. 233 | This is intended for use by scripts that need to access files 234 | in the work directory, particularly when the 235 | .Fl -workdir 236 | option is not used but the name of the directory is automatically 237 | generated. 238 | .El 239 | .Sh OPTIONS 240 | The following command line options are supported: 241 | .Bl -tag -width indent 242 | .It Fl -workdir Ar directory 243 | The work directory. The default is an automatically generated 244 | name under ".". 245 | .It Fl -vmm Ar qemu | xl | xm | gxemul | simh 246 | Specify the virtual machine monitor. 247 | The default virtual machine monitor is qemu. 248 | If the target system architecture is not supported by qemu, 249 | .Nm 250 | will automatically switch to 251 | .Cm gxemul 252 | or 253 | .Cm simh 254 | as needed. 255 | If 256 | .Nm 257 | is running in a Xen dom0 and the target system architecture 258 | is i386 or amd64, it is also possible to 259 | specify 260 | .Cm xl 261 | or 262 | .Cm xm , 263 | which will make 264 | .Nm 265 | install the target system in a Xen domU using the current 266 | .Cm xl 267 | or the historic 268 | .Cm xm 269 | interface, respectively. This requires 270 | running 271 | .Nm 272 | as root. Any changes made to the system disk image will 273 | be persistent whether or not the 274 | .Fl -persist 275 | option was given. The Xen support should be considered experimental 276 | and may not be fully tested. For backwards compatibility, 277 | .Ar xen 278 | is accepted as a synonym for 279 | .Ar xm . 280 | .It Fl -vmm-args Ar string 281 | Additional arguments to pass to the virtual machine monitor (e.g., qemu). 282 | The arguments are given 283 | as a single string, which may contain multiple arguments separated 284 | by whitespace. There is no way to pass an argument containing 285 | whitespace. This option was formerly called 286 | .Fl -qemu-args ; 287 | the old name is still accepted for backwards compatibility. 288 | .It Fl -disk-size Ar size 289 | The size of the virtual disk NetBSD gets installed on. The default 290 | is large enough to hold the OS installation itself when also using 291 | default values for the 292 | .Fl -sets 293 | and 294 | .Fl -memory-size 295 | options, but if you need 296 | additional space, you can specify a larger size. The size is given in 297 | bytes, or a suffix of k, M, G, or T can be used for kilo-, mega-, 298 | giga-, or terabytes. 299 | .It Fl -memory-size Ar size 300 | The size of the virtual RAM. The size is given in 301 | bytes, or a suffix of k, M, or G can be used as with 302 | the 303 | .Fl -disk-size 304 | option. The default is architecture dependent. Note that since 305 | sysinst sizes the swap partition based on the amount of RAM, if you 306 | run 307 | .Cm anita install 308 | with a large 309 | .Fl -memory-size , 310 | you may also have to increase 311 | .Fl -disk-size . 312 | .It Fl -run Ar command 313 | Log in to the virtual machine as root and execute the given shell 314 | .Ar command 315 | in it once it has booted. This is only meaningful when used with the 316 | .Ar boot 317 | or 318 | .Ar interact 319 | command. Since the command is sent to an interactive shell over the 320 | console tty, it should be kept short and simple to avoid running into tty 321 | limitations or quoting issues. Complex commands may be executed by 322 | preparing a disk image containing a file system containing a shell 323 | script, and specifying something like 324 | .Pp 325 | .Dl --vmm-args '-hdb disk.img' --run 'mount /dev/wd1a /mnt && /mnt/script' 326 | .Pp 327 | The shell command is run using 328 | .Cm /bin/sh 329 | regardless of the login shell of the root user on the target system. 330 | The exit status of the shell command is returned as the exit status 331 | of 332 | .Nm . 333 | .It Fl -sets Ar sets 334 | The distribution sets to install, as a comma-separated list. 335 | For a minimal install, use something like 336 | .Pp 337 | .Dl --sets kern-GENERIC,modules,base,etc 338 | .Pp 339 | A kernel, base, and etc must always be included. 340 | .It Fl -run-timeout Ar timeout 341 | Set a timeout for the tests run using the 342 | .Fl -run 343 | option, in seconds. The default is 3600 seconds (one hour). 344 | .It Fl -test-timeout Ar timeout 345 | Set a timeout for the tests run in the 346 | .Cm test 347 | mode, in seconds. Starting with 348 | .Nm 349 | version 2.2, the timer is reset at the beginning of each ATF test 350 | program, so the timeout only needs to be greater than the duration 351 | of the longest test program rather than the full test run. 352 | The default is 10800 seconds (3 hours). 353 | .It Fl -persist 354 | Store any changes to the contents of the system disk persistently, 355 | such that they may affect future 356 | .Nm 357 | runs, instead of the default behavior where only the 358 | .Ar install 359 | mode can modify the disk contents and all other modes work with 360 | an ephemeral snapshot copy of the freshly installed system. 361 | .It Fl -boot-from Ar cdrom | floppy | kernel 362 | For architectures that support booting from more than one type of 363 | media (typically CD-ROM or floppies), specify which one to use. 364 | Most architectures only support one type of boot media, and 365 | specifying an unsupported typ will cause the boot to fail. Due 366 | to limitations of the emulators or their emulated firmware, some 367 | architectures cannot be booted from install media at all, and 368 | instead boot by passing a kernel directly to the emulator. 369 | The default is 370 | .Ar floppy 371 | for i386, 372 | .Ar kernel 373 | for alpha, and 374 | .Ar cdrom 375 | for all other targets. 376 | .It Fl -structured-log 377 | Generate log output in a structured format similar to Python code. 378 | Commands sent to the serial console by 379 | .Nm 380 | are logged as 381 | .Cm send(t, 'command...') , 382 | where 383 | .Va t 384 | is a timestamp in seconds since the Unix epoch. 385 | Data received are logged 386 | as 387 | .Cm recv(t, 'data...') . 388 | Calls to pexpect's 389 | .Fn expect 390 | function are logged as 391 | .Cm expect(t, 'regexp...') , 392 | and the actual strings matched by them as 393 | .Cm match(t, '...') . 394 | Unprintable characters in the data strings are escaped using Python 395 | string syntax. 396 | .Pp 397 | The default is to do unstructured logging where the raw output from 398 | the virtual machine console is sent to standard output as-is, 399 | and the commands sent to the console are only logged if echoed 400 | by the virtual machine. 401 | .It Fl -structured-log-file 402 | Like 403 | .Fl -structured-log , 404 | but logs to a given file rather than to standard output, 405 | and in addition to rather than instead of the default 406 | unstructured logging. 407 | .It Fl -tests Ar kyua | atf 408 | The test framework to use for running tests. The default is 409 | .Cm atf . 410 | .It Fl -dtb 411 | The location of the Device Tree Blob file, needed with the 412 | .Ar evbarm-earmv7hf 413 | port when using the default 414 | .Ar vexpress-a15 415 | machine type. The default is the location of the file 416 | .Pa vexpress-v2p-ca15-tc1.dtb 417 | in the 418 | .Pa dtb-arm-vexpress 419 | package assuming both it and 420 | .Nm 421 | itself have been installed via pkgsrc. 422 | .It Fl -xen-type Ar type 423 | Select the type of virtualization to use with Xen. This can be 424 | .Ar pv , 425 | .Ar pvshim , 426 | .Ar hvm , 427 | or 428 | .Ar pvh . 429 | The default is 430 | .Ar pv . 431 | .It Fl -image-format Ar format 432 | The disk image format to use for the virtual machine's system disk. 433 | Supported values are 434 | .Ar dense , 435 | a raw disk image that has been fully preallocated by writing zeros, 436 | and 437 | .Ar sparse , 438 | a raw disk image with holes (when supported by the underlying file 439 | system). The default is 440 | .Ar dense . 441 | .It Fl -machine Ar machine 442 | The machine type to emulate. This may be used with the 443 | evbarm-earmv7hf port to select the 444 | .Ar virt 445 | qemu machine type instead of the default of 446 | .Ar vexpress-a15 . 447 | Not consistently supported for other ports and VMMs. 448 | .It Fl -partitioning-scheme Ar scheme 449 | Use the given partitioning scheme instead of the port's default one. 450 | Currently only supported for the i386 and amd64 ports, where a scheme 451 | of 452 | .Ar MBR 453 | can be selected instead of the default of GPT. 454 | .It Fl -no-entropy 455 | If sysinst prompts the user to enter entropy and offers an option not 456 | to enter it, select that option. This is intended for testing the 457 | behavior of said option and the resulting installation, and only works 458 | with certain versions of NetBSD that offer such an option. The 459 | default is to supply the guest being installed with entropy from the 460 | host. 461 | .El 462 | .Sh DEBUGGING NETBSD USING ANITA 463 | .Nm 464 | can serve as a convenient platform of installing and booting NetBSD 465 | for debugging purposes. When doing this, it is useful to build NetBSD 466 | with debug symbols and to install the source on the virtual machine to 467 | enable source-level debugging. 468 | .Pp 469 | For more details, please see 470 | .Dl http://wiki.netbsd.org/kernel_debugging_with_qemu/ . 471 | .Sh SEE ALSO 472 | .Xr atf-report 1 , 473 | .Xr atf-run 1 , 474 | .Xr qemu 1 , 475 | .Xr kyua 1 , 476 | .Xr tests 7 477 | .Sh BUGS IN ANITA 478 | .Nm 479 | supports only a limited number of NetBSD ports. There may 480 | be other ports with working emulators, and support should 481 | be added for those. Patches are welcome. 482 | .Pp 483 | .Nm 484 | is likely to break whenever any significant change is made to 485 | the sysinst user interface. 486 | .Pp 487 | Installing NetBSD releases older than 2.1 has not been tested. 488 | .Pp 489 | Exporting ATF test reports to the host system does not yet work 490 | with the evbarm-earmv7hf or hpcmips ports for lack of support for 491 | a second block device. 492 | .Pp 493 | .Sh BUGS IN NETBSD 494 | .Pp 495 | NetBSD/i386 releases older than 4.0 will install, but when booting 496 | the installed image, they hang after the "root on ffs" message. 497 | .Pp 498 | NetBSD/i386 versions older than 2009-06-13 13:35:11 fail to find 499 | any PCI buses when run under qemu; see PRs 38729 and 42681. 500 | .Pp 501 | NetBSD/vax is unable to run the ATF tests as of source date 502 | 2018.03.22.12.16.11. 503 | .Pp 504 | Timing is off by a factor of two when running on NetBSD hosts; 505 | see PR 43997. 506 | .Pp 507 | For current reports of other NetBSD bugs found using anita, see 508 | .Pp 509 | .Dl http://releng.netbsd.org/test-results.html 510 | .Pp 511 | .Sh BUGS IN QEMU 512 | .Pp 513 | Versions of qemu known to work with 514 | .Nm 515 | on NetBSD hosts are 516 | 0.15, 1.2, 1.4.1, and 2.0.0nb4 517 | or newer. 518 | .Pp 519 | Some floating point tests fail under qemu but not on real hardware, 520 | presuambly due to bugs qemu's floating point emulation. 521 | In particular, floating point exceptions are broken when using 522 | the default TCG acceleration, as reported in 523 | .Pp 524 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/215 525 | .Pp 526 | Running multithreaded programs (such as the NetBSD test suite) on an 527 | emulated i386 or amd64 system used to require qemu patches that were 528 | in pkgsrc beginning with qemu 0.12.3nb3. They were finally integrated 529 | into qemu on 2011-12-11. See PR 42158 and 530 | .Pp 531 | .Dl https://bugs.launchpad.net/bugs/569760 532 | .Pp 533 | for details. 534 | .Pp 535 | When attempting to install NetBSD-current in qemu 1.0, it panics 536 | during the install kernel boot due to a regression in qemu's 537 | emulation of the PCI configuration registers. The work-around 538 | is to use qemu 0.xx. See PR 45671 and 539 | https://bugs.launchpad.net/qemu/+bug/897771 for details. 540 | This bug has since been fixed on the qemu mainline. 541 | .Pp 542 | In addition to the above, there have been several further 543 | regression in the 1.x series of qemu that have impacted 544 | .Nm : 545 | .Pp 546 | .Dl https://bugs.launchpad.net/qemu/+bug/1089996 547 | .Dl https://bugs.launchpad.net/qemu/+bug/1091241 548 | .Dl https://bugs.launchpad.net/qemu/+bug/1127369 549 | .Dl https://bugs.launchpad.net/qemu/+bug/1154328 550 | .Pp 551 | These are believed to be fixed in qemu 1.5. 552 | .Pp 553 | Installing NetBSD 5 or older on i386 or amd64 takes a long 554 | time with recent versions of qemu because the bootloader countdown 555 | runs at 1/20 the normal speed, and there is a long delay between 556 | loading the kernel and the kernel printing its first console output, 557 | which can easily be mistaken for a hang. Please be patient. This 558 | issue has been worked around in NetBSD 6 and newer; see PR 43156 for 559 | details. 560 | .Pp 561 | Installing using qemu version 1.5.1, 1.6.0, 1.7.0, or 2.0.0 562 | prior to 2.0.0nb4 on 563 | NetBSD fails due to the serial console dropping characters; see 564 | PR 48071 and the qemu bug reports 565 | .Pp 566 | .Dl https://bugs.launchpad.net/qemu/+bug/1335444 567 | .Dl https://bugs.launchpad.net/qemu/+bug/1399943 568 | .Pp 569 | The same qemu versions work when hosted on Linux, but 570 | only by accident. This bug is fixed for i386 and amd64 571 | targets in qemu 2.0.0nb4 in pkgsrc and the qemu 2.1 release, 572 | and finally fixed for sparc in qemu 2.8. 573 | .Pp 574 | Sending a break sequence to the serial console using 575 | "control-a b" was broken, fixed, broken again, fixed 576 | again, broken again as the fix was reverted 577 | for causing another regression, and finally fixed again 578 | in qemu 3.0: 579 | .Pp 580 | .Dl https://bugs.launchpad.net/qemu/+bug/1654137 581 | .Pp 582 | Interacting with the boot blocks over the serial console 583 | is also currently broken: 584 | .Pp 585 | .Dl https://bugs.launchpad.net/qemu/+bug/1743191 586 | .Pp 587 | The qemu 588 | .Fl icount 589 | option looks useful for making the tests less dependent on host timing, 590 | and its 591 | .Ar sleep=on|off 592 | argument might be used to speed up the tests by emulating the passage 593 | of time instead of waiting for actual time to pass. This is now 594 | partly functional as 595 | .Pp 596 | .Dl https://bugs.launchpad.net/qemu/+bug/1774677 597 | .Pp 598 | has been fixed, but causes spurious IDE disk errors in the guest. 599 | Qemu also advertises record/replay capability, but it does not actually 600 | work, as reported in 601 | .Pp 602 | .Dl https://bugs.launchpad.net/qemu/+bug/1810590 603 | .Pp 604 | In qemu 5.1.0, booting NetBSD/sparc did not work: 605 | .Pp 606 | .Dl https://bugs.launchpad.net/qemu/+bug/1892540 607 | .Pp 608 | This is fixed since 5.2.0. 609 | .Pp 610 | Other qemu bugs affecting NetBSD include 611 | .Pp 612 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/1269 613 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/2654 614 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/2654 615 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/2741 616 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/2773 617 | .Dl https://gitlab.com/qemu-project/qemu/-/issues/2775 618 | .Pp 619 | .Sh BUGS IN KVM 620 | .Pp 621 | When 622 | .Nm 623 | is run on a Linux host using qemu-kvm 0.12.3, and is used to 624 | boot a version of NetBSD-current newer than 2009-11-04 625 | 14:39:17, the emulated NetBSD system hangs during boot; see 626 | PR 44069 for details. This issue can be worked 627 | around by passing Anita the command line option 628 | .Fl -vmm-args 629 | .Ar -no-kvm 630 | to disable kvm. The alternative 631 | .Fl -vmm-args 632 | .Ar -no-kvm-irqchip 633 | performs better but doesn't quite work: the system installs and 634 | boots, but the test suite occasionally fails to complete; see PR 44176. 635 | .Pp 636 | As of 2020, the above issue has been fixed. 637 | .Pp 638 | .Sh BUGS IN PYTHON 639 | .Pp 640 | Versions of 641 | .Nm 642 | prior to 1.40 may get the error 643 | .Pp 644 | .Dl [Errno ftp error] 200 Type set to I 645 | .Pp 646 | during the downloading of distribution sets; this is a regression in 647 | Python 2.7.12: 648 | .Pp 649 | .Dl http://bugs.python.org/issue27973 650 | .Pp 651 | This problem is worked around in 652 | .Nm 653 | 1.40. 654 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | 2 | - 2.17 released - 3 | 4 | When downloading a file results in an error, attempt to determine 5 | whether the error is due to the expected nonexistence of the file or 6 | some other error that should be treated differently. 7 | 8 | Fix a compatibility issue between setup.py and recent pkgsrc pip. 9 | 10 | Fix a regression in NetBSD/pmax target support. 11 | 12 | - 2.16 released - 13 | 14 | Deal with Python 3.13 no longer having urllib.FancyURLopener 15 | 16 | - 2.15 released - 17 | 18 | Fix invalid escape sequences that cause warnings in Python 3.12. 19 | 20 | - 2.14 released - 21 | 22 | In the deprecated anita.Release method, use https, and use 23 | archive.netbsd.org for releases older than 9.0. 24 | 25 | Ignoring a suffix of "-dvd" when deducing the architecture name from 26 | the URL of an install ISO, to work around the issue of PR 58357. 27 | 28 | Define a scratch disk for vax guests so that they can export ATF test 29 | results to the host. 30 | 31 | Support Xen pvshim. From Manuel Bouyer. 32 | 33 | - 2.13 released - 34 | 35 | Install the base32 set by default (when present). 36 | 37 | When installing a NetBSD version that has the new "base32" set, don't 38 | mistake it for the "base" set. 39 | 40 | - 2.12 released - 41 | 42 | Fix incorrect scratch disk device name for riscv-riscv64 causing the 43 | export of test result data to the host to fail. 44 | 45 | Support the new manhtml, base32, and debug32 sets. 46 | 47 | When installing a NetBSD version that has the new "manhtml" set, don't 48 | mistake it for the "man" set. 49 | 50 | - 2.11 released - 51 | 52 | Increase the default memory size for sparc to 64M. 53 | 54 | On macOS, use the -joliet option to "hdiutil makehybrid" as the -iso 55 | option fails to create a Rock Ridge file system even though the 56 | hdiutil manpage says it will. From Benny Siegert. 57 | 58 | Add initial support for riscv-riscv64. 59 | 60 | Increase the default of the --test-timeout option to 3 hours, as the 61 | timeout of the crypto/libcrypto/t_libcrypto:bn test case in the NetBSD 62 | ATF test suite has now been increased to 2 hours, and anita must use a 63 | longer timeout than any individual test case. 64 | 65 | New command line --no-entropy. 66 | 67 | Support Xen PVH virtualization, enabled by "--xen-type pvh". 68 | From Manuel Bouyer. 69 | 70 | New command line --partitioning-scheme, for testing i386 and amd64 71 | with MBR partitioning rather than the default GPT partitioning. 72 | 73 | - 2.10 released - 74 | 75 | Support the new sysinst root password and entropy configuration user 76 | interface introduced on source date 2022.04.21.17.30.15. 77 | 78 | Fix stray quotes in the anita --help output. 79 | 80 | When choosing to overwrite a non-NetBSD partition, handle yes/no 81 | choices presented in either order. 82 | 83 | Define a scratch disk for macppc (wd1c) so that ATF test results can 84 | be exported to the host. 85 | 86 | - 2.9 released - 87 | 88 | Support the new "gpufw" set. 89 | 90 | Actually reduce the default test timeout to 1 hour as documented in 91 | version 2.5 when anita is invoked as a program rather than as a 92 | library. 93 | 94 | Log a message to the structured log file on timeout so that the time 95 | when the timeout occurred can be determined from the message timestamp. 96 | 97 | Define a scratch disk for alpha (wd1c) so that ATF test results can 98 | be exported to the host. 99 | 100 | In addition to the plain-text ATF test report printed to the console 101 | as the tests progress, also store a clean copy without other console 102 | output in test.txt and export it to the host. Suggested by 103 | Greg Troxel. 104 | 105 | Fix failure to destroy the Xen DomU used by the installation stage 106 | before trying to create the one for the boot stage when running 107 | under Python 3. 108 | 109 | Fix failure to log in after reboot when running "anita test" on a 110 | self-resizing image without running a separate "anita install" first. 111 | From Joerg Sonnenberger. 112 | 113 | Support coypu's proposed sysinst entropy patch. 114 | 115 | Remove extra newline in reponse to entropy prompt. 116 | 117 | - 2.8 released - 118 | 119 | Preemeptively support sysinst requesting entropy as a single line. 120 | 121 | Fix bug in 2.7 that caused the provision of entropy to the installer 122 | to fail when running under Python 3. 123 | 124 | Omit ",cdrom" when passing a CD-ROM device to a Xen PV domain because 125 | doing so will cause NetBSD/Xen to ignore device since xenbus_probe.c 126 | 1.51. 127 | 128 | - 2.7 released - 129 | 130 | Fix bug in 2.6 that caused the provision of entropy to the installer 131 | to fail when running under Python 2. 132 | 133 | - 2.6 released - 134 | 135 | Support upcoming sysinst changes to support interactive input of 136 | entropy. 137 | 138 | Add support for NetBSD/alpha targets. 139 | 140 | The "interact" mode now supports interaction with the boot process, 141 | except when the --run option is used. 142 | 143 | - 2.5 released - 144 | 145 | Reset the test timeout once per test case, not just once per test 146 | program, and reduce the default timeout to 1 hour. 147 | 148 | - 2.4 released - 149 | 150 | Deal with the root device name of evbarm-aarch64 changing as a result 151 | of the the switch to GPT at source date 2020.05.24.18.44.46. 152 | 153 | - 2.3 released - 154 | 155 | Deal with curses sometimes optimizing away the final "s" in "Debug 156 | symbols", causing the debug set to not be installed. 157 | 158 | Increase default memory size for amd64 from 128M to 192M, as the 159 | installer no longer runs reliably in 128M without exhausting memory. 160 | 161 | Keep logging the console output for a few seconds after shutdown to 162 | catch the autoconf detach messages and possible panics on detach. 163 | 164 | When logging shell commands, avoid line breaks between options and 165 | their arguments. 166 | 167 | Support the "virt" machine type for evbarm-eamv7hf as an alternative 168 | to the default of vexpress-a15. To enable, use the command line 169 | option --machine virt. 170 | 171 | - 2.2 released - 172 | 173 | Clean up some b'' quoting that appeared around strings in the log 174 | output under python3. 175 | 176 | New command line option --image-format for selecting the guest disk 177 | image format. Supported values are "dense" (the default) and 178 | "sparse". 179 | 180 | Reset the install timeout whenever a new set extraction or other 181 | command is started by sysinst, and reduce the timeout to 1 hour. 182 | This allows hangs in the install phase to be detected more quickly. 183 | 184 | Reset the test timeout whenever a new test program is started by ATF, 185 | and reduce the default timeout to 2 hours. If you are passing a 186 | non-default test timeout with the --test-timeout option, it can also 187 | be reduced. This allows hangs during test runs to be detected more 188 | quickly. 189 | 190 | Fix the sparc64 CD install which was broken in 2.1, by adding an 191 | index=2 attribute to the virtual CD drive. 192 | 193 | Make NetBSD-current/macppc boot by passing "-prom-env qemu_boot_hack=y" 194 | to qemu-system-ppc. From Joerg Sonnenberger. 195 | 196 | - 2.1 released - 197 | 198 | Add support for the newly added rescue set. 199 | 200 | Deal with the sysinst changes of 2019-11-16. 201 | 202 | Add support for macppc targets, based on a patch from Joerg 203 | Sonnenberger. 204 | 205 | Fix failure to install the sharesrc set due to curses optimization. 206 | 207 | - 2.0 released - 208 | 209 | In the test suite, run the tests in parallel to reduce the run time. 210 | 211 | Downloads can now be interrupted using SIGINT (control-C). 212 | 213 | On NetBSD, invoke makefs using its absolute path /usr/sbin/makefs 214 | as /usr/sbin may not be in PATH. 215 | 216 | Add support for Python 3. 217 | 218 | When using qemu from pkgsrc, log the exact pkgsrc version of qemu and 219 | glib2, including any nb suffix. 220 | 221 | - 1.49 released - 222 | 223 | Fix issue introduced in 1.48 that caused the Xen install kernel to be 224 | used instead of the regular Xen kernel when booting the installed 225 | system. 226 | 227 | - 1.48 released - 228 | 229 | When sysinst offers a choice of partitioning schemes, anita now uses 230 | the one listed first. Therefore, i386 and amd64 installs of versions 231 | where sysinst offers GPT as the first choice will use GPT rather than 232 | MBR as was previously the case. 233 | 234 | Add support for NetBSD/hppa targets. 235 | 236 | Support some upcoming sysinst UI changes. 237 | 238 | The structured log file is now flushed after each line so that 239 | it can be used to debug a hung installation without waiting for 240 | it to time out first. 241 | 242 | Move the automatic determination of the type of the URL argument 243 | (e.g., distribution directory vs. installation ISO) into a new library 244 | function anita.distribution() so that it can be used not only from the 245 | command line but also by Python programs calling anita as a library. 246 | 247 | When using a pre-installed image, "anita install" will now boot it to 248 | let it resize itself, rather than postponing the resize to when the 249 | image is later booted in another mode such as "anita test". 250 | 251 | - 1.47 released - 252 | 253 | Deal with the removal of the VEXPRESS_A15 kernel, used by the 254 | evbarm-earmv7hf target, in favor of GENERIC. 255 | 256 | - 1.46 released - 257 | 258 | Add support for NetBSD/evbarm-aarch64 targets. 259 | 260 | Add support for NetBSD/sparc64 targets, from Utkarsh Anand. 261 | 262 | The run_tests() method now halts the VM, to ensure that the 263 | scratch disk containing ATF test results is flushed by the 264 | guest before being read by the host. 265 | 266 | - 1.45 released - 267 | 268 | Support xz compressed distribution sets. 269 | 270 | Support NetBSD/vax via simh. Installing and booting works; 271 | running the ATF tests does not. From Utkarsh Anand. 272 | 273 | Mark the NumberedVersion and Release classes as deprecated. 274 | 275 | Remove the obsolete clases DailyBuild and LocalBuild. 276 | 277 | Fix a bug that caused the guest OS to unnecessarily boot twice 278 | when entering "interact" mode since version 1.43. 279 | 280 | Don't send a spurious "x" at the end of installation. 281 | 282 | Log the pexpect version. 283 | 284 | - 1.44 released - 285 | 286 | Disable optimization avoiding copies when installing from a file: URL 287 | or local path, as it broke installation of source sets. 288 | 289 | Deal with pexpect 4.3.1 requiring delayafterclose and 290 | delayafterterminate to be set in two different places 291 | (https://github.com/pexpect/pexpect/issues/462). 292 | 293 | Support installing on disks with a corrupt or out-of-date MBR. 294 | 295 | Log the command line arguments on startup. 296 | 297 | - 1.43 released - 298 | 299 | Log interactive mode input and output to the structured log file. 300 | 301 | Add support for NetBSD/hpcmips targets, from Utkarsh Anand. 302 | 303 | - 1.42 released - 304 | 305 | VM console input is no longer logged to stdout, to keep it from 306 | appearing twice when echoed by the VM. 307 | 308 | Fix VM console output appearing twice in interact mode (again). 309 | 310 | Add support for gxemul as a new VMM and NetBSD/pmax as a new guest. 311 | From Utkarsh Anand. 312 | 313 | Increase the default test timeout to 24 hours, as the tests now 314 | take much longer to run than before due to a combination of 315 | the addition of new tests and qemu performance regressions. 316 | 317 | Added target support for NetBSD/evbarm-earmv7hf, from Utkarsh Anand. 318 | This installs from a pre-built image rather than using sysinst. 319 | 320 | When installing from a file: URL or local path, avoid creating 321 | unnecessary copies of the release files. From Utkarsh Anand. 322 | 323 | New command line option --tests for explicitly selecting the the Kyua 324 | test framework instead of ATF, from Utkarsh Anand. 325 | 326 | - 1.41 released - 327 | 328 | If the VMM exits unexpectedly while running the ATF tests or some 329 | other shell command, log the exit status and the signal that caused it 330 | to exit (if any). 331 | 332 | Log the qemu version used to stdout. 333 | 334 | Fix reference to uninitialized variable when reporting a 335 | timeout waiting for VM halt confirmation. 336 | 337 | Increase default child timeout from 300 to 600 seconds 338 | to support VMMs where power-up takes a long time. 339 | 340 | Increase child.delayafterclose and child.delayafterterminate 341 | to support VMMs where power-down takes a long time. 342 | 343 | Run more network diagnostics after failed network installs. 344 | 345 | Add timestamps to structured log output. 346 | 347 | - 1.40 released - 348 | 349 | Work around a bug in the urllib library of Python 2.7.12 that causes 350 | FTP transfers to fail with the error message "[Errno ftp error] 200 351 | Type set to I"; see https://bugs.python.org/issue27973. 352 | 353 | When halting the child results in an EOF, don't print the exception 354 | since that may be confused with a failure. 355 | 356 | Run vmstat -s at the end of the ATF tests to help diagnose issues 357 | like excessive paging due to insufficient memory. 358 | 359 | Increase the default of the --test-timeout option from 3 hours to 8 360 | hours, since the sparc tests are now taking more than 5 hours. 361 | 362 | Increase the default disk size to 1526M, as 1G is no longer enough 363 | for amd64. 364 | 365 | Pass format=raw in the qemu -drive option to suppress warnings in 366 | recent qemu versions. 367 | 368 | - 1.39 released - 369 | 370 | Deal with incompatible change in the output format of the "info block" 371 | command in qemu 2.5.0. 372 | 373 | Fix bug where the exit status of the shell command specified with the 374 | --run option was not propagated to the exit status of anita. 375 | 376 | New public method start_boot, to facilitate writing test cases that 377 | interact with the boot prompt. 378 | 379 | The structured log file now also contains informational messages, 380 | of the form info('message'). 381 | 382 | - 1.38 released - 383 | 384 | Deal with the sysinst message changes of 2015-04-30. 385 | 386 | When halting the VM, don't quit as soon as the text "halted" is seen, 387 | because it may be part the message "halted by root", which is printed 388 | before disks have been synced. Instead look for "The operating system 389 | has halted", EOF, or a timeout. 390 | 391 | - 1.37 released - 392 | 393 | The global test() function is no longer supported, use the run_tests() 394 | method of the Anita object instead. 395 | 396 | When halting the VM, do not attempt to log in if already logged in. 397 | 398 | Make the distinctive shell prompts different from one invocation of 399 | anita to another, so that anita can run itself recursively on the VM 400 | without the outer instance mistaking a shell prompt from an inner 401 | instance for one of its own. 402 | 403 | - 1.36 released - 404 | 405 | When running a shell command, wrap the exit status in distictive text 406 | so that it can be reliably matched in the presence of buffered output 407 | containing other numeric strings. 408 | 409 | Halt the VM when done. 410 | 411 | Print the qemu command. 412 | 413 | - 1.35 released - 414 | 415 | Support Linux host systems that have genisoimage but not mkisofs, such 416 | as Debian 7. 417 | 418 | New command line option --run-timeout. 419 | 420 | - 1.34 released - 421 | 422 | Do not require releases to contain install floppies, since amd64 no 423 | longer has them. 424 | 425 | The default value of the --boot-from option is now "cdrom" if the 426 | install floppies are missing. 427 | 428 | When reporting a failed download of optional files, be clearer that 429 | this is not an error. Also, don't treat arbitrary exceptions during 430 | downloads as indicating a missing file, only the expected IOError. 431 | 432 | Increase the default disk size from 768M to 1G, as 768M is no 433 | longer sufficient for amd64. 434 | 435 | New command line option --structured-log-file. This is like 436 | --structured-log but takes the name of a log file as an 437 | argument, and the structured logging is in addition to, not 438 | instead of, the traditional logging to stdout. 439 | 440 | Increase installation timeout from 1 hour to 3 hours so that 441 | a system built with DEBUG and LOCKDEBUG can be installed under 442 | qemu without timing out. 443 | 444 | Unconditionally print a greeting message including the version number 445 | of startup, so that logs from automated anita runs will always 446 | indicate the version used. 447 | 448 | In --structured-log mode, log the string matched by expect() instead 449 | of the return value, as the latter is always zero and therefore 450 | conveys no useful information. 451 | 452 | - 1.33 released - 453 | 454 | Support recent versions of sysinst (CVS date 2014.08.03.16.09.38 or 455 | newer), where set selection happens after disk partitioning rather 456 | than before. 457 | 458 | Document qemu 2.0.0nb4 as a known working version. 459 | 460 | New command line option --structured-log. 461 | 462 | - 1.32 released - 463 | 464 | Fix installation of source and debug sets, which had been broken since 465 | the label displayed by sysinst was changed from "Source sets" to 466 | "Source and debug sets". 467 | 468 | - 1.31 released - 469 | 470 | Fix a failure to consume a shell prompt which would cause it to remain 471 | buffered and later be mistaken for one that had not yet issued. For 472 | added protection, add a unique serial number to each prompt. 473 | 474 | Omit drive index from qemu command line for backwards compatibility 475 | with scripts written for anita 1.29 or older. 476 | 477 | Fix X11 set names. 478 | 479 | - 1.30 released - 480 | 481 | Increase the default timeout for running the NetBSD test 482 | suite from 2 hours to 3 hours. 483 | 484 | Add support for installing the new "debug" set, and add a new 485 | man page section on using anita for debugging NetBSD. 486 | 487 | Organize the BUGS section of the man page into sections, 488 | and add links to four more qemu 1.x regressions. 489 | 490 | Add Kuya support, based on patch from Julio Merino. 491 | 492 | New command line option "--version". 493 | 494 | Support booting i386 and amd64 from boot-com.iso instead 495 | of the boot floppies, attaching a second virtual CD-ROM drive 496 | for the install sets. Enable with "--boot-from cdrom". 497 | 498 | Fix the Anita 1.2 backwards compatibility Release class. 499 | 500 | - 1.29 released - 501 | 502 | Fix "xm" Xen interface support broken in 1.28. 503 | 504 | - 1.28 released - 505 | 506 | The "xl" Xen interface can now be selected with "--vmm xl". 507 | 508 | Increase the default memory size for amd64 to 128M. 509 | 510 | Improve progress messages when creating the disk image. 511 | 512 | When execing /bin/sh, wait for its prompt before sending 513 | it a command. 514 | 515 | - 1.27 released - 516 | 517 | Run shell commands on the target system using /bin/sh rather 518 | than root's login shell, as sysinst no longer sets the login 519 | shell to /bin/sh by default. 520 | 521 | - 1.26 released - 522 | 523 | Track the sysinst changes of 2012-04-06 introducing the new 524 | post-install configuration menu. 525 | 526 | Print a message before creating the disk image, as it can take 527 | a long time and make it appear that anita has hung. 528 | 529 | - 1.25 released - 530 | 531 | Before running a shell command on the system under test, change the 532 | shell prompt to a more distinctive one to reduce the risk of command 533 | output being mistaken for a prompt. 534 | 535 | Support installing SPARC ISOs having a non-numeric suffix following 536 | the version number, such as NetBSD-6.0_BETA-sparc.iso. 537 | 538 | Added experimental support for using Xen instead of qemu as the 539 | virtual machine monitor. This is enabled by specifying "--vmm xen" on 540 | the anita command line while running as root in a Xen dom0. 541 | 542 | New command line option "--persist". 543 | 544 | - 1.24 released - 545 | 546 | Support the new distribution ISO naming scheme, e.g., 547 | NetBSD-5.99.60-sparc.iso instead of sparccd.iso. 548 | 549 | Support installation of the X11 and source sets. 550 | 551 | On unknown platforms, use mkisofs instead of genisoimage. 552 | 553 | On sparc, the scratch disk is sd1c, not wd1d. 554 | 555 | Allow a full hour for extracting sets. 556 | 557 | Deal with the backwards incompatible renaming of "qemu" to 558 | "qemu-system-i386" in qemu 1.0. 559 | 560 | After running the ATF tests, run "ps" to show any processes possibly 561 | left running by the tests. 562 | 563 | - 1.23 released - 564 | 565 | Support qemu 0.15, where the output of the "info block" monitor 566 | command has backwards-incompatibly changed to no longer include a 567 | "type" field. 568 | 569 | - 1.22 released - 570 | 571 | New command line option "--memory-size". 572 | 573 | - 1.21 released - 574 | 575 | Disk images no longer contain holes, making them easier to use with 576 | vnd(4). Partially based on patch from Vladimir Kirillov. 577 | 578 | Increase the default disk size from 512M to 768M, as 512M is no longer 579 | sufficient for amd64. 580 | 581 | - 1.20 released - 582 | 583 | Run df before and after the ATF tests so that disk usage data 584 | can be gathered from the test logs. 585 | 586 | Increase the default timeout for the ATF test from 1 hour to 2 hours 587 | as the number of tests has now increased to a point where 1 hour is 588 | not always enough even on a fast machine. 589 | 590 | Increase the timeout for the set extraction phase from 1200 to 2400 591 | seconds; the extra time is needed when installing a system built 592 | without optimization. 593 | 594 | - 1.19 released - 595 | 596 | Rename the --qemu-args option to --vmm-args in preparation for adding 597 | support for other virtual machine monitors in addition to qemu. 598 | 599 | Fix typos found by Antti Kantee and Jean-Yves Migeon. 600 | 601 | Deprecate the --qemu-args -no-kvm-irqchip workaround, as it doesn't 602 | quite work. 603 | 604 | Add support for MacOS X hosts, from Jeff Rizzo. 605 | 606 | New command line option "--test-timeout". 607 | 608 | Deal with yet another seemingly nondeterministic "Press enter to 609 | continue" prompt. 610 | 611 | - 1.18 released - 612 | 613 | Deal with sysinst no longer asking for a password cipher. 614 | 615 | Create the work directory in "test" mode so that it works without 616 | running "install" first. 617 | 618 | Fix incorrect description of the --workdir option in the man page, and 619 | consistently use the term "work directory" throughout. 620 | 621 | Reorganized the man page, adding a MODES section. 622 | 623 | New mode "print-workdir". 624 | 625 | Fix installation of historical versions of -current around 626 | CVS date 2009.08.23.20.57.40. 627 | 628 | - 1.17 released - 629 | 630 | Fix bugs in the new features introduced in 1.16: the --sets option 631 | only worked when passing the URL of a distribution directory (rather 632 | than a pathname or the url of an ISO), and exporting of ATF output 633 | only worked when using --workdir with an abolute pathname. 634 | 635 | - 1.16 released - 636 | 637 | Update the discussion of Linux KVM virtualization compatibility in the 638 | BUGS section, noting the --qemu-args -no-kvm-irqchip workaround and 639 | including a reference to PR 44069. 640 | 641 | New command line option "--sets". 642 | 643 | Add a rudimentary INSTALL file. 644 | 645 | The "misc" set is now installed by default, as it is required for 646 | the ATF XML DTD, XSL files, and style sheets. 647 | 648 | The "test" command now exports the raw and XML format ATF output 649 | to the host, along with some auxiliary files needed for further 650 | processing of the XML output. 651 | 652 | Add support for FreeBSD hosts, from Kurt Lidl. 653 | 654 | - 1.15 released - 655 | 656 | Increase the timeout for "--run" commands to one hour. 657 | 658 | Restrict snapshotting to the system disk, so that additional disks 659 | mounted for the purpose of exporting data can be persistently 660 | modified. 661 | 662 | - 1.14 released - 663 | 664 | Increase the maximum number of boot floppies, as four is no longer 665 | enough for amd64. 666 | 667 | New command line option "--run". 668 | 669 | Handle the additional dialogs displayed by sysinst when an 670 | i386 or amd64 system has more than one disk. 671 | 672 | Add the words "OR CONTRIBUTORS" to the disclaimer of liabilities 673 | in the copyright notice. 674 | 675 | - 1.13 released - 676 | 677 | When installing NetBSD/sparc, use a terminal type of "xterm" rather 678 | than "sun", as anita is more likely run from an xterm or other 679 | ANSI-like terminal than from a sun console. 680 | 681 | In the BUGS section of the man page, mention the specific NetBSD ports 682 | affected. 683 | 684 | Fix typos in the man page. 685 | 686 | - 1.12 released - 687 | 688 | Add support for the amd64 and sparc ports as targets. 689 | 690 | Improve error reporting for common types of unsuitable URLs. 691 | 692 | Avoid sending multiple responses when the NetBSD 3.0.1 sysinst 693 | updates the menu display multiple times. 694 | 695 | New command line option "--disk-size". 696 | 697 | - 1.11 released - 698 | 699 | Remove '-serial stdio' from qemu argument list. It's not actually 700 | needed when the '-nographic' option is present, and it confuses 701 | qemu 0.12. 702 | 703 | - 1.10 released - 704 | 705 | Deal with recent sysinst changes: sysinst no longer displays a dialog 706 | to select the CD-ROM device path, etc. 707 | 708 | New command line option "--qemu-args". 709 | 710 | Anita no longer passes the "-no-acpi" option to qemu by default, 711 | because the ACPI case now works with -current, and disabling 712 | ACPI triggers other bugs. See PR port-i386/42681 for more details. 713 | 714 | - 1.9 released - 715 | 716 | Deal with recent renumbering of password encryption choices in sysinst. 717 | 718 | - 1.8 released - 719 | 720 | Increase timeouts, particularly when extracting sets and running the 721 | ATF tests 722 | 723 | Document the "test" command and path-in-place-of-URL 724 | 725 | - 1.7 released - 726 | 727 | Remove workaround for lib/39175, which broke the installation 728 | of the "comp" set 729 | 730 | Add "test" command for running the ATF tests 731 | 732 | Accept a local distribution directory path in place of a URL 733 | 734 | Check for missing slash at end of distribution directory URL 735 | 736 | Detect HTTP 404 errors 737 | 738 | - 1.6 released - 739 | -------------------------------------------------------------------------------- /anita.py: -------------------------------------------------------------------------------- 1 | # 2 | # This is the library part of Anita, the Automated NetBSD Installation 3 | # and Test Application. 4 | # 5 | 6 | from __future__ import print_function 7 | from __future__ import division 8 | 9 | import gzip 10 | import os 11 | import pexpect 12 | import re 13 | import string 14 | import shutil 15 | import subprocess 16 | import sys 17 | import time 18 | 19 | # Deal with gratuitous urllib changes in Python 3 20 | 21 | if sys.version_info >= (3, 13, 0): 22 | import urllib.request 23 | import urllib.parse 24 | 25 | if sys.version_info[0] >= 3: 26 | import urllib.request as good_old_urllib 27 | import urllib.parse as good_old_urlparse 28 | else: 29 | import urllib as good_old_urllib 30 | import urlparse as good_old_urlparse 31 | 32 | # Find a function for quoting shell commands 33 | try: 34 | from shlex import quote as sh_quote 35 | except ImportError: 36 | from pipes import quote as sh_quote 37 | 38 | # Disable buffering of all printed messages (Python 3 only) 39 | 40 | if sys.version_info[0] >= 3: 41 | import functools 42 | print = functools.partial(print, flush = True) 43 | 44 | __version__='2.17' 45 | 46 | # Your preferred NetBSD FTP mirror site, and the archive site used for 47 | # obtaining older releases. 48 | # 49 | # These are used only by the obsolete code for getting releases 50 | # by number, not by the recommended method of getting them by URL. 51 | 52 | netbsd_mirror_url = "https://ftp.netbsd.org/pub/NetBSD/" 53 | netbsd_archive_url = "https://archive.netbsd.org/pub/NetBSD-archive/" 54 | 55 | # The supported architectures, and their properties. 56 | 57 | # If an 'image_name' property is present, installation is done 58 | # using a pre-built image of that name and a kernel from the 59 | # 'kernel_name' list, rather than using sysinst. If multiple 60 | # kernel names are listed, the first one present in the release 61 | # is used. 62 | 63 | arch_props = { 64 | 'i386': { 65 | 'qemu': { 66 | 'executable': 'qemu-system-i386', 67 | }, 68 | 'scratch_disk': 'wd1d', 69 | 'boot_from_default': 'floppy', 70 | 'memory_size': '64M', 71 | }, 72 | 'amd64': { 73 | 'qemu': { 74 | 'executable': 'qemu-system-x86_64', 75 | }, 76 | 'scratch_disk': 'wd1d', 77 | 'memory_size': '192M', 78 | }, 79 | 'sparc': { 80 | 'qemu': { 81 | 'executable': 'qemu-system-sparc', 82 | }, 83 | 'scratch_disk': 'sd1c', 84 | 'memory_size': '64M', 85 | }, 86 | 'sparc64': { 87 | 'qemu': { 88 | 'executable': 'qemu-system-sparc64', 89 | }, 90 | 'scratch_disk': 'wd1c', 91 | 'memory_size': '128M', 92 | }, 93 | 'evbarm-earmv7hf': { 94 | 'qemu': { 95 | 'executable': 'qemu-system-arm', 96 | 'machine_default': 'vexpress-a15', 97 | }, 98 | 'image_name': 'armv7.img.gz', 99 | 'kernel_name': ['netbsd-VEXPRESS_A15.ub.gz', 'netbsd-GENERIC.ub.gz'], 100 | 'scratch_disk': None, 101 | 'memory_size': '128M', 102 | 'disk_size': '2G', 103 | }, 104 | 'evbarm-aarch64': { 105 | 'qemu': { 106 | 'executable': 'qemu-system-aarch64', 107 | 'machine_default': 'virt', 108 | }, 109 | 'image_name': 'arm64.img.gz', 110 | 'kernel_name': ['netbsd-GENERIC64.img.gz'], 111 | 'scratch_disk': 'ld5c', 112 | 'memory_size': '512M', 113 | 'disk_size': '2G', 114 | }, 115 | 'pmax': { 116 | 'gxemul': { 117 | }, 118 | 'scratch_disk': 'sd1c', 119 | 'memory_size': '128M', 120 | 'inst_kernel': 'binary/kernel/netbsd-INSTALL.gz', 121 | }, 122 | 'hpcmips': { 123 | 'gxemul': { 124 | }, 125 | 'inst_kernel': 'installation/netbsd.gz', 126 | 'scratch_disk': None, 127 | }, 128 | 'landisk': { 129 | 'gxemul': { 130 | }, 131 | 'scratch_disk': 'wd1d' 132 | }, 133 | 'vax': { 134 | 'simh': { 135 | }, 136 | 'scratch_disk': 'ra1c', 137 | }, 138 | 'hppa': { 139 | 'qemu': { 140 | 'executable': 'qemu-system-hppa', 141 | }, 142 | 'scratch_disk': 'sd1c', 143 | }, 144 | 'macppc': { 145 | 'qemu': { 146 | 'executable': 'qemu-system-ppc', 147 | 'machine_default': 'mac99', 148 | }, 149 | 'memory_size': '256M', 150 | 'inst_kernel': 'binary/kernel/netbsd-INSTALL.gz', 151 | 'scratch_disk': 'wd1c', 152 | }, 153 | 'alpha': { 154 | 'qemu': { 155 | 'executable': 'qemu-system-alpha', 156 | }, 157 | 'boot_from_default': 'kernel', 158 | # Consistency would be nice 159 | 'inst_kernel': 'installation/instkernel/netbsd.gz', 160 | 'scratch_disk': 'wd1c', 161 | }, 162 | 'riscv-riscv64': { 163 | 'qemu': { 164 | 'executable': 'qemu-system-riscv64', 165 | 'machine_default': 'virt', 166 | }, 167 | 'image_name': 'riscv64.img.gz', 168 | 'kernel_name': ['netbsd-GENERIC64.gz'], 169 | 'memory_size': '256M', 170 | 'boot_from_default': 'kernel', 171 | 'scratch_disk': 'ld5c', 172 | }, 173 | # Experimental, for noemu 174 | 'evbmips-mips64eb': { 175 | 'noemu': { 176 | }, 177 | 'inst_kernel': 'installation/netbsd-INSTALL_OCTEON.gz', 178 | }, 179 | } 180 | 181 | # Filename extensions used for the installation sets in different 182 | # versions of NetBSD 183 | set_exts = ['.tgz', '.tar.xz'] 184 | 185 | # External command to build ISO images. This must be mkisofs to 186 | # build the macppc ISO images. 187 | 188 | # Several different kinds of ISO images are used for different purposes: 189 | # 190 | # install boot ISO 191 | # for booting the install kernel, e.g., boot-com.iso from the i386 192 | # distribution 193 | # 194 | # install sets ISO 195 | # for holding the installation sets, e.g., the install_tmp.iso built 196 | # by anita for i386 installation 197 | # 198 | # install combined ISO 199 | # a single ISO serving both the above roles, e.g., the sparc install ISO 200 | # 201 | # runtime boot ISO 202 | # for booting installed maccppc targets only 203 | 204 | # A shared file descriptor for /dev/null 205 | fnull = open(os.devnull, 'w') 206 | 207 | # Return true if the given program (+args) can be successfully run 208 | 209 | def try_program(argv): 210 | try: 211 | result = subprocess.call(argv, stdout = fnull, stderr = fnull) 212 | return result == 0 213 | except OSError: 214 | return False 215 | 216 | # Create a directory if missing 217 | 218 | def mkdir_p(dir): 219 | if not os.path.isdir(dir): 220 | os.makedirs(dir) 221 | 222 | # Remove a file, ignoring errors 223 | def rm_f(fn): 224 | try: 225 | os.unlink(fn) 226 | except: 227 | pass 228 | 229 | # Create a hard link, removing the destination first 230 | def ln_f(src, dst): 231 | rm_f(dst) 232 | os.link(src, dst) 233 | 234 | # Uncompress a file 235 | def gunzip(src, dst): 236 | with gzip.open(src, 'rb') as srcf: 237 | with open(dst, 'wb') as dstf: 238 | shutil.copyfileobj(srcf, dstf) 239 | 240 | # Quote a shell command. This is intended to make it possible to 241 | # manually cut and paste logged command into a shell. 242 | 243 | def quote_shell_command(v): 244 | s = '' 245 | for i in range(len(v)): 246 | if i > 0: 247 | # Try to keep options and their arguments on the same line 248 | if v[i - 1].startswith('-') and not v[i].startswith('-'): 249 | s += ' ' 250 | else: 251 | s += ' \\\n ' 252 | s += sh_quote(v[i]) 253 | return s 254 | 255 | # Run a shell command safely and with error checking 256 | 257 | def spawn(command, args): 258 | print(quote_shell_command(args)) 259 | sys.stdout.flush() 260 | ret = os.spawnvp(os.P_WAIT, command, args) 261 | if ret != 0: 262 | raise RuntimeError("could not run " + command) 263 | 264 | # Subclass pexpect.spawn to add logging of expect() calls 265 | 266 | class pexpect_spawn_log(pexpect.spawn): 267 | def __init__(self, logf, *args, **kwargs): 268 | self.structured_log_f = logf 269 | return super(pexpect_spawn_log, self).__init__(*args, **kwargs) 270 | def expect(self, pattern, *args, **kwargs): 271 | slog(self.structured_log_f, "expect", pattern, timestamp = False); 272 | r = pexpect.spawn.expect(self, pattern, *args, **kwargs) 273 | slog(self.structured_log_f, "match", self.match.group(0), timestamp = False); 274 | return r 275 | 276 | if sys.version_info < (3, 13, 0): 277 | # Subclass urllib.FancyURLopener so that we can catch 278 | # HTTP 404 errors 279 | class MyURLopener(good_old_urllib.FancyURLopener): 280 | def http_error_default(self, url, fp, errcode, errmsg, headers): 281 | raise IOError('HTTP error code %d' % errcode) 282 | 283 | def my_urlretrieve(url, filename): 284 | if sys.version_info >= (3, 13, 0): 285 | r = urllib.request.urlretrieve(url, filename) 286 | else: 287 | r = MyURLopener().retrieve(url, filename) 288 | if sys.version_info >= (2, 7, 12): 289 | # Work around https://bugs.python.org/issue27973 290 | good_old_urllib.urlcleanup() 291 | return r 292 | 293 | # Download a file, cleaning up the partial file if the transfer 294 | # fails or is aborted before completion. 295 | 296 | def download_file(url, file): 297 | try: 298 | my_urlretrieve(url, file) 299 | except IOError as e: 300 | if os.path.exists(file): 301 | os.unlink(file) 302 | raise 303 | 304 | # Create a file of the given size, containing NULs, without holes. 305 | 306 | def make_dense_image(fn, size): 307 | f = open(fn, "wb") 308 | blocksize = 64 * 1024 309 | while size > 0: 310 | chunk = min(size, blocksize) 311 | f.write(b"\000" * chunk) 312 | size = size - chunk 313 | f.close() 314 | 315 | # As above but with holes 316 | 317 | def make_sparse_image(fn, size): 318 | f = open(fn, "wb") 319 | f.seek(size - 1) 320 | f.write(b"\000") 321 | f.close() 322 | 323 | def make_image(fn, size, format): 324 | if format == 'dense': 325 | f = make_dense_image 326 | elif format == 'sparse': 327 | f = make_sparse_image 328 | else: 329 | raise RuntimeError("unknown image format %s" % format) 330 | f(fn, size) 331 | 332 | # Parse a size with optional k/M/G/T suffix and return an integer 333 | 334 | def parse_size(size): 335 | m = re.match(r'(\d+)([kMGT])?$', size) 336 | if not m: 337 | raise RuntimeError("%s: invalid size" % size) 338 | size, suffix = m.groups() 339 | mult = dict(k=1024, M=1024**2, G=1024**3, T=1024**4).get(suffix, 1) 340 | return int(size) * mult 341 | 342 | # Download "url" to the local file "file". If the file already 343 | # exists locally, do nothing. If "optional" is true, ignore download 344 | # failures and cache the absence of a missing file by creating a marker 345 | # file with the extension ".MISSING". 346 | # 347 | # Returns true iff the file is present. 348 | 349 | def download_if_missing_2(url, file, optional = False): 350 | if os.path.exists(file): 351 | return True 352 | if os.path.exists(file + ".MISSING"): 353 | return False 354 | dir = os.path.dirname(file) 355 | mkdir_p(dir) 356 | try: 357 | print("Downloading", url + "...", end=' ') 358 | sys.stdout.flush() 359 | download_file(url, file) 360 | print("OK") 361 | sys.stdout.flush() 362 | return True 363 | except IOError as e: 364 | if optional: 365 | if is_real_error(url, e): 366 | raise 367 | print("missing but optional, so that's OK") 368 | sys.stdout.flush() 369 | f = open(file + ".MISSING", "w") 370 | print(e, file = f) 371 | f.close() 372 | return False 373 | else: 374 | print(e) 375 | raise 376 | 377 | def download_if_missing_3(urlbase, dirbase, relpath, optional = False): 378 | url = urlbase + "/".join(relpath) 379 | file = os.path.join(*([dirbase] + relpath)) 380 | return download_if_missing_2(url, file, optional) 381 | 382 | if sys.version_info >= (3, 13, 0): 383 | # Return true if we can confidenlty determine that an attempt to 384 | # download "url" that raised the exception "e" is due to a real 385 | # error, not just the file legitimately not existing. When in 386 | # doubt, for example in the case of ftp URLs, return false. 387 | def is_real_error(url, e): 388 | parts = urllib.parse.urlparse(url) 389 | scheme = parts[0].lower() 390 | return \ 391 | ((scheme == 'http' or scheme == 'https') and \ 392 | isinstance(e, urllib.error.HTTPError) and e.code != 404) or \ 393 | scheme == 'file' and \ 394 | not (isinstance(e, urllib.error.URLError) and 395 | isinstance(e.reason, FileNotFoundError)) 396 | else: 397 | # Supporting this for all old versions is too painful 398 | def is_real_error(url, e): 399 | return False 400 | 401 | # Map a URL to a directory name. No two URLs should map to the same 402 | # directory. 403 | 404 | def url2dir(url): 405 | tail = [] 406 | def munge(match): 407 | index = "/:+-".find(match.group()) 408 | if index != 0: 409 | tail.append(chr(0x60 + index) + str(match.start())) 410 | return "-" 411 | return "work-" + re.sub("[/:+-]", munge, url) + "+" + "".join(tail) 412 | 413 | # Inverse of the above; not used, but included just to show that the 414 | # mapping is invertible and therefore collision-free 415 | 416 | class InvalidDir(Exception): 417 | pass 418 | 419 | def dir2url(dir): 420 | match = re.match(r"(work-)(.*)\+(.*)", dir) 421 | work, s, tail = match.groups() 422 | if work != 'work-': 423 | raise InvalidDir() 424 | s = re.sub("-", "/", s) 425 | chars = list(s) 426 | while True: 427 | m = re.match(r"([a-z])([0-9]+)", tail) 428 | if not m: 429 | break 430 | c, i = m.groups() 431 | chars[int(i)] = "/:+-"[ord(c) - 0x60] 432 | tail = tail[m.end():] 433 | return "".join(chars) 434 | 435 | def check_arch_supported(arch, dist_type): 436 | if not arch in arch_props: 437 | raise RuntimeError(("'%s' is not the name of a " + \ 438 | "supported NetBSD port") % arch) 439 | if arch in ['i386', 'amd64'] and dist_type != 'reltree': 440 | raise RuntimeError(("NetBSD/%s must be installed from " + 441 | "a release tree, not an ISO") % arch) 442 | if (arch in ['sparc', 'sparc64', 'vax']) and dist_type != 'iso': 443 | raise RuntimeError(("NetBSD/%s must be installed from " + 444 | "an ISO, not a release tree") % arch) 445 | 446 | # Expect any of a set of alternatives. The *args are alternating 447 | # patterns and actions; an action can be a string to be sent 448 | # or a function to be called with no arguments. The alternatives 449 | # will be expected repeatedly until the last one in the list has 450 | # been selected. 451 | 452 | def expect_any(child, *args): 453 | # http://stackoverflow.com/questions/11702414/split-a-list-into-half-by-even-and-odd-elements 454 | patterns = args[0:][::2] 455 | actions = args[1:][::2] 456 | while True: 457 | r = child.expect(list(patterns)) 458 | action = actions[r] 459 | if isinstance(action, str): 460 | child.send(action) 461 | else: 462 | action() 463 | if r == len(actions) - 1: 464 | break 465 | 466 | # Receive and discard (but log) input from the child or a time 467 | # period of "seconds". This is effectively a delay like 468 | # time.sleep(seconds), but generates more useful log output. 469 | 470 | def gather_input(child, seconds): 471 | try: 472 | # This regexp will never match 473 | child.expect("(?!)", seconds) 474 | except pexpect.TIMEOUT: 475 | pass 476 | 477 | # Reverse the order of sublists of v of length sublist_len 478 | # for which the predicate pred is true. 479 | 480 | def reverse_sublists(v, sublist_len, pred): 481 | # Build a list of indices in v in where a sublist satisfying 482 | # pred begins 483 | indices = [] 484 | for i in range(len(v) - (sublist_len - 1)): 485 | if pred(v[i:i+sublist_len]): 486 | indices.append(i) 487 | # Swap list element pairs, working outside in 488 | for i in range(len(indices) >> 1): 489 | a = indices[i] 490 | b = indices[-i - 1] 491 | def swap(a, b): 492 | v[a], v[b] = v[b], v[a] 493 | for j in range(sublist_len): 494 | swap(a + j, b + j) 495 | 496 | # Reverse the order of any "-drive ... -device virtio-blk-device,..." 497 | # option pairs in v 498 | 499 | def reverse_virtio_drives(v): 500 | def is_virtio_blk(sublist): 501 | return sublist[0] == '-drive' and sublist[2] == '-device' \ 502 | and sublist[3].startswith('virtio-blk-device') 503 | reverse_sublists(v, 4, is_virtio_blk) 504 | 505 | 506 | # Format at set of key-value pairs as used in qemu command line options. 507 | # Takes a sequence of tuples. 508 | 509 | def qemu_format_attrs(attrs): 510 | return ','.join(["%s=%s" % pair for pair in attrs]) 511 | 512 | ############################################################################# 513 | 514 | # A NetBSD version. 515 | # 516 | # Subclasses should define: 517 | # 518 | # dist_url(self) 519 | # the top-level URL for the machine-dependent download tree where 520 | # the version can be downloaded, for example, 521 | # ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-5.0.2/i386/ 522 | # 523 | # mi_url(self) 524 | # The top-level URL for the machine-independent download tree, 525 | # for example, ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-5.0.2/ 526 | # 527 | # default_workdir(self) 528 | # a file name component identifying the version, for use in 529 | # constructing a unique, version-specific working directory 530 | # 531 | # arch(self) 532 | # the name of the machine architecture the version is for, 533 | # e.g., i386 534 | 535 | def make_item(t): 536 | d = dict(list(zip(['filename', 'label', 'install'], t[0:3]))) 537 | if isinstance(t[3], list): 538 | d['group'] = make_set_dict_list(t[3]) 539 | else: 540 | d['optional'] = t[3] 541 | d['label'] = d['label'].encode('ASCII') 542 | return d 543 | 544 | def make_set_dict_list(list_): 545 | return [make_item(t) for t in list_] 546 | 547 | def flatten_set_dict_list(list_): 548 | def item2list(item): 549 | group = item.get('group') 550 | if group: 551 | return group 552 | else: 553 | return [item] 554 | return sum([item2list(item) for item in list_], []) 555 | 556 | class Version(object): 557 | # Information about the available installation file sets. As the 558 | # set of sets (sic) has evolved over time, this actually represents 559 | # the union of those sets of sets, in other words, this list should 560 | # contain all currently and historically known sets. 561 | # 562 | # This list is used for to determine 563 | # - Which sets we should attempt to download 564 | # - Which sets we should install by default 565 | # 566 | # Each array element is a tuple of four fields: 567 | # - the file name 568 | # - a regular expression matching the label used by sysinst 569 | # (taking into account that it may differ between sysinst versions) 570 | # - a flag indicating that the set should be installed by default 571 | # - a flag indicating that the set is not present in all versions 572 | # 573 | 574 | sets = make_set_dict_list([ 575 | # In all version but not in all ports (missing from evmips-mips64eb) 576 | [ 'kern-GENERIC', r'Kernel (GENERIC)', 1, 1 ], 577 | [ 'kern-GENERIC.NOACPI', r'Kernel \(GENERIC\.NOACPI\)', 0, 1 ], 578 | [ 'modules', r'Kernel [Mm]odules', 1, 1 ], 579 | # Must match the end of the label here so we don't accidentally 580 | # match "Base 32-bit compatibility libraries". 581 | [ 'base', r'Base$', 1, 0 ], 582 | [ 'base32', r'Base 32-bit compatibility libraries', 1, 1 ], 583 | [ 'etc', r'(System)|(System configuration files)|(Configuration files) \(/etc\)', 1, 0 ], 584 | [ 'comp', r'Compiler [Tt]ools', 1, 0 ], 585 | [ 'games', r'Games', 0, 0 ], 586 | [ 'gpufw', r'Graphics driver firmware', 1, 1 ], 587 | # Must match the end of the label here so we don't accidentally 588 | # match "Manual pages (HTML)". 589 | [ 'man', r'(Online )?Manual [Pp]ages$', 0, 0 ], 590 | [ 'manhtml', r'Manual pages \(HTML\)}', 0, 1 ], 591 | [ 'misc', r'Miscellaneous', 1, 0 ], 592 | [ 'rescue', r'Recovery [Tt]ools', 1, 1 ], 593 | [ 'tests', r'Test programs', 1, 1 ], 594 | [ 'text', r'Text [Pp]rocessing [Tt]ools', 0, 0 ], 595 | [ '_x11', r'X11 sets', 0, [ 596 | ['xbase', r'X11 base and clients', 0, 1 ], 597 | ['xcomp', r'X11 programming', 0, 1 ], 598 | ['xetc', r'X11 configuration', 0, 1 ], 599 | ['xfont', r'X11 fonts', 0, 1 ], 600 | ['xserver', r'X11 servers', 0, 1 ], 601 | ]], 602 | [ '_src', r'Source (and debug )?sets', 0, [ 603 | ['syssrc', r'Kernel sources', 0, 1], 604 | ['src', r'Base sources', 0, 1], 605 | # The optionsal "es"? is because the source sets are 606 | # displayed in a pop-up box atop the main distribution 607 | # set list, and as of source date 2019.09.12.06.19.47, 608 | # the "es" in "Share sources" happens to land exactly 609 | # on top of an existing "es" from the word "Yes" in 610 | # the underlying window. 611 | # Curses, eager to to optimize, will reuse that 612 | # existing "es" instead of outputting it anew, causing 613 | # the pattern not to match if it includes the "es". 614 | ['sharesrc', r'Share sourc(es)?', 0, 1], 615 | ['gnusrc', r'GNU sources', 0, 1], 616 | ['xsrc', r'X11 sources', 0, 1], 617 | # The final "s" in "Debug symbols" can also fall victim 618 | # to curses optimization. 619 | ['debug', r'(debug sets)|(Debug symbols?)$', 0, 1], 620 | ['debug32', r'Debug symbols \(32-bit\)', 0, 1 ], 621 | ['xdebug', r'(debug X11 sets)|(X11 debug symbols)', 0, 1], 622 | ]] 623 | ]) 624 | 625 | flat_sets = flatten_set_dict_list(sets) 626 | 627 | def __init__(self, sets = None): 628 | self.tempfiles = [] 629 | if sets is not None: 630 | if not any([re.match(r'kern-', s) for s in sets]): 631 | raise RuntimeError("no kernel set specified") 632 | # Create a Python set containing the names of the NetBSD sets we 633 | # want for O(1) lookup. Yes, the multiple meansings of the word 634 | # "set" here are confusing. 635 | sets_wanted = set(sets) 636 | for required in ['base', 'etc']: 637 | if not required in sets_wanted: 638 | raise RuntimeError("the '%s' set is required", required) 639 | for s in self.flat_sets: 640 | s['install'] = (s['filename'] in sets_wanted) 641 | sets_wanted.discard(s['filename']) 642 | if len(sets_wanted): 643 | raise RuntimeError("no such set: " + sets_wanted.pop()) 644 | 645 | def set_workdir(self, dir): 646 | self.workdir = dir 647 | # The directory where we mirror files needed for installation 648 | def download_local_mi_dir(self): 649 | return self.workdir + "/download/" 650 | def download_local_arch_dir(self): 651 | return self.download_local_mi_dir() + self.arch() + "/" 652 | # The path to the install sets ISO image, which 653 | # may or may not also be the install boot ISO 654 | def install_sets_iso_path(self): 655 | return os.path.join(self.workdir, self.install_sets_iso_name()) 656 | # The path to the ISO used for booting an installed 657 | # macppc system (not to be confused with the installation 658 | # boot ISO) 659 | def runtime_boot_iso_path(self): 660 | return os.path.join(self.workdir, 'boot.iso') 661 | # The directory for the install floppy images 662 | def floppy_dir(self): 663 | return os.path.join(self.download_local_arch_dir(), 664 | "installation/floppy") 665 | def boot_iso_dir(self): 666 | return os.path.join(self.download_local_arch_dir(), 667 | "installation/cdrom") 668 | def boot_from_default(self): 669 | return arch_props[self.arch()].get('boot_from_default') 670 | 671 | def xen_boot_kernel(self, type): 672 | if type == 'pvh': 673 | return 'netbsd-GENERIC.gz' 674 | arch = self.arch() 675 | if arch == 'i386': 676 | return 'netbsd-XEN3PAE_DOMU.gz' 677 | elif arch == 'amd64': 678 | return 'netbsd-XEN3_DOMU.gz' 679 | else: 680 | return None 681 | 682 | def xen_install_kernel(self, type): 683 | if type == 'pvh': 684 | return 'netbsd-INSTALL.gz' 685 | arch = self.arch() 686 | if arch == 'i386': 687 | return 'netbsd-INSTALL_XEN3PAE_DOMU.gz' 688 | elif arch == 'amd64': 689 | return 'netbsd-INSTALL_XEN3_DOMU.gz' 690 | else: 691 | return None 692 | 693 | def xen_kernel(self, type, install): 694 | if install: 695 | return self.xen_install_kernel(type) 696 | else: 697 | return self.xen_boot_kernel(type) 698 | 699 | # The list of boot floppies we should try downloading; 700 | # not all may actually exist. amd64 currently has five, 701 | # i386 has three, and older versions may have fewer. 702 | # Add a couple extra to accomodate future growth. 703 | def potential_floppies(self): 704 | return ['boot-com1.fs'] + ['boot%i.fs' % i for i in range(2, 8)] 705 | 706 | # The list of boot floppies we actually have 707 | def floppies(self): 708 | return [f for f in self.potential_floppies() \ 709 | if os.path.exists(os.path.join(self.floppy_dir(), f))] 710 | 711 | # The list of boot ISOs we should try downloading 712 | def boot_isos(self): 713 | return ['boot-com.iso'] 714 | 715 | def cleanup(self): 716 | for fn in self.tempfiles: 717 | try: 718 | os.unlink(fn) 719 | except: 720 | pass 721 | 722 | def set_path(self, setname, ext): 723 | if re.match(r'.*src$', setname): 724 | return ['source', 'sets', setname + ext] 725 | else: 726 | return [self.arch(), 'binary', 'sets', setname + ext] 727 | 728 | # Download this release 729 | # The ISO class overrides this to download the ISO only 730 | def download(self): 731 | # Optimization of file:// URLs is disabled for now; it doesn't 732 | # work for the source sets. 733 | #if hasattr(self, 'url') and self.url[:7] == 'file://': 734 | # mkdir_p(os.path.join(self.workdir, 'download')) 735 | # if not os.path.lexists(os.path.join(self.workdir, 'download', self.arch())): 736 | # os.symlink(self.url[7:], os.path.join(self.workdir, 'download', self.arch())) 737 | # return 738 | 739 | # Deal with architectures that we don't know how to install 740 | # using sysinst, but instead use a pre-installed image 741 | if 'image_name' in arch_props[self.arch()]: 742 | download_if_missing_3(self.dist_url(), self.download_local_arch_dir(), ["binary", "gzimg", arch_props[self.arch()]['image_name']]) 743 | for file in arch_props[self.arch()]['kernel_name']: 744 | if download_if_missing_3(self.dist_url(), self.download_local_arch_dir(), ["binary", "kernel", file], True): 745 | break 746 | # Nothing more to do as we aren't doing a full installation 747 | return 748 | 749 | if self.arch() in ['hpcmips', 'landisk', 'macppc', 'alpha']: 750 | download_if_missing_3(self.dist_url(), self.download_local_arch_dir(), ["binary", "kernel", "netbsd-GENERIC.gz"]) 751 | 752 | # Download installation kernel if needed 753 | inst_kernel_prop = arch_props[self.arch()].get('inst_kernel') 754 | if inst_kernel_prop is not None: 755 | download_if_missing_3(self.dist_url(), self.download_local_arch_dir(), 756 | inst_kernel_prop.split(os.path.sep)) 757 | 758 | i = 0 759 | # Depending on the NetBSD version, there may be two or more 760 | # boot floppies. Treat any floppies past the first two as 761 | # optional files. 762 | for floppy in self.potential_floppies(): 763 | download_if_missing_3(self.dist_url(), 764 | self.download_local_arch_dir(), 765 | ["installation", "floppy", floppy], 766 | True) 767 | i = i + 1 768 | 769 | for bootcd in (self.boot_isos()): 770 | download_if_missing_3(self.dist_url(), 771 | self.download_local_arch_dir(), 772 | ["installation", "cdrom", bootcd], 773 | True) 774 | 775 | # For netbooting/noemu 776 | if self.arch() in ['i386', 'amd64']: 777 | # Must be optional so that we can still install NetBSD 4.0 778 | # where it doesn't exist yet. 779 | download_if_missing_3(self.dist_url(), 780 | self.download_local_arch_dir(), 781 | ["installation", "misc", "pxeboot_ia32.bin"], 782 | True) 783 | download_if_missing_3(self.dist_url(), 784 | self.download_local_arch_dir(), 785 | ["binary", "kernel", "netbsd-INSTALL.gz"], 786 | True) 787 | 788 | for set in self.flat_sets: 789 | if set['install']: 790 | present = [ 791 | download_if_missing_3(self.mi_url(), 792 | self.download_local_mi_dir(), 793 | self.set_path(set['filename'], 794 | ext), 795 | True) 796 | for ext in set_exts 797 | ] 798 | if not set['optional'] and not any(present): 799 | raise RuntimeError('install set %s does not exist with extension %s' % 800 | (set['filename'], ' nor '.join(set_exts))) 801 | 802 | # Create an ISO image 803 | def make_iso(self, image, dir): 804 | mkisofs = ["mkisofs", "-r", "-o"] 805 | 806 | if self.arch() == 'macppc': 807 | # Need to use mkisofs for HFS support 808 | makefs = ["mkisofs", "-r", "-hfs", "-part", "-l", "-J", "-N", "-o"] 809 | else: 810 | # Prefer native tools 811 | if os.uname()[0] == 'NetBSD': 812 | makefs = ["/usr/sbin/makefs", "-t", "cd9660", "-o", "rockridge"] 813 | elif os.uname()[0] == 'FreeBSD': 814 | makefs = mkisofs 815 | elif os.uname()[0] == 'Darwin': 816 | makefs = ["hdiutil", "makehybrid", "-iso", "-joliet", "-o"] 817 | else: 818 | # Linux distributions differ. Ubuntu has genisoimage 819 | # and mkisofs (as an alias of genisoimage); CentOS has 820 | # mkisofs only. Debian 7 has genisoimage only. 821 | if os.path.isfile('/usr/bin/genisoimage'): 822 | makefs = ["genisoimage", "-r", "-o"] 823 | else: 824 | makefs = mkisofs 825 | # hdiutil will fail if the iso already exists, so remove it first. 826 | rm_f(image) 827 | spawn(makefs[0], makefs + [image, dir]) 828 | 829 | # Create the install sets ISO image 830 | def make_install_sets_iso(self): 831 | self.download() 832 | if self.arch() == 'macppc': 833 | gunzip(os.path.join(self.download_local_arch_dir(), 'binary/kernel/netbsd-INSTALL.gz'), 834 | os.path.join(self.download_local_mi_dir(), 'netbsd-INSTALL')) 835 | self.make_iso(self.install_sets_iso_path(), 836 | os.path.dirname(os.path.realpath(os.path.join(self.download_local_mi_dir(), self.arch())))) 837 | self.tempfiles.append(self.install_sets_iso_path()) 838 | 839 | # Create the runtime boot ISO image (macppc only) 840 | def make_runtime_boot_iso(self): 841 | # The ISO will contain only the GENERIC kernel 842 | d = os.path.join(self.workdir, 'runtime_boot_iso') 843 | mkdir_p(d) 844 | gunzip(os.path.join(self.download_local_arch_dir(), 'binary/kernel/netbsd-GENERIC.gz'), 845 | os.path.join(d, 'netbsd-GENERIC')) 846 | self.make_iso(self.runtime_boot_iso_path(), d) 847 | # Do not add the ISO to self.tempfiles as it's needed after the install. 848 | 849 | # Get the architecture name. This is a hardcoded default for use 850 | # by the obsolete subclasses; the "URL" class overrides it. 851 | def arch(self): 852 | return "i386" 853 | 854 | # Backwards compatibility with Anita 1.2 and older 855 | def install(self): 856 | Anita(dist = self).install() 857 | def boot(self): 858 | Anita(dist = self).boot() 859 | def interact(self): 860 | Anita(dist = self).interact() 861 | 862 | # Subclass for versions where we pass in the version number explicitly 863 | # Deprecated, use anita.URL instead 864 | 865 | class NumberedVersion(Version): 866 | def __init__(self, ver, **kwargs): 867 | Version.__init__(self, **kwargs) 868 | self.ver = ver 869 | # The file name of the install ISO (sans directory) 870 | def install_sets_iso_name(self): 871 | if re.match(r"^[3-9]", self.ver) is not None: 872 | return "i386cd-" + self.ver + ".iso" 873 | else: 874 | return "i386cd.iso" 875 | # The directory for files related to this release 876 | def default_workdir(self): 877 | return "netbsd-" + self.ver 878 | 879 | # An official NetBSD release 880 | # Deprecated, use anita.URL instead 881 | 882 | class Release(NumberedVersion): 883 | def __init__(self, ver, **kwargs): 884 | NumberedVersion.__init__(self, ver, **kwargs) 885 | pass 886 | def mi_url(self): 887 | major_ver = int(self.ver.split('.')[0]) 888 | if major_ver >= 9: 889 | url = netbsd_mirror_url 890 | else: 891 | url = netbsd_archive_url 892 | return url + "NetBSD-" + self.ver + "/" 893 | def dist_url(self): 894 | return self.mi_url() + self.arch() + "/" 895 | 896 | # The top-level URL of a release tree 897 | 898 | class URL(Version): 899 | def __init__(self, url, **kwargs): 900 | Version.__init__(self, **kwargs) 901 | self.url = url 902 | match = re.match(r'(^.*/)([^/]+)/$', url) 903 | if match is None: 904 | raise RuntimeError(("URL '%s' doesn't look like the URL of a " + \ 905 | "NetBSD distribution") % url) 906 | self.url_mi_part = match.group(1) 907 | self.m_arch = match.group(2) 908 | check_arch_supported(self.m_arch, 'reltree') 909 | def dist_url(self): 910 | return self.url 911 | def mi_url(self): 912 | return self.url_mi_part 913 | def install_sets_iso_name(self): 914 | return "install_tmp.iso" 915 | def default_workdir(self): 916 | return url2dir(self.url) 917 | def arch(self): 918 | return self.m_arch 919 | 920 | # A local release directory 921 | 922 | class LocalDirectory(URL): 923 | def __init__(self, dir, **kwargs): 924 | # This could be optimized to avoid copying the files 925 | URL.__init__(self, "file://" + dir, **kwargs) 926 | 927 | # An URL or local file name pointing at an ISO image 928 | 929 | class ISO(Version): 930 | def __init__(self, iso_url, **kwargs): 931 | Version.__init__(self, **kwargs) 932 | if re.match(r'/', iso_url): 933 | self.m_iso_url = "file://" + iso_url 934 | self.m_iso_path = iso_url 935 | else: 936 | self.m_iso_url = iso_url 937 | self.m_iso_path = None 938 | # We can't determine the final ISO file name yet because the work 939 | # directory is not known at this point, but we can precalculate the 940 | # basename of it. 941 | self.m_iso_basename = os.path.basename( 942 | good_old_urllib.url2pathname(good_old_urlparse.urlparse(iso_url)[2])) 943 | m = re.match(r"(.*)cd.*iso|NetBSD-[0-9\._A-Z]+-(.*).iso", self.m_iso_basename) 944 | if m is None: 945 | raise RuntimeError("cannot guess architecture from ISO name '%s'" 946 | % self.m_iso_basename) 947 | arch = None 948 | if m.group(1) is not None: 949 | arch = m.group(1) 950 | if m.group(2) is not None: 951 | arch = m.group(2) 952 | if arch is None: 953 | raise RuntimeError("cannot guess architecture from ISO name '%s'" 954 | % self.m_iso_basename) 955 | arch = re.sub(r'-dvd$', '', arch) 956 | check_arch_supported(arch, 'iso') 957 | self.m_arch = arch 958 | def install_sets_iso_path(self): 959 | if self.m_iso_path is not None: 960 | return self.m_iso_path 961 | else: 962 | return os.path.join(self.download_local_arch_dir(), 963 | self.m_iso_basename) 964 | def default_workdir(self): 965 | return url2dir(self.m_iso_url) 966 | def make_install_sets_iso(self): 967 | self.download() 968 | def download(self): 969 | if self.m_iso_path is None: 970 | download_if_missing_2(self.m_iso_url, self.install_sets_iso_path()) 971 | else: 972 | mkdir_p(self.workdir) 973 | def arch(self): 974 | return self.m_arch 975 | def boot_from_default(self): 976 | return 'cdrom-with-sets' 977 | 978 | # Virtual constructior that accepts a release URL, ISO, or local path 979 | # and constructs an URL, ISO, or LocalDirectory object as needed. 980 | 981 | def distribution(distarg, **kwargs): 982 | if re.search(r'\.iso$', distarg): 983 | return ISO(distarg, **kwargs) 984 | elif re.match(r'/', distarg): 985 | if not re.search(r'/$', distarg): 986 | raise RuntimeError("distribution directory should end in a slash") 987 | return LocalDirectory(distarg, **kwargs) 988 | elif re.match(r'[a-z0-9\.0-]+:', distarg): 989 | if not re.search(r'/$', distarg): 990 | raise RuntimeError("distribution URL should end in a slash") 991 | return URL(distarg, **kwargs) 992 | else: 993 | raise RuntimeError("expected distribution URL or directory, got " + distarg) 994 | 995 | ############################################################################# 996 | 997 | def vmm_is_xen(vmm): 998 | return vmm == 'xm' or vmm == 'xl' 999 | 1000 | # Log a message to the structured log file "fd". 1001 | 1002 | def slog(fd, tag, data, timestamp = True): 1003 | if timestamp: 1004 | print("%s(%.3f, %s)" % (tag, time.time(), repr(data)), file=fd) 1005 | else: 1006 | print("%s(%s)" % (tag, repr(data)), file=fd) 1007 | fd.flush() 1008 | 1009 | def slog_info(fd, data): 1010 | slog(fd, 'info', data) 1011 | 1012 | # A file-like object that escapes unprintable data and prefixes each 1013 | # line with a tag, for logging I/O. 1014 | 1015 | class Logger(object): 1016 | def __init__(self, tag, fd): 1017 | self.tag = tag 1018 | self.fd = fd 1019 | def write(self, data): 1020 | slog(self.fd, self.tag, data) 1021 | def __getattr__(self, name): 1022 | return getattr(self.fd, name) 1023 | 1024 | # Logger veneer that hides the data sent, for things like passwords and entropy 1025 | 1026 | class CensorLogger(object): 1027 | def __init__(self, fd): 1028 | self.fd = fd 1029 | def write(self, data): 1030 | self.fd.write(b'*' * len(data)) 1031 | def __getattr__(self, name): 1032 | return getattr(self.fd, name) 1033 | 1034 | # http://stackoverflow.com/questions/616645/how-do-i-duplicate-sys-stdout-to-a-log-file-in-python 1035 | class multifile(object): 1036 | def __init__(self, files): 1037 | self._files = files 1038 | def __getattr__(self, attr, *args): 1039 | return self._wrap(attr, *args) 1040 | def _wrap(self, attr, *args): 1041 | def g(*a, **kw): 1042 | for f in self._files: 1043 | res = getattr(f, attr, *args)(*a, **kw) 1044 | return res 1045 | return g 1046 | 1047 | class BytesWriter(object): 1048 | def __init__(self, fd): 1049 | self.fd = fd 1050 | def write(self, data): 1051 | self.fd.buffer.write(data) 1052 | def __getattr__(self, name): 1053 | return getattr(self.fd, name) 1054 | 1055 | # Convert binary data to a hexadecimal string 1056 | 1057 | if sys.version_info[0] >= 3: 1058 | def bytes2hex(s): 1059 | return s.hex() 1060 | else: 1061 | def bytes2hex(s): 1062 | return s.encode('hex') 1063 | 1064 | class Anita(object): 1065 | def __init__(self, dist, workdir = None, vmm = None, vmm_args = None, 1066 | disk_size = None, memory_size = None, persist = False, boot_from = None, 1067 | structured_log = None, structured_log_file = None, no_install = False, 1068 | tests = 'atf', dtb = '', xen_type = 'pv', image_format = 'dense', 1069 | machine = None, network_config = None, partitioning_scheme = None, 1070 | no_entropy = False): 1071 | self.dist = dist 1072 | if workdir: 1073 | self.workdir = workdir 1074 | else: 1075 | self.workdir = dist.default_workdir() 1076 | 1077 | self.structured_log = structured_log 1078 | self.structured_log_file = structured_log_file 1079 | 1080 | out = sys.stdout 1081 | null = open("/dev/null", "w") 1082 | if sys.version_info[0] >= 3: 1083 | out = BytesWriter(out) 1084 | null = BytesWriter(null) 1085 | 1086 | if self.structured_log_file: 1087 | self.structured_log_f = open(self.structured_log_file, "w") 1088 | self.unstructured_log_f = out 1089 | else: 1090 | if self.structured_log: 1091 | self.structured_log_f = sys.stdout 1092 | self.unstructured_log_f = null 1093 | else: 1094 | self.structured_log_f = open("/dev/null", "w") 1095 | self.unstructured_log_f = out 1096 | 1097 | # Set the default disk size if none was given. 1098 | disk_size = disk_size or \ 1099 | arch_props[self.dist.arch()].get('disk_size') or \ 1100 | '1536M' 1101 | self.disk_size = disk_size 1102 | 1103 | # Set the default memory size if none was given. 1104 | memory_size = memory_size or \ 1105 | arch_props[self.dist.arch()].get('memory_size') or \ 1106 | '32M' 1107 | self.memory_size_bytes = parse_size(memory_size) 1108 | 1109 | self.persist = persist 1110 | self.boot_from = boot_from 1111 | self.no_install = no_install 1112 | 1113 | props = arch_props.get(dist.arch()) 1114 | if not props: 1115 | raise RuntimeError("NetBSD port '%s' is not supported" % 1116 | dist.arch()) 1117 | 1118 | # Get name of qemu executable (if applicable) 1119 | if 'qemu' in props: 1120 | self.qemu = props['qemu']['executable'] 1121 | # Support old versions of qemu where qemu-system-i386 was 1122 | # simply called qemu 1123 | if self.qemu == 'qemu-system-i386' and \ 1124 | not try_program(['qemu-system-i386', '--version']) \ 1125 | and try_program(['qemu', '--version']): \ 1126 | self.qemu = 'qemu' 1127 | else: 1128 | self.qemu = None 1129 | 1130 | # Choose a default vmm if none was explicitly requested 1131 | if not vmm: 1132 | if self.qemu: 1133 | vmm = 'qemu' 1134 | elif 'simh' in props: 1135 | vmm = 'simh' 1136 | elif 'gxemul' in props: 1137 | vmm = 'gxemul' 1138 | else: 1139 | raise RuntimeError("%s has no default VMM" % self.dist.arch()) 1140 | 1141 | # Backwards compatibility 1142 | if vmm == 'xen': 1143 | vmm = 'xm' 1144 | 1145 | self.vmm = vmm 1146 | if vmm_args is None: 1147 | vmm_args = [] 1148 | self.extra_vmm_args = vmm_args 1149 | 1150 | self.dtb = dtb 1151 | self.xen_type = xen_type 1152 | self.image_format = image_format 1153 | self.machine = machine or self.get_arch_vmm_prop('machine_default') 1154 | self.partitioning_scheme = partitioning_scheme 1155 | self.no_entropy = no_entropy 1156 | 1157 | self.is_logged_in = False 1158 | self.halted = False 1159 | self.tests = tests 1160 | 1161 | # Number of CD-ROM devices 1162 | self.n_cdrom = 0 1163 | 1164 | # Read netboot configuration file, if any 1165 | self.net_config = {} 1166 | if network_config: 1167 | f = open(network_config, "r") 1168 | for line in f: 1169 | s = line.rstrip() 1170 | if s == '': 1171 | continue 1172 | if s[0] == '#': 1173 | continue 1174 | l, r = s.split("=") 1175 | self.net_config[l] = r 1176 | f.close() 1177 | 1178 | self.child = None 1179 | self.cleanup_child_func = None 1180 | 1181 | def __enter__(self): 1182 | return self 1183 | 1184 | def __exit__(self, *stuff): 1185 | self.slog("exit") 1186 | self.cleanup_child() 1187 | return False 1188 | 1189 | def cleanup(self): 1190 | self.cleanup_child() 1191 | 1192 | def cleanup_child(self): 1193 | if self.cleanup_child_func: 1194 | self.cleanup_child_func() 1195 | self.cleanup_child_func = None 1196 | self.child = None 1197 | 1198 | # Get the name of the actual uncompressed kernel file, out of 1199 | # potentially multiple alternative kernels. Used with images. 1200 | def actual_kernel(self): 1201 | for kernel_name in self.get_arch_prop('kernel_name'): 1202 | kernel_name_nogz = kernel_name[:-3] 1203 | kernel_fn = os.path.join(self.workdir, kernel_name_nogz) 1204 | if os.path.exists(kernel_fn): 1205 | return kernel_fn 1206 | raise RuntimeError("missing kernel") 1207 | 1208 | def arch_vmm_args(self): 1209 | if self.dist.arch() == 'pmax': 1210 | a = ["-e3max"] 1211 | elif self.dist.arch() == 'landisk': 1212 | a = ["-Elandisk"] 1213 | elif self.dist.arch() == 'hpcmips': 1214 | a = ["-emobilepro880"] 1215 | elif self.dist.arch() == 'macppc': 1216 | a = ["-M", self.machine, "-prom-env", "qemu_boot_hack=y"] 1217 | elif self.dist.arch() == 'evbarm-earmv7hf': 1218 | a = ['-M', self.machine] 1219 | if self.machine == 'virt': 1220 | a += [ 1221 | '-append', 'root=ld4a', 1222 | ] 1223 | else: 1224 | a += [ 1225 | '-append', 'root=ld0a', 1226 | '-dtb', self.dtb 1227 | ] 1228 | elif self.dist.arch() == 'evbarm-aarch64': 1229 | a = [ 1230 | '-M', self.machine, 1231 | '-cpu', 'cortex-a57', 1232 | ] 1233 | elif self.dist.arch() == 'alpha': 1234 | a = [ '-append', 'root=/dev/wd0a' ] 1235 | elif self.dist.arch() == 'riscv-riscv64': 1236 | a = [ 1237 | '-M', self.machine, 1238 | '-append', 'root=dk1', 1239 | ] 1240 | else: 1241 | a = [] 1242 | # When booting an image, we need to pass a kernel 1243 | if self.get_arch_prop('image_name'): 1244 | a += ['-kernel', self.actual_kernel()] 1245 | return a 1246 | 1247 | def slog(self, message): 1248 | slog_info(self.structured_log_f, message) 1249 | 1250 | # Wrapper around pexpect.spawn to let us log the command for 1251 | # debugging. Note that unlike os.spawnvp, args[0] is not 1252 | # the name of the command. 1253 | 1254 | def pexpect_spawn(self, command, args): 1255 | print(quote_shell_command([command] + args)) 1256 | child = pexpect_spawn_log(self.structured_log_f, command, args) 1257 | print("child pid is %d" % child.pid) 1258 | return child 1259 | 1260 | # The path to the NetBSD hard disk image 1261 | def wd0_path(self): 1262 | return os.path.join(self.workdir, "wd0.img") 1263 | 1264 | # Return the memory size rounded up to whole megabytes 1265 | def memory_megs(self): 1266 | megs = (self.memory_size_bytes + 2 ** 20 - 1) // 2 ** 20 1267 | if megs != self.memory_size_bytes // 2 **20: 1268 | print("warning: rounding up memory size of %i bytes to %i megabytes" \ 1269 | % (self.memory_size_bytes, megs), file=sys.stderr) 1270 | return megs 1271 | 1272 | def configure_child(self, child): 1273 | # Log reads from child 1274 | child.logfile_read = multifile([self.unstructured_log_f, Logger('recv', self.structured_log_f)]) 1275 | # Log writes to child 1276 | child.logfile_send = Logger('send', self.structured_log_f) 1277 | child.timeout = 3600 1278 | child.setecho(False) 1279 | # Xen installs sometimes fail if we don't increase this 1280 | # from the default of 0.1 seconds. And powering down noemu 1281 | # using iLO3 over ssh takes more than 5 seconds. 1282 | child.delayafterclose = 30.0 1283 | # Also increase this just in case 1284 | child.delayafterterminate = 30.0 1285 | # pexpect 4.3.1 needs this, too 1286 | ptyproc = getattr(child, 'ptyproc') 1287 | if ptyproc: 1288 | ptyproc.delayafterclose = child.delayafterclose 1289 | ptyproc.delayafterterminate = child.delayafterterminate 1290 | self.halted = False 1291 | self.child = child 1292 | 1293 | def start_simh(self, vmm_args = []): 1294 | f = open(os.path.join(self.workdir, 'netbsd.ini'), 'w') 1295 | f.write('set cpu ' + str(self.memory_megs()) + 'm\n' + 1296 | 'set rq0 ra92\n' + 1297 | 'set rq3 cdrom\n' + 1298 | '\n'.join(vmm_args) + '\n' + 1299 | 'attach rq0 ' + self.wd0_path() + '\n' + 1300 | 'attach -r rq3 ' + self.dist.install_sets_iso_path() + '\n' + 1301 | 'boot cpu') 1302 | f.close() 1303 | child = self.pexpect_spawn('simh-vax', [os.path.join(self.workdir, 'netbsd.ini')]) 1304 | self.configure_child(child) 1305 | return child 1306 | 1307 | def start_gxemul(self, vmm_args): 1308 | child = self.pexpect_spawn('gxemul', ["-M", str(self.memory_megs()) + 'M', 1309 | "-d", os.path.abspath(self.wd0_path())] + self.extra_vmm_args + self.arch_vmm_args() + vmm_args) 1310 | self.configure_child(child) 1311 | return child 1312 | 1313 | # Return true iff the disk image partitioning scheme is GPT 1314 | def image_is_gpt(self): 1315 | f = open(self.wd0_path(), 'rb') 1316 | f.seek(512, 0) 1317 | data = f.read(8) 1318 | f.close() 1319 | return data == b'EFI PART' 1320 | 1321 | def start_qemu(self, vmm_args, snapshot_system_disk): 1322 | # Log the qemu version to stdout 1323 | subprocess.call([self.qemu, '--version']) 1324 | try: 1325 | # Identify the exact qemu version in pkgsrc if applicable, 1326 | # ignoring exceptions that may be raised if qemu was not 1327 | # installed from pkgsrc. 1328 | def f(label, command): 1329 | output = subprocess.check_output(command).rstrip() 1330 | print(label + ":", output.decode('ASCII', 'ignore')) 1331 | sys.stdout.flush() 1332 | return output 1333 | qemu_path = f('qemu path', ['which', self.qemu]) 1334 | f('qemu package', ['pkg_info', '-Fe', qemu_path]) 1335 | f('glib2 package', ['pkg_info', '-e', 'glib2']) 1336 | except: 1337 | pass 1338 | qemu_args = [ 1339 | "-m", str(self.memory_megs()) 1340 | ] + self.qemu_disk_args(self.wd0_path(), 0, True, snapshot_system_disk) + [ 1341 | "-nographic" 1342 | ] + vmm_args + self.extra_vmm_args + self.arch_vmm_args() 1343 | # Deal with virtio device ordering issues 1344 | arch = self.dist.arch() 1345 | if arch == 'evbarm-aarch64' or \ 1346 | arch == 'evbarm-earmv7hf' and self.machine == 'virt': 1347 | print("reversing virtio devices") 1348 | reverse_virtio_drives(qemu_args) 1349 | else: 1350 | #print("not reversing virtio devices") 1351 | pass 1352 | # Deal with evbarm-aarch64 using a different root device with 1353 | # MBR vs GPT 1354 | if arch == 'evbarm-aarch64': 1355 | if self.image_is_gpt(): 1356 | rootdev = 'NAME=netbsd-root' 1357 | else: 1358 | rootdev = 'ld4a' 1359 | qemu_args += [ '-append', 'root=' + rootdev ] 1360 | 1361 | # Start the actual qemu child process 1362 | child = self.pexpect_spawn(self.qemu, qemu_args) 1363 | self.configure_child(child) 1364 | 1365 | return child 1366 | 1367 | def xen_disk_arg(self, path, devno = 0, cdrom = False): 1368 | writable = not cdrom 1369 | if self.vmm == 'xm': 1370 | dev = "0x%x" % devno 1371 | else: # xl 1372 | if self.xen_type == 'hvm': 1373 | devtype = 'hd' 1374 | else: 1375 | devtype = 'xvd' 1376 | dev = devtype + chr(ord('a') + devno) 1377 | s = "disk=file:%s,%s,%s" % (path, dev, "rw"[writable]) 1378 | # Omit the ,cdrom part in the PV case because NetBSD/Xen ignores cdrom 1379 | # devices since xenbus_probe.c 1.51. 1380 | if cdrom and self.xen_type == 'hvm': 1381 | s += ",cdrom" 1382 | return s 1383 | 1384 | def qemu_disk_args(self, path, devno = 0, writable = True, snapshot = False): 1385 | drive_attrs = [ 1386 | ('file', path), 1387 | ('format', 'raw'), 1388 | ('media', 'disk'), 1389 | ('snapshot', ["off", "on"][snapshot]) 1390 | ] 1391 | dev_args = [] 1392 | if self.dist.arch() == 'evbarm-earmv7hf' and self.machine == 'virt' or \ 1393 | self.dist.arch() == 'evbarm-aarch64' or \ 1394 | self.dist.arch() == 'riscv-riscv64': 1395 | # Use virtio 1396 | drive_attrs += [('if', 'none'), ('id', 'hd%d' % devno)] 1397 | dev_args += ['-device', 'virtio-blk-device,drive=hd%d' % devno] 1398 | elif self.dist.arch() == 'evbarm-earmv7hf': 1399 | # Use SD card 1400 | drive_attrs += [('if', 'sd')] 1401 | else: 1402 | pass 1403 | return ["-drive", qemu_format_attrs(drive_attrs)] + dev_args 1404 | 1405 | def qemu_add_cdrom(self, path, extra_attrs = None): 1406 | if extra_attrs is None: 1407 | extra_attrs = [] 1408 | drive_attrs = [ 1409 | ('file', path), 1410 | ('format', 'raw'), 1411 | ('media', 'cdrom'), 1412 | ('readonly', 'on'), 1413 | ] 1414 | if self.dist.arch() in ('macppc', 'sparc64'): 1415 | assert(self.n_cdrom == 0) 1416 | drive_attrs += [('index', '2')] 1417 | drive_attrs += extra_attrs 1418 | argv = ["-drive", qemu_format_attrs(drive_attrs)] 1419 | dev = 'cd%da' % self.n_cdrom 1420 | self.n_cdrom += 1 1421 | return argv, dev 1422 | 1423 | def gxemul_cdrom_args(self): 1424 | return ('', 'd:')[self.dist.arch() == 'landisk'] + self.dist.install_sets_iso_path() 1425 | def gxemul_disk_args(self, path): 1426 | return ["-d", path] 1427 | 1428 | def xen_string_arg(self, name, value): 1429 | if self.vmm == 'xm': 1430 | return '%s=%s' % (name, value) 1431 | else: # xl 1432 | return '%s="%s"' % (name, value) 1433 | 1434 | def xen_args(self, install): 1435 | if self.xen_type == 'pv': 1436 | k = self.dist.xen_kernel('pv', install) 1437 | return [self.xen_string_arg('kernel', 1438 | os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 1439 | "binary", "kernel", k)))] 1440 | if self.xen_type == 'pvshim': 1441 | k = self.dist.xen_kernel('pv', install) 1442 | return [self.xen_string_arg('kernel', 1443 | os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 1444 | "binary", "kernel", k))), 1445 | self.xen_string_arg('type', 'pvh'), 1446 | 'pvshim=1' 1447 | ] 1448 | elif self.xen_type == 'pvh': 1449 | k = self.dist.xen_kernel('pvh', install) 1450 | return [self.xen_string_arg('kernel', 1451 | os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 1452 | "binary", "kernel", k))), 1453 | self.xen_string_arg('type', 'pvh'), 1454 | ] 1455 | elif self.xen_type == 'hvm': 1456 | return [ 1457 | self.xen_string_arg('type', 'hvm'), 1458 | self.xen_string_arg('serial', 'pty'), 1459 | ] 1460 | else: 1461 | raise RuntimeError('unknown xen type %s' % self.xen_type) 1462 | 1463 | def start_xen_domu(self, vmm_args): 1464 | frontend = self.vmm 1465 | name = "anita-%i" % os.getpid() 1466 | args = [ 1467 | frontend, 1468 | "create", 1469 | "-c", 1470 | "/dev/null", 1471 | self.xen_disk_arg(os.path.abspath(self.wd0_path()), 0), 1472 | "memory=" + str(self.memory_megs()), 1473 | self.xen_string_arg('name', name) 1474 | ] + vmm_args + self.extra_vmm_args + self.arch_vmm_args() 1475 | 1476 | # Multiple "disk=" arguments are no longer supported with xl; 1477 | # combine them 1478 | if self.vmm == 'xl': 1479 | disk_args = [] 1480 | no_disk_args = [] 1481 | for arg in args: 1482 | if arg.startswith('disk='): 1483 | disk_args.append(arg[5:]) 1484 | else: 1485 | no_disk_args.append(arg) 1486 | args = no_disk_args + [ "disk=[%s]" % (','.join(["'%s'" % arg for arg in disk_args]))] 1487 | 1488 | child = self.pexpect_spawn(args[0], args[1:]) 1489 | self.configure_child(child) 1490 | 1491 | def cleanup_domu(): 1492 | spawn(self.vmm, [self.vmm, "destroy", name]) 1493 | self.cleanup_child_func = cleanup_domu 1494 | 1495 | return child 1496 | 1497 | def start_noemu(self, vmm_args): 1498 | noemu_always_args = [ 1499 | '--workdir', self.workdir, 1500 | '--releasedir', os.path.join(self.workdir, 'download'), 1501 | '--arch', self.dist.arch() 1502 | ] 1503 | child = self.pexpect_spawn('sudo', ['noemu'] + 1504 | noemu_always_args + vmm_args + self.extra_vmm_args + self.arch_vmm_args()) 1505 | self.configure_child(child) 1506 | return child 1507 | 1508 | def get_arch_prop(self, key): 1509 | return arch_props[self.dist.arch()].get(key) 1510 | 1511 | def get_arch_vmm_prop(self, key): 1512 | vmm_props = arch_props[self.dist.arch()].get(self.vmm) 1513 | if vmm_props is None: 1514 | return None 1515 | return vmm_props.get(key) 1516 | 1517 | def provide_entropy(self, child): 1518 | if self.no_entropy: 1519 | child.expect(r'([a-z]): Not now') 1520 | child.send(child.match.group(1) + b"\n") 1521 | return 1522 | 1523 | while True: 1524 | # It would be good to match the "1:" prompt to detect 1525 | # multi-line mode, but there's an ANSI control sequence 1526 | # between the "1" and the ":". 1527 | r = child.expect([r'([a-z]): Manual(ly)? input', 1528 | r'Terminate (the )?input with an empty line.|' 1529 | r'Supply input to be used as a random seed', 1530 | r'(single)|(one) line']) 1531 | if r == 0: 1532 | child.send(child.match.group(1) + b"\n") 1533 | else: 1534 | break 1535 | multiline = (r == 1) 1536 | nbytes = 32 # 256 bits 1537 | f = open("/dev/random", "rb") 1538 | data = f.read(nbytes) 1539 | f.close() 1540 | assert(len(data) == nbytes) 1541 | text = bytes2hex(data) 1542 | # Temporarily disable logging of data to keep the seed secret 1543 | old_logfile_send = child.logfile_send 1544 | old_logfile_read = child.logfile_read 1545 | try: 1546 | child.logfile_send = CensorLogger(old_logfile_send) 1547 | child.logfile_read = CensorLogger(old_logfile_read) 1548 | child.send(text) 1549 | gather_input(child, 1) 1550 | finally: 1551 | child.logfile_send = old_logfile_send 1552 | child.logfile_read = old_logfile_read 1553 | child.send('\n') 1554 | if multiline: 1555 | child.send('\n') 1556 | 1557 | def _install(self): 1558 | # Download or build the install ISO 1559 | self.dist.set_workdir(self.workdir) 1560 | if self.get_arch_prop('image_name'): 1561 | self.dist.download() 1562 | else: 1563 | self.dist.make_install_sets_iso() 1564 | # Build the runtime boot ISO if needed 1565 | if self.dist.arch() == 'macppc': 1566 | self.dist.make_runtime_boot_iso() 1567 | if self.vmm != 'noemu': 1568 | print("Creating hard disk image...", end=' ') 1569 | sys.stdout.flush() 1570 | make_image(self.wd0_path(), parse_size(self.disk_size), 1571 | self.image_format) 1572 | print("done.") 1573 | sys.stdout.flush() 1574 | if self.get_arch_prop('image_name'): 1575 | self._install_from_image() 1576 | else: 1577 | self._install_using_sysinst() 1578 | 1579 | def _install_from_image(self): 1580 | image_name = self.get_arch_prop('image_name') 1581 | gzimage_fn = os.path.join(self.workdir, 1582 | 'download', self.dist.arch(), 1583 | 'binary', 'gzimg', image_name) 1584 | print("Decompressing image...", end=' ') 1585 | gzimage = open(gzimage_fn, 'r') 1586 | subprocess.call('gunzip | dd of=' + self.wd0_path() + ' conv=notrunc', 1587 | shell = True, stdin = gzimage) 1588 | gzimage.close() 1589 | print("done.") 1590 | # Unzip the kernel, whatever its name 1591 | for kernel_name in self.get_arch_prop('kernel_name'): 1592 | gzkernel_fn = os.path.join(self.workdir, 1593 | 'download', self.dist.arch(), 'binary', 'kernel', kernel_name) 1594 | if not os.path.exists(gzkernel_fn): 1595 | continue 1596 | kernel_name_nogz = kernel_name[:-3] 1597 | kernel_fn = os.path.join(self.workdir, kernel_name_nogz); 1598 | gunzip(gzkernel_fn, kernel_fn) 1599 | 1600 | # Boot the system to let it resize the image. 1601 | self.start_boot(install = False, snapshot_system_disk = False) 1602 | # The system will resize the image and then reboot. 1603 | # Wait for the login prompt and shut down cleanly. 1604 | self.child.expect(r"login:") 1605 | self.halt() 1606 | 1607 | def _install_using_sysinst(self): 1608 | # The name of the CD-ROM device holding the sets 1609 | sets_cd_device = None 1610 | 1611 | arch = self.dist.arch() 1612 | 1613 | if vmm_is_xen(self.vmm): 1614 | if self.xen_type == 'pv' or self.xen_type == 'pvshim' or self.xen_type == 'pvh': 1615 | # Download XEN kernels 1616 | xenkernels = [k for k in [ 1617 | self.dist.xen_boot_kernel(type = self.xen_type), 1618 | self.dist.xen_install_kernel(type = self.xen_type)] if k] 1619 | for kernel in xenkernels: 1620 | download_if_missing_3(self.dist.dist_url(), 1621 | self.dist.download_local_arch_dir(), 1622 | ["binary", "kernel", kernel], 1623 | True) 1624 | vmm_args = [] 1625 | vmm_args += self.xen_args(install = True) 1626 | if self.xen_type == 'pv' or self.xen_type == 'pvshim' or self.xen_type == 'pvh': 1627 | vmm_args += [self.xen_disk_arg(os.path.abspath( 1628 | self.dist.install_sets_iso_path()), 1, cdrom = True)] 1629 | sets_cd_device = 'xbd1d' 1630 | elif self.xen_type == 'hvm': 1631 | # Similar the qemu boot_from == 'cdrom' case below 1632 | boot_cd_path = os.path.join(self.dist.boot_iso_dir(), 1633 | self.dist.boot_isos()[0]) 1634 | vmm_args += [self.xen_disk_arg(os.path.abspath( 1635 | boot_cd_path), 2, cdrom = True)] 1636 | vmm_args += [self.xen_disk_arg(os.path.abspath( 1637 | self.dist.install_sets_iso_path()), 3, cdrom = True)] 1638 | sets_cd_device = 'cd1a' 1639 | else: 1640 | raise RuntimeError('unknown xen type %s' % self.xen_type) 1641 | child = self.start_xen_domu(vmm_args) 1642 | elif self.vmm == 'qemu': 1643 | # Determine what kind of media to boot from. 1644 | floppy_paths = [ os.path.join(self.dist.floppy_dir(), f) \ 1645 | for f in self.dist.floppies() ] 1646 | if self.boot_from is None: 1647 | self.boot_from = self.dist.boot_from_default() 1648 | if self.boot_from is None: 1649 | self.boot_from = 'cdrom' 1650 | 1651 | sets_cd_device = None 1652 | 1653 | # Set up VM arguments based on the chosen boot media 1654 | if self.boot_from == 'cdrom': 1655 | if self.dist.arch() in ['macppc']: 1656 | # Boot from the CD we just built, with the sets. 1657 | # The drive must have index 2. 1658 | cd_path = self.dist.install_sets_iso_path() 1659 | vmm_args, sets_cd_device = self.qemu_add_cdrom(cd_path) 1660 | vmm_args += [ "-prom-env", "boot-device=cd:,netbsd-INSTALL" ] 1661 | else: 1662 | # Boot from a downloaded boot CD w/o sets 1663 | cd_path = os.path.join(self.dist.boot_iso_dir(), self.dist.boot_isos()[0]) 1664 | vmm_args, dummy = self.qemu_add_cdrom(cd_path) 1665 | vmm_args += ["-boot", "d"] 1666 | elif self.boot_from == 'floppy': 1667 | if len(floppy_paths) == 0: 1668 | raise RuntimeError("found no boot floppies") 1669 | vmm_args = ["-drive", "file=%s,format=raw,if=floppy,readonly=on" 1670 | % floppy_paths[0]] 1671 | vmm_args += ["-boot", "a"] 1672 | elif self.boot_from == 'cdrom-with-sets': 1673 | # Single CD 1674 | vmm_args = ["-boot", "d"] 1675 | elif self.boot_from == 'net': 1676 | # This is incomplete. It gets as far as running 1677 | # pxeboot, but pxeboot is unable to load the kernel 1678 | # because it defaults to loading the kernel over NFS, 1679 | # and we support only TFTP. To specify "tftp:netbsd", 1680 | # we would need a way to respond with different bootfile 1681 | # DHCP options at the PXE ROM and pxeboot stages, but 1682 | # the built-in BOOTP/DHCP server in qemu has no way to 1683 | # do that. A pxeboot patched to load "tftp:netbsd" 1684 | # instead of "netbsd" does successfully load the kernel. 1685 | 1686 | # Note that although the kernel is netbooted, the sets 1687 | # are still read from a CD (unlike the noemu case). 1688 | 1689 | tftpdir = os.path.join(self.workdir, 'tftp') 1690 | mkdir_p(tftpdir) 1691 | 1692 | # Configure pxeboot for a serial console 1693 | # On a non-NetBSD host, you need to cross-build installboot and 1694 | # install it in the PATH as nbinstalboot 1695 | # XXX dup wrt noemu 1696 | pxeboot_com_fn = 'pxeboot_ia32_com.bin' 1697 | pxeboot_com_path = os.path.join(tftpdir, pxeboot_com_fn) 1698 | shutil.copyfile(os.path.join(self.dist.download_local_arch_dir(), 1699 | 'installation/misc/pxeboot_ia32.bin'), 1700 | pxeboot_com_path) 1701 | 1702 | if os.uname()[0] == 'NetBSD': 1703 | installboot = '/usr/sbin/installboot' 1704 | else: 1705 | installboot = 'nbinstallboot' 1706 | subprocess.check_call([installboot, '-e', 1707 | '-m', self.dist.arch(), 1708 | '-o', 'console=com0', pxeboot_com_path]) 1709 | 1710 | inst_kernel = os.path.join(tftpdir, 'netbsd') 1711 | # Use the INSTALL kernel 1712 | # Unzip the install kernel into the tftp directory 1713 | zipped_kernel = os.path.join(self.dist.download_local_arch_dir(), 1714 | 'binary/kernel/netbsd-INSTALL.gz') 1715 | gunzip(zipped_kernel, inst_kernel) 1716 | 1717 | vmm_args = ['-boot', 'n', 1718 | '-nic', 1719 | 'user,' + 1720 | qemu_format_attrs([('id', 'um0'), 1721 | ('tftp', tftpdir), 1722 | ('bootfile', pxeboot_com_fn)])] 1723 | elif self.boot_from == 'kernel': 1724 | # alpha 1725 | cd_path = self.dist.install_sets_iso_path() 1726 | vmm_args, sets_cd_device = self.qemu_add_cdrom(cd_path) 1727 | # Uncompress the installation kernel 1728 | inst_kernel = os.path.join(self.workdir, 'netbsd_install') 1729 | gunzip(os.path.join(self.dist.download_local_arch_dir(), 1730 | *arch_props[self.dist.arch()]['inst_kernel'].split(os.path.sep)), 1731 | inst_kernel) 1732 | vmm_args += ['-kernel', inst_kernel] 1733 | else: 1734 | raise RuntimeError("unsupported boot-from value %s" % self.boot_from) 1735 | 1736 | # If we don't have a CD with sets already, use the next 1737 | # available CD drive 1738 | if not sets_cd_device: 1739 | sets_cd_args, sets_cd_device = self.qemu_add_cdrom(self.dist.install_sets_iso_path()) 1740 | vmm_args += sets_cd_args 1741 | child = self.start_qemu(vmm_args, snapshot_system_disk = False) 1742 | elif self.vmm == 'noemu': 1743 | child = self.start_noemu(['--boot-from', 'net']) 1744 | if self.dist.arch() in ('i386', 'amd64'): 1745 | child.expect(r'(PXE [Bb]oot)|(BIOS [Bb]oot)') 1746 | if child.match.group(2): 1747 | raise RuntimeError("got BIOS bootloader instead of PXE") 1748 | # For 'evbmips-mips64eb', look for r'BOOTP broadcast' instead? 1749 | elif self.vmm == 'gxemul': 1750 | sets_cd_device = 'cd0a' 1751 | if self.dist.arch() == 'hpcmips': 1752 | sets_cd_device = 'cd0d' 1753 | elif self.dist.arch() == 'landisk': 1754 | sets_cd_device = 'wd1a' 1755 | vmm_args = ["-d", self.gxemul_cdrom_args()] 1756 | if self.dist.arch() in ['pmax', 'landisk']: 1757 | vmm_args += [os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 1758 | "binary", "kernel", "netbsd-INSTALL.gz"))] 1759 | elif self.dist.arch() == 'hpcmips': 1760 | vmm_args += [os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 1761 | "installation", "netbsd.gz"))] 1762 | child = self.start_gxemul(vmm_args) 1763 | elif self.vmm == 'simh': 1764 | sets_cd_device = 'cd0a' 1765 | child = self.start_simh() 1766 | child.expect(r">>>") 1767 | child.send("boot dua3\r\n") 1768 | else: 1769 | raise RuntimeError('unknown vmm %s' % self.vmm) 1770 | 1771 | term = None 1772 | if self.dist.arch() in ['hpcmips', 'landisk', 'hppa']: 1773 | term = 'vt100' 1774 | 1775 | # Do the floppy swapping dance and other pre-sysinst interaction 1776 | floppy0_name = None 1777 | while True: 1778 | # NetBSD/i386 will prompt for a terminal type if booted from a 1779 | # CD-ROM, but not when booted from floppies. Sigh. 1780 | r = child.expect([ 1781 | # 0 (was Group 1-2) 1782 | r"insert disk (\d+), and press return...", 1783 | # 1 (was Group 3) 1784 | # Match either the English or the German text. 1785 | # This is a kludge to deal with kernel messages 1786 | # like "ciss0: normal state on 'ciss0:1'" that 1787 | # sometimes appear in the middle of one or the 1788 | # other, but are unlikely to appear in the middle of 1789 | # both. The installation is done in English no 1790 | # matter which one we happen to match. 1791 | r"Installation messages in English|Installation auf Deutsch", 1792 | # 2 (was Group 4) 1793 | r"Terminal type", 1794 | # 3 (was Group 5) 1795 | r"Installation medium to load the additional utilities from: ", 1796 | # 4 (was Group 6) 1797 | r"1. Install NetBSD", 1798 | # 5 (was Group 7) 1799 | r"\(I\)nstall, \(S\)hell or \(H\)alt", 1800 | # 6 Erlite3 bootloader 1801 | r'UBNT_E100', 1802 | ]) 1803 | if r == 0: 1804 | # We got the "insert disk" prompt 1805 | # There is no floppy 0, hence the "- 1" 1806 | floppy_index = int(child.match.group(1)) - 1 1807 | 1808 | # Escape into qemu command mode to switch floppies 1809 | child.send("\001c") 1810 | # We used to wait for a (qemu) prompt here, but qemu 0.9.1 1811 | # no longer prints it 1812 | # child.expect(r'\(qemu\)') 1813 | if not floppy0_name: 1814 | # Between qemu 0.9.0 and 0.9.1, the name of the floppy 1815 | # device accepted by the "change" command changed from 1816 | # "fda" to "floppy0" without any provision for backwards 1817 | # compatibility. Deal with it. Also deal with the fact 1818 | # that as of qemu 0.15, "info block" no longer prints 1819 | # "type=floppy" for floppy drives. And in qemu 2.5.0, 1820 | # the format changed again from "floppy0: " to 1821 | # "floppy0 (#block544): ", so we no longer match the 1822 | # colon and space. 1823 | child.send("info block\n") 1824 | child.expect(r'\n(fda|floppy0)') 1825 | floppy0_name = child.match.group(1) 1826 | # Now we can change the floppy 1827 | child.send(b"change " + floppy0_name + b" " + 1828 | floppy_paths[floppy_index].encode('ASCII') + b"\n") 1829 | # Exit qemu command mode 1830 | child.send("\001c\n") 1831 | elif r == 1: 1832 | # "Installation messages in English" 1833 | break 1834 | elif r == 2: 1835 | # "Terminal type" 1836 | child.send("xterm\n") 1837 | term = "xterm" 1838 | elif r == 3: 1839 | # "Installation medium to load the additional utilities from" 1840 | # (SPARC) 1841 | child.send("cdrom\n") 1842 | child.expect(r"CD-ROM device to use") 1843 | child.send("\n") 1844 | child.expect(r"Path to instfs.tgz") 1845 | child.send("\n") 1846 | child.expect(r"Terminal type") 1847 | # The default is "sun", but anita is more likely to run 1848 | # in an xterm or some other ansi-like terminal than on 1849 | # a sun console. 1850 | child.send("xterm\n") 1851 | term = "xterm" 1852 | child.expect(r"nstall/Upgrade") 1853 | child.send("I\n") 1854 | elif r == 4: 1855 | # "1. Install NetBSD" 1856 | child.send("1\n") 1857 | elif r == 5: 1858 | # "(I)nstall, (S)hell or (H)alt ?" 1859 | child.send("i\n") 1860 | elif r == 6: 1861 | child.send('\003') # control-c 1862 | child.expect('Octeon ubnt_e100#') 1863 | child.send('dhcp;tftp $loadaddr erlite.elf32;bootoctlinux\r') 1864 | if self.vmm == 'noemu': 1865 | self.slog("wait for envsys to settle down") 1866 | time.sleep(30) 1867 | 1868 | # Confirm "Installation messages in English" 1869 | child.send("\n") 1870 | 1871 | # i386 and amd64 ask for keyboard type here; sparc doesn't 1872 | while True: 1873 | child.expect(r"(Keyboard type)|(a: Install NetBSD to hard disk)|" + 1874 | "(Shall we continue)") 1875 | if child.match.group(1) or child.match.group(2): 1876 | child.send("\n") 1877 | elif child.match.group(3): 1878 | child.expect(r"([a-z]): Yes") 1879 | child.send(child.match.group(1) + b"\n") 1880 | break 1881 | else: 1882 | raise AssertionError 1883 | 1884 | # We may or may not get an entropy prompt here. Then, 1885 | # dpending on the number of disks attached, we get either 1886 | # "found only one disk" followed by "Hit enter to continue", 1887 | # or "On which disk do you want to install". 1888 | 1889 | while True: 1890 | r = child.expect([r'not enough entropy|if a small random seed', 1891 | r'Hit enter to continue', 1892 | r'On which disk do you want to install']) 1893 | if r == 0: 1894 | self.provide_entropy(child) 1895 | elif r == 1: 1896 | child.send("\n") 1897 | break 1898 | elif r == 2: 1899 | child.send("a\n") 1900 | break 1901 | else: 1902 | raise AssertionError 1903 | 1904 | def choose_no(): 1905 | child.expect(r"([a-z]): No") 1906 | child.send(child.match.group(1) + b"\n") 1907 | def choose_yes(): 1908 | child.expect(r"([a-z]): Yes") 1909 | child.send(child.match.group(1) + b"\n") 1910 | 1911 | # Keep track of sets we have already handled, by label. 1912 | # This is needed so that parsing a pop-up submenu is not 1913 | # confused by earlier output echoing past choices. 1914 | labels_seen = set() 1915 | 1916 | def choose_sets(set_list, level = 0): 1917 | sets_this_screen = [] 1918 | # First parse the set selection screen or popup; it's messy. 1919 | while True: 1920 | # Match a letter-label pair, like "h: Compiler Tools", 1921 | # followed by an installation status of Yes, No, All, 1922 | # or None. The label can be separated from the "Yes/No" 1923 | # field either by spaces (at least two, so that there can 1924 | # be single spaces within the label), or by a cursor 1925 | # positioning escape sequence. The escape sequence may 1926 | # be preceded by zero or more spaces. Apparently this 1927 | # variety arises from the fact that the submenus are drawn 1928 | # on top of the existing text of the main menu without 1929 | # clearing the screen inbetween, so that the preexisting 1930 | # screen content between the label and the "Yes/No" may 1931 | # or may not consist of spaces that curses can reuse. 1932 | # 1933 | # Alternatively, match the special letter "x: " which 1934 | # is not followed by an installation status. 1935 | child.expect( 1936 | r"(?:([a-z]): ([^ \x1b]+(?: [^ \x1b]+)*)(?:(?:\s\s+)|(?:\s*\x1b\[\d+;\d+H\x00*))(Yes|No|All|None))|(x: )") 1937 | (letter, label, yesno, exit) = child.match.groups() 1938 | if exit: 1939 | if len(sets_this_screen) != 0: 1940 | break 1941 | else: 1942 | #self.slog(label) 1943 | for set in set_list: 1944 | if re.match(set[r'label'], label) and label not in labels_seen: 1945 | sets_this_screen.append({ 1946 | 'set': set, 1947 | 'letter': letter, 1948 | 'state': yesno 1949 | }) 1950 | labels_seen.add(label) 1951 | 1952 | # Then make the actual selections 1953 | for item in sets_this_screen: 1954 | set = item['set'] 1955 | enable = set['install'] 1956 | state = item['state'] 1957 | group = set.get('group') 1958 | if (enable and state == b"No" or \ 1959 | not enable and state == b"Yes") \ 1960 | or group: 1961 | child.send(item['letter'] + b"\n") 1962 | if group: 1963 | # Recurse to handle sub-menu 1964 | choose_sets(group, level + 1) 1965 | 1966 | # Exit the set selection menu 1967 | child.send("x\n") 1968 | 1969 | # Older NetBSD versions show a prompt like [re0] and ask you 1970 | # to type in the interface name (or enter for the default); 1971 | # newer versions show a menu. 1972 | 1973 | def choose_interface_oldstyle(): 1974 | self.slog('old-style interface list') 1975 | # Choose the first non-fwip interface 1976 | while True: 1977 | child.expect(r"([a-z]+)([0-9]) ") 1978 | ifname = child.match.group(1) 1979 | ifno = child.match.group(2) 1980 | self.slog('old-style interface: <%s,%s>' % (repr(ifname), repr(ifno))) 1981 | if ifname != 'fwip': 1982 | # Found an acceptable interface 1983 | child.send(ifname + ifno + b"\n") 1984 | break 1985 | 1986 | def choose_interface_newstyle(): 1987 | self.slog('new-style interface list') 1988 | child.expect(r'Available interfaces') 1989 | # Choose the first non-fwip interface 1990 | while True: 1991 | # Make sure to match the digit after the interface 1992 | # name so that we don't accept a partial interface 1993 | # name like "fw" from "fwip0". 1994 | child.expect(r"([a-z]): ([a-z]+)[0-9]") 1995 | if child.match.group(2) != 'fwip': 1996 | # Found an acceptable interface 1997 | child.send(child.match.group(1) + b"\n") 1998 | break 1999 | 2000 | def configure_network(): 2001 | def choose_dns_server(): 2002 | child.expect(r"([a-z]): other") 2003 | child.send(child.match.group(1) + b"\n") 2004 | child.send((self.net_config.get('dnsserveraddr') or "10.0.1.1") + "\n") 2005 | 2006 | expect_any(child, 2007 | r"Network media type", "\n", 2008 | r"Perform (DHCP )?autoconfiguration", choose_no, 2009 | r"Hit enter to continue", "\n", 2010 | r"Your host name", 2011 | "anita-test\n", 2012 | r"Your DNS domain", 2013 | "netbsd.org\n", 2014 | r"Your IPv4 (number)|(address)", 2015 | (self.net_config.get('client_addr') or "10.169.0.2") + "\n", 2016 | r"IPv4 Netmask", 2017 | (self.net_config.get('netmask') or "255.255.255.0") + "\n", 2018 | r"IPv4 gateway", 2019 | (self.net_config.get('gateway_addr') or "10.169.0.1") + "\n", 2020 | r"IPv4 name server", 2021 | (self.net_config.get('dnsserveraddr') or "10.0.1.1") + "\n", 2022 | r"Perform IPv6 autoconfiguration", choose_no, 2023 | r"Select (IPv6 )?DNS server", choose_dns_server, 2024 | r"Are they OK", choose_yes) 2025 | self.network_configured = True 2026 | 2027 | def choose_install_media(): 2028 | # Noemu installs from HTTP, otherwise we use the CD-ROM 2029 | media = ['CD-ROM', 'HTTP'][self.vmm == 'noemu'] 2030 | child.expect(r'([a-h]): ' + media) 2031 | child.send(child.match.group(1) + b"\n") 2032 | 2033 | self.network_configured = False 2034 | 2035 | # Many different things can happen at this point: 2036 | # 2037 | # Versions older than 2009/08/23 21:16:17 will display a menu 2038 | # for choosing the extraction verbosity 2039 | # 2040 | # Versions older than 2010/03/30 20:09:25 will display a menu for 2041 | # choosing the CD-ROM device (newer versions will choose automatically) 2042 | # 2043 | # Versions older than Fri Apr 6 23:48:53 2012 UTC will ask 2044 | # you to "Please choose the timezone", wheras newer ones will 2045 | # instead as you to "Configure the additional items". 2046 | # 2047 | # At various points, we may or may not get "Hit enter to continue" 2048 | # prompts (and some of them seem to appear nondeterministically) 2049 | # 2050 | # i386/amd64 can ask whether to use normal or serial console bootblocks 2051 | # 2052 | # Try to deal with all of the possible options. 2053 | # 2054 | # We specify a longer timeout than the default here, because the 2055 | # set extraction can take a long time on slower machines. 2056 | # 2057 | # It has happened (at least with NetBSD 3.0.1) that sysinst paints the 2058 | # screen twice. This can cause problem because we will then respond 2059 | # twice, and the second response will be interpreted as a response to 2060 | # a subsequent prompt. Therefore, we check whether the match is the 2061 | # same as the previous one and ignore it if so. 2062 | # 2063 | # OTOH, -current as of 2009.08.23.20.57.40 will issue the message "Hit 2064 | # enter to continue" twice in a row, first as a result of MAKEDEV 2065 | # printing a warning messages "MAKEDEV: dri0: unknown device", and 2066 | # then after "sysinst will give you the opportunity to configure 2067 | # some essential things first". We match the latter text separately 2068 | # so that the "Hit enter to continue" matches are not consecutive. 2069 | # 2070 | # The changes of Apr 6 2012 broght with them a new redraw problem, 2071 | # which is worked around using the seen_essential_things variable. 2072 | # 2073 | prevmatch = [] 2074 | seen_essential_things = 0 2075 | loop = 0 2076 | while True: 2077 | loop = loop + 1 2078 | if loop == 100: 2079 | raise RuntimeError("loop detected") 2080 | r = child.expect([ 2081 | # 0 (unused, never matches) 2082 | r"(?!)", 2083 | # 1 2084 | r"a: Progress bar", 2085 | # 2 2086 | r"Select medium|Install from", 2087 | # 3 2088 | r"Enter the CDROM device", 2089 | # 4 2090 | r"unused-group-should-not-match", 2091 | # 5 2092 | r"Hit enter to continue", 2093 | # 6 2094 | r"b: Use serial port com0", 2095 | # 7 2096 | r"Please choose the timezone", 2097 | # 8 2098 | r"essential things", 2099 | # 9 2100 | r"Configure the additional items", 2101 | # 10 2102 | r"Multiple CDs found", 2103 | # 11 2104 | r"The following are the http site", 2105 | # 12 2106 | r"Is the network information you entered accurate", 2107 | # 13 (old-style) 2108 | r"I have found the following network interface", 2109 | # 14 (new-style) 2110 | r"Which network device would you like to use", 2111 | # 15 2112 | r"No allows you to continue anyway", 2113 | # 16 2114 | r"Can't connect to", 2115 | # 17 2116 | r"/sbin/newfs", 2117 | # 18 2118 | r"Do you want to install the NetBSD bootcode", 2119 | # 19 2120 | r"Do you want to update the bootcode in the Master Boot Record to the latest", 2121 | # 20 2122 | r"([a-z]): Custom installation", 2123 | # 21 (unused, never matches) 2124 | r"(?!)", 2125 | # 22 2126 | r"a: This is the correct geometry", 2127 | # 23 2128 | r"a: Use one of these disks", 2129 | # 24 2130 | r"([a-z]): Set sizes of NetBSD partitions", 2131 | # 25 (unused, never matches) 2132 | r"(?!)", 2133 | # 26 2134 | r"a partitioning scheme", 2135 | # 27 2136 | r"([a-z]): Use the entire disk", 2137 | # 28 (unused, never matches) 2138 | r"(?!)", 2139 | # 29 2140 | r'Do you want to install the NetBSD bootcode', 2141 | # 30 2142 | r'Do you want to update the bootcode', 2143 | # 31 2144 | r"Please enter a name for your NetBSD disk", 2145 | # 32 2146 | # Matching "This is your last chance" will not work 2147 | r"ready to install NetBSD on your hard disk", 2148 | # 33 2149 | r"We now have your (?:BSD.|GPT.)?(?:disklabel )?partitions", 2150 | # 34 (formerly 28) 2151 | r'Your disk currently has a non-NetBSD partition', 2152 | # 35 (formerly 25) 2153 | r"Sysinst could not automatically determine the BIOS geometry of the disk", 2154 | # 36 2155 | r"Do you want to re-edit the disklabel partitions", 2156 | # 37 (to reset timeout while set extraction is making progress) 2157 | r'Command: ', 2158 | # 38 2159 | r'not enough entropy', 2160 | # 39 2161 | r'Changing local password for root', 2162 | # 40 2163 | r'(Accept partition sizes)|(Go on)', 2164 | ], 10800) 2165 | 2166 | if child.match.group(0) == prevmatch: 2167 | self.slog('ignoring repeat match') 2168 | continue 2169 | prevmatch = child.match.group(0) 2170 | if r == 0: 2171 | raise AssertionError 2172 | elif r == 1: 2173 | # (a: Progress bar) 2174 | child.send("\n") 2175 | elif r == 2: 2176 | # (Install from) 2177 | choose_install_media() 2178 | elif r == 3: 2179 | # "(Enter the CDROM device)" 2180 | if sets_cd_device != 'cd0a': 2181 | child.send(b"a\n" + sets_cd_device.encode('ASCII') + b"\n") 2182 | # In 3.0.1, you type "c" to continue, whereas in -current, 2183 | # you type "x". Handle both cases. 2184 | child.expect(r"([cx]): Continue") 2185 | child.send(child.match.group(1) + b"\n") 2186 | elif r == 5: 2187 | # (Hit enter to continue) 2188 | if seen_essential_things >= 2: 2189 | # This must be a redraw 2190 | pass 2191 | else: 2192 | child.send("\n") 2193 | elif r == 6: 2194 | # (b: Use serial port com0) 2195 | child.send("bx\n") 2196 | elif r == 7: 2197 | # (Please choose the timezone) 2198 | # "Press 'x' followed by RETURN to quit the timezone selection" 2199 | child.send("x\n") 2200 | # The strange non-deterministic "Hit enter to continue" prompt has 2201 | # also been spotted after executing the sed commands to set the 2202 | # root password cipher, with 2010.10.27.10.42.12 source. 2203 | while True: 2204 | child.expect(r"(([a-z]): DES)|(root password)|(Hit enter to continue)") 2205 | if child.match.group(1): 2206 | # DES 2207 | child.send(child.match.group(2) + b"\n") 2208 | elif child.match.group(3): 2209 | # root password 2210 | break 2211 | elif child.match.group(4): 2212 | # (Hit enter to continue) 2213 | child.send("\n") 2214 | else: 2215 | raise AssertionError 2216 | # Don't set a root password 2217 | child.expect(r"b: No") 2218 | child.send("b\n") 2219 | child.expect(r"a: /bin/sh") 2220 | child.send("\n") 2221 | 2222 | # "The installation of NetBSD-3.1 is now complete. The system 2223 | # should boot from hard disk. Follow the instructions in the 2224 | # INSTALL document about final configuration of your system. 2225 | # The afterboot(8) manpage is another recommended reading; it 2226 | # contains a list of things to be checked after the first 2227 | # complete boot." 2228 | # 2229 | # We are supposed to get a single "Hit enter to continue" 2230 | # prompt here, but sometimes we get a weird spurious one 2231 | # after running chpass above. 2232 | 2233 | while True: 2234 | child.expect(r"(Hit enter to continue)|(x: Exit)") 2235 | if child.match.group(1): 2236 | child.send("\n") 2237 | elif child.match.group(2): 2238 | child.send("x\n") 2239 | break 2240 | else: 2241 | raise AssertionError 2242 | break 2243 | elif r == 8: 2244 | # (essential things) 2245 | seen_essential_things += 1 2246 | elif r == 9: 2247 | # (Configure the additional items) 2248 | child.expect(r"x: Finished configuring") 2249 | child.send("x\n") 2250 | break 2251 | elif r == 10: 2252 | # (Multiple CDs found) 2253 | # This happens if we have a boot CD and a CD with sets; 2254 | # we need to choose the latter. 2255 | child.send("b\n") 2256 | elif r == 11: 2257 | gather_input(child, 1) 2258 | # (The following are the http site) 2259 | # \027 is control-w, which clears the field 2260 | child.send("a\n") # IP address 2261 | child.send("\027" + 2262 | (self.net_config.get('serveraddr') or "10.169.0.1") + "\n") 2263 | child.send("b\n\027\n") # Directory = empty string 2264 | if not self.network_configured: 2265 | child.send("j\n") # Configure network 2266 | choose_interface_newstyle() 2267 | configure_network() 2268 | # We get 'Hit enter to continue' if this sysinst 2269 | # version tries ping6 even if we have not configured 2270 | # IPv6 2271 | expect_any(child, 2272 | r'Hit enter to continue', '\r', 2273 | r'x: Get Distribution', 'x\n') 2274 | r = child.expect([r"Install from", r"/usr/bin/ftp"]) 2275 | if r == 0: 2276 | # ...and I'm back at the "Install from" menu? 2277 | # Probably the same bug reported as install/49440. 2278 | choose_install_media() 2279 | # And again... 2280 | child.expect(r"The following are the http site") 2281 | child.expect(r"x: Get Distribution") 2282 | child.send("x\n") 2283 | elif r == 1: 2284 | pass 2285 | else: 2286 | assert(0) 2287 | elif r == 12: 2288 | # "Is the network information you entered accurate" 2289 | child.expect(r"([a-z]): Yes") 2290 | child.send(child.match.group(1) + b"\n") 2291 | elif r == 13: 2292 | # "(I have found the following network interfaces)" 2293 | choose_interface_oldstyle() 2294 | configure_network() 2295 | elif r == 14: 2296 | # "(Which network device would you like to use)" 2297 | choose_interface_newstyle() 2298 | configure_network() 2299 | elif r == 15: 2300 | choose_no() 2301 | child.expect(r"No aborts the install process") 2302 | choose_yes() 2303 | elif r == 16: 2304 | self.slog("network problems detected") 2305 | child.send("\003") # control-c 2306 | gather_input(child, 666) 2307 | for i in range(60): 2308 | child.send("ifconfig -a\n") 2309 | gather_input(child, 1) 2310 | # would run netstat here but it's not on the install media 2311 | gather_input(child, 30) 2312 | sys.exit(1) 2313 | elif r == 17: 2314 | self.slog("matched newfs to defeat repeat match detection") 2315 | elif r == 18: 2316 | # "Do you want to install the NetBSD bootcode" 2317 | choose_yes() 2318 | elif r == 19: 2319 | # "Do you want to update the bootcode in the Master Boot Record to the latest" 2320 | choose_yes() 2321 | elif r == 20: 2322 | # Custom installation is choice "d" in 6.0, 2323 | # but choice "c" or "b" in older versions 2324 | # We could use "Minimal", but it doesn't exist in 2325 | # older versions. 2326 | child.send(child.match.group(1) + b"\n") 2327 | # Enable/disable sets. 2328 | choose_sets(self.dist.sets) 2329 | elif r == 21: 2330 | raise AssertionError 2331 | # On non-Xen i386/amd64 we first get group 22 or 23, 2332 | # then group 24; on sparc and Xen, we just get group 24. 2333 | elif r == 22: 2334 | # "This is the correct geometry" 2335 | child.send("\n") 2336 | elif r == 23: 2337 | # "a: Use one of these disks" 2338 | child.send("a\n") 2339 | child.expect(r"Choose disk") 2340 | child.send("0\n") 2341 | elif r == 24: 2342 | # "(([a-z]): Set sizes of NetBSD partitions)" 2343 | child.send(child.match.group(1) + b"\n") 2344 | elif r == 25: 2345 | raise AssertionError 2346 | elif r == 26: 2347 | # "a partitioning scheme" 2348 | if self.partitioning_scheme == 'MBR': 2349 | child.expect(r"([a-z]): Master Boot Record") 2350 | child.send(child.match.group(1) + b"\n") 2351 | else: 2352 | # Sparc asks the question but does not have MBR as an option, 2353 | # only disklabel. Just use the first choice, whatever that is. 2354 | child.send("a\n") 2355 | elif r == 27: 2356 | # "([a-z]): use the entire disk" 2357 | child.send(child.match.group(1) + b"\n") 2358 | elif r == 28: 2359 | raise AssertionError 2360 | elif r == 29 or r == 30: 2361 | # Install or replace bootcode 2362 | child.expect(r"a: Yes") 2363 | child.send("\n") 2364 | elif r == 31: 2365 | # "Please enter a name for your NetBSD disk" 2366 | child.send("\n") 2367 | elif r == 32: 2368 | # "ready to install NetBSD on your hard disk" 2369 | child.expect(r"Shall we continue") 2370 | child.expect(r"b: Yes") 2371 | child.send("b\n") 2372 | # newfs is run at this point 2373 | elif r == 33: 2374 | # "We now have your BSD disklabel partitions" 2375 | child.expect(r"x: Partition sizes ok") 2376 | child.send("x\n") 2377 | elif r == 34: 2378 | # Your disk currently has a non-NetBSD partition 2379 | choose_yes() 2380 | elif r == 35: 2381 | # We need to enter these values in cases where sysinst could not 2382 | # determine disk geometry. Currently, this happens for NetBSD/hpcmips 2383 | child.expect(r"sectors") 2384 | child.send("\n") 2385 | child.expect(r"heads") 2386 | child.send("\n") 2387 | elif r == 36: 2388 | raise RuntimeError('setting up partitions did not work first time') 2389 | elif r == 37: 2390 | pass 2391 | elif r == 38: 2392 | # not enough entropy 2393 | self.provide_entropy(child) 2394 | elif r == 39: 2395 | # Changing local password for root 2396 | child.expect(r"sword:") 2397 | child.send("\n") 2398 | elif r == 40: 2399 | # (Accept partition sizes)|(Go on) 2400 | # 2401 | # In 2.1, no letter label like "x: " is printed before 2402 | # "Accept partition sizes", hence the kludge of sending 2403 | # multiple cursor-down sequences. 2404 | 2405 | #child.send(child.match.group(1) + "\n") 2406 | # Press cursor-down enough times to get to the end of the list, 2407 | # to the "Accept partition sizes" entry, then press 2408 | # enter to continue. Previously, we used control-N ("\016"), 2409 | # but if it gets echoed (which has happened), it is interpreted by 2410 | # the terminal as "enable line drawing character set", leaving the 2411 | # terminal in an unusable state. 2412 | if term in ['xterm', 'vt100']: 2413 | # For unknown reasons, when using a terminal type of "xterm", 2414 | # sysinst puts the terminal in "application mode", causing the 2415 | # cursor keys to send a different escape sequence than the default. 2416 | cursor_down = b"\033OB" 2417 | else: 2418 | # Use the default ANSI cursor-down escape sequence 2419 | cursor_down = b"\033[B" 2420 | child.send(cursor_down * 8 + b"\n") 2421 | else: 2422 | raise AssertionError 2423 | 2424 | # Installation is finished, halt the system. 2425 | # Historically, i386 and amd64, you get a root shell, 2426 | # while sparc just halts. 2427 | # Since Fri Apr 6 23:48:53 2012 UTC, you are kicked 2428 | # back into the main menu. 2429 | 2430 | x_sent = False 2431 | while True: 2432 | r = child.expect([r'Hit enter to continue', 2433 | r'x: Exit Install System', 2434 | r'#', 2435 | r'halting machine', 2436 | r'halted by root', 2437 | r'Would you like to setup system entropy now']) 2438 | if r == 0: 2439 | child.send("\n") 2440 | elif r == 1: 2441 | # Back in menu 2442 | # Menu may get redrawn, so only send this once 2443 | if not x_sent: 2444 | child.send("x\n") 2445 | x_sent = True 2446 | elif r == 2: 2447 | # Root shell prompt 2448 | child.send("halt\n") 2449 | elif r == 3 or r == 4: 2450 | # halted 2451 | break 2452 | elif r == 5: 2453 | # Would you like to set up system entropy now? 2454 | choose_yes(); 2455 | self.provide_entropy(child) 2456 | else: 2457 | raise AssertionError 2458 | 2459 | self.halted = True 2460 | self.is_logged_in = False 2461 | self.post_halt_cleanup() 2462 | 2463 | def post_halt_cleanup(self): 2464 | # Keep logging for a few seconds more so that we gather 2465 | # the autoconf detach messages or a possible panic on 2466 | # detach. If we get EOF during the wait, ignore it. 2467 | try: 2468 | gather_input(self.child, 5) 2469 | except pexpect.EOF: 2470 | pass 2471 | self.slog('done') 2472 | self.child.close() 2473 | self.dist.cleanup() 2474 | self.cleanup_child() 2475 | 2476 | # Install NetBSD if not installed already 2477 | 2478 | def install(self): 2479 | # This is needed for Xen and noemu, where we get the kernel 2480 | # from the dist rather than the installed image 2481 | self.dist.set_workdir(self.workdir) 2482 | if self.vmm == 'noemu': 2483 | self.dist.download() 2484 | self._install() 2485 | else: 2486 | # Already installed? 2487 | if os.path.exists(self.wd0_path()): 2488 | return 2489 | try: 2490 | self._install() 2491 | except: 2492 | # "xl destroy" gets confused if the disk image 2493 | # has been removed, so run it before removing 2494 | # the disk image rather than after. 2495 | self.cleanup_child() 2496 | if os.path.exists(self.wd0_path()): 2497 | os.unlink(self.wd0_path()) 2498 | raise 2499 | 2500 | # Boot the virtual machine (installing it first if it's not 2501 | # installed already). The vmm_args argument applies when 2502 | # booting, but not when installing. Does not wait for 2503 | # a login prompt. 2504 | 2505 | def start_boot(self, vmm_args = None, install = None, snapshot_system_disk = None): 2506 | if vmm_args is None: 2507 | vmm_args = [] 2508 | if install is None: 2509 | install = not self.no_install 2510 | if snapshot_system_disk is None: 2511 | snapshot_system_disk = not self.persist 2512 | 2513 | if install: 2514 | self.install() 2515 | 2516 | # Start counting CD drives from 0 again after the install 2517 | self.n_cdrom = 0 2518 | 2519 | if self.dist.arch() in ['hpcmips', 'landisk']: 2520 | vmm_args += [os.path.abspath(os.path.join(self.dist.download_local_arch_dir(), 2521 | "binary", "kernel", "netbsd-GENERIC.gz"))] 2522 | if self.dist.arch() == 'macppc': 2523 | # macppc does not support booting from FFS, so boot from 2524 | # a CD instead 2525 | args, dummy = self.qemu_add_cdrom(self.dist.runtime_boot_iso_path(), [('index', '2')]) 2526 | vmm_args += args 2527 | vmm_args += ["-prom-env", "boot-device=cd:,netbsd-GENERIC"] 2528 | if self.dist.arch() == 'alpha': 2529 | generic_kernel = os.path.join(self.workdir, 'netbsd_generic') 2530 | gunzip(os.path.join(self.dist.download_local_arch_dir(), 2531 | "binary", "kernel", "netbsd-GENERIC.gz"), 2532 | generic_kernel) 2533 | vmm_args += ['-kernel', generic_kernel] 2534 | 2535 | if self.vmm == 'qemu': 2536 | child = self.start_qemu(vmm_args, snapshot_system_disk = snapshot_system_disk) 2537 | # "-net", "nic,model=ne2k_pci", "-net", "user" 2538 | if self.dist.arch() == 'macppc' and self.machine == 'mac99': 2539 | child.expect(r'root device.*:') 2540 | for c in "wd0a\r\n": 2541 | child.send(c) 2542 | child.expect(r"dump device \(default wd0b\):") 2543 | child.send("\r\n") 2544 | child.expect(r"file system \(default generic\):") 2545 | child.send("\r\n") 2546 | child.expect(r"init path \(default /sbin/init\):") 2547 | child.send("\r\n") 2548 | elif vmm_is_xen(self.vmm): 2549 | vmm_args += self.xen_args(install = False) 2550 | child = self.start_xen_domu(vmm_args) 2551 | elif self.vmm == 'noemu': 2552 | child = self.start_noemu(vmm_args + ['--boot-from', 'disk']) 2553 | elif self.vmm == 'gxemul': 2554 | child = self.start_gxemul(vmm_args) 2555 | elif self.vmm == 'simh': 2556 | child = self.start_simh(vmm_args) 2557 | child.expect(r">>>") 2558 | child.send("boot dua0\r\n") 2559 | else: 2560 | raise RuntimeError('unknown vmm %s' % vmm) 2561 | self.child = child 2562 | return child 2563 | 2564 | # Like start_boot(), but wait for a login prompt. 2565 | def boot(self, vmm_args = None): 2566 | # Needed to determine runtime_boot_iso_path on macppc 2567 | self.dist.set_workdir(self.workdir) 2568 | self.start_boot(vmm_args) 2569 | while True: 2570 | r = self.child.expect([r'\033\[c', r'\033\[5n', r'login:']) 2571 | if r == 0: 2572 | # The guest is trying to identify the terminal. 2573 | # Dell servers do this. Respond like an xterm. 2574 | self.child.send('\033[?1;2c') 2575 | elif r == 1: 2576 | # The guest sent "request terminal status". HP servers 2577 | # do this. Respond with "terminal ready". 2578 | self.child.send('\033[0n') 2579 | elif r == 2: 2580 | # Login prompt 2581 | break 2582 | else: 2583 | assert(0) 2584 | 2585 | # Can't close child here because we still need it if called from 2586 | # interact() 2587 | return self.child 2588 | 2589 | # Deprecated 2590 | def interact(self): 2591 | self.boot() 2592 | self.console_interaction() 2593 | 2594 | # Interact with the console of a system that has already been booted 2595 | def console_interaction(self): 2596 | # With pexpect 2.x and newer, we need to disable logging to stdout 2597 | # of data read from the slave, or otherwise everything will be 2598 | # printed twice. We can still log to the structured log, though. 2599 | self.child.logfile_read = Logger('recv', self.structured_log_f) 2600 | self.slog('entering console interaction') 2601 | self.child.interact() 2602 | 2603 | # Run the NetBSD ATF test suite on the guest. Note that the 2604 | # default timeout is separately defined here (for library callers) 2605 | # and in the "anita" script (for command-line callers). 2606 | def run_tests(self, timeout = 10800): 2607 | mkdir_p(self.workdir) 2608 | results_by_net = (self.vmm == 'noemu') 2609 | 2610 | # Create a scratch disk image for exporting test results from the VM. 2611 | # The results are stored in tar format because that is more portable 2612 | # and easier to manipulate than a file system image, especially if the 2613 | # host is a non-NetBSD system. 2614 | # 2615 | # If we are getting the results back by tftp, this file will 2616 | # be overwritten. 2617 | scratch_disk_path = os.path.join(self.workdir, "tests-results.img") 2618 | if vmm_is_xen(self.vmm): 2619 | scratch_disk = 'xbd1d' 2620 | elif self.dist.arch() == 'evbarm-earmv7hf' and self.machine == 'virt': 2621 | scratch_disk = 'ld5c' 2622 | else: 2623 | scratch_disk = self.get_arch_prop('scratch_disk') 2624 | 2625 | scratch_disk_args = [] 2626 | if scratch_disk: 2627 | scratch_image_megs = 100 2628 | make_dense_image(scratch_disk_path, parse_size('%dM' % scratch_image_megs)) 2629 | # Leave a 10% safety margin 2630 | max_result_size_k = scratch_image_megs * 900 2631 | 2632 | if vmm_is_xen(self.vmm): 2633 | scratch_disk_args = [self.xen_disk_arg(os.path.abspath(scratch_disk_path), 1)] 2634 | elif self.vmm == 'qemu': 2635 | scratch_disk_args = self.qemu_disk_args(os.path.abspath(scratch_disk_path), 1, True, False) 2636 | elif self.vmm == 'noemu': 2637 | pass 2638 | elif self.vmm == 'gxemul': 2639 | scratch_disk_args = self.gxemul_disk_args(os.path.abspath(scratch_disk_path)) 2640 | elif self.vmm == 'simh': 2641 | scratch_disk_args = ['set rq1 ra92', 'attach rq1 ' + scratch_disk_path] 2642 | else: 2643 | raise RuntimeError('unknown vmm') 2644 | 2645 | child = self.boot(scratch_disk_args) 2646 | self.login() 2647 | 2648 | # Build a shell command to run the tests 2649 | if self.tests == "kyua": 2650 | if self.shell_cmd("grep -q 'MKKYUA.*=.*yes' /etc/release") != 0: 2651 | raise RuntimeError("kyua is not installed.") 2652 | test_cmd = ( 2653 | "kyua " + 2654 | "--loglevel=error " + 2655 | "--logfile=/tmp/tests/kyua-test.log " + 2656 | "test " + 2657 | "--store=/tmp/tests/store.db; " + 2658 | "echo $? >/tmp/tests/test.status; " + 2659 | "kyua " + 2660 | "report " + 2661 | "--store=/tmp/tests/store.db " + 2662 | "| tail -n 3; " + 2663 | "kyua " + 2664 | "--loglevel=error " + 2665 | "--logfile=/tmp/tests/kyua-report-html.log " + 2666 | "report-html " + 2667 | "--store=/tmp/tests/store.db " + 2668 | "--output=/tmp/tests/html; ") 2669 | elif self.tests == "atf": 2670 | atf_aux_files = ['/usr/share/xsl/atf/tests-results.xsl', 2671 | '/usr/share/xml/atf/tests-results.dtd', 2672 | '/usr/share/examples/atf/tests-results.css'] 2673 | test_cmd = ( 2674 | "{ atf-run; echo $? >/tmp/tests/test.status; } | " + 2675 | "tee /tmp/tests/test.tps | " + 2676 | "atf-report -o ticker:- -o ticker:/tmp/tests/test.txt " + 2677 | "-o xml:/tmp/tests/test.xml; " + 2678 | "(cd /tmp && for f in %s; do cp $f tests/; done;); " % ' '.join(atf_aux_files)) 2679 | else: 2680 | raise RuntimeError('unknown testing framework %s' % self.test) 2681 | 2682 | # Build a shell command to save the test results 2683 | if results_by_net: 2684 | save_test_results_cmd = ( 2685 | "{ cd /tmp && " + 2686 | "tar cf tests-results.img tests && " + 2687 | "(echo blksize 8192; echo put tests-results.img) | tftp %s; }; " % \ 2688 | (self.net_config.get('serveraddr') or "10.169.0.1") 2689 | ) 2690 | elif scratch_disk: 2691 | save_test_results_cmd = ( 2692 | "{ cd /tmp && " + 2693 | # Make sure the files will fit on the scratch disk 2694 | "test `du -sk tests | awk '{print $1}'` -lt %d && " % max_result_size_k + 2695 | # To guard against accidentally overwriting the wrong 2696 | # disk image, check that the disk contains nothing 2697 | # but nulls. 2698 | "test ` ', # sparc64 firmware prompt 2765 | r'System halted!', # hppa 2766 | r'halted', # macppc 2767 | ], timeout = 60) 2768 | except pexpect.EOF: 2769 | # Didn't see the text but got an EOF; that's OK. 2770 | print("EOF") 2771 | except pexpect.TIMEOUT as e: 2772 | # This is unexpected but mostly harmless 2773 | print("timeout waiting for halt confirmation:", e) 2774 | self.halted = True 2775 | self.is_logged_in = False 2776 | self.post_halt_cleanup() 2777 | 2778 | # Calling this directly is deprecated, use Anita.login() 2779 | 2780 | def login(child): 2781 | # Send a newline character to get another login prompt, since boot() consumed one. 2782 | child.send("\n") 2783 | child.expect(r"login:") 2784 | child.send("root\n") 2785 | # This used to be "\n# ", but that doesn't work if the machine has 2786 | # a hostname 2787 | child.expect(r"# ") 2788 | 2789 | # Generate a root shell prompt string that is less likely to appear in 2790 | # the console output by accident than the default of "# ". Must end with "# ". 2791 | 2792 | def gen_shell_prompt(): 2793 | return 'anita-root-shell-prompt-%s# ' % str(time.time()) 2794 | 2795 | # Quote a prompt in /bin/sh syntax, with some extra quotes 2796 | # in the middle so that an echoed command to set the prompt is not 2797 | # mistaken for the prompt itself. 2798 | 2799 | def quote_prompt(s): 2800 | midpoint = len(s) // 2 2801 | return "".join("'%s'" % part for part in (s[0:midpoint], s[midpoint:])) 2802 | 2803 | # Expect any of "patterns" with timeout "timeout", resetting the timeout 2804 | # whenever one of "keepalive_patterns" occurs. 2805 | 2806 | def expect_with_keepalive(child, patterns, timeout, keepalive_patterns): 2807 | if keepalive_patterns is None: 2808 | keepalive_patterns = [] 2809 | all_patterns = patterns + keepalive_patterns 2810 | while True: 2811 | i = child.expect(all_patterns, timeout) 2812 | if i < len(patterns): 2813 | break 2814 | return i 2815 | 2816 | # Calling this directly is deprecated, use Anita.shell_cmd() 2817 | 2818 | def shell_cmd(child, cmd, timeout = -1, keepalive_patterns = None): 2819 | child.send("exec /bin/sh\n") 2820 | child.expect(r"# ") 2821 | prompt = gen_shell_prompt() 2822 | child.send("PS1=" + quote_prompt(prompt) + "\n") 2823 | prompt_re = prompt 2824 | child.expect(prompt_re) 2825 | child.send(cmd + "\n") 2826 | # Catch EOF to log the signalstatus, to help debug qemu crashes 2827 | try: 2828 | expect_with_keepalive(child, [prompt_re], timeout, keepalive_patterns) 2829 | except pexpect.EOF: 2830 | print("pexpect reported EOF - VMM exited unexpectedly") 2831 | child.close() 2832 | print("exitstatus", child.exitstatus) 2833 | print("signalstatus", child.signalstatus) 2834 | raise 2835 | except: 2836 | raise 2837 | child.send("echo exit_status=$?=\n") 2838 | child.expect(r"exit_status=(\d+)=") 2839 | r = int(child.match.group(1)) 2840 | child.expect(prompt_re, timeout) 2841 | return r 2842 | 2843 | def test(child): 2844 | raise RuntimeError("global test() function is gone, use Anita.run_tests()") 2845 | 2846 | ############################################################################# 2847 | --------------------------------------------------------------------------------