diff options
| author | manuel <manuel@mausz.at> | 2012-03-27 11:51:08 +0200 |
|---|---|---|
| committer | manuel <manuel@mausz.at> | 2012-03-27 11:51:08 +0200 |
| commit | 4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b (patch) | |
| tree | 868c52e06f207b5ec8a3cc141f4b8b2bdfcc165c /utils/pintos | |
| parent | eae0bd57f0a26314a94785061888d193d186944a (diff) | |
| download | progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.gz progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.bz2 progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.zip | |
reorganize file structure to match the upstream requirements
Diffstat (limited to 'utils/pintos')
| -rwxr-xr-x | utils/pintos | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/utils/pintos b/utils/pintos new file mode 100755 index 0000000..91f73ad --- /dev/null +++ b/utils/pintos | |||
| @@ -0,0 +1,955 @@ | |||
| 1 | #! /usr/bin/perl -w | ||
| 2 | |||
| 3 | use strict; | ||
| 4 | use POSIX; | ||
| 5 | use Fcntl; | ||
| 6 | use File::Temp 'tempfile'; | ||
| 7 | use Getopt::Long qw(:config bundling); | ||
| 8 | use Fcntl qw(SEEK_SET SEEK_CUR); | ||
| 9 | |||
| 10 | # Read Pintos.pm from the same directory as this program. | ||
| 11 | BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } | ||
| 12 | |||
| 13 | # Command-line options. | ||
| 14 | our ($start_time) = time (); | ||
| 15 | our ($sim); # Simulator: bochs, qemu, or player. | ||
| 16 | our ($debug) = "none"; # Debugger: none, monitor, or gdb. | ||
| 17 | our ($mem) = 4; # Physical RAM in MB. | ||
| 18 | our ($serial) = 1; # Use serial port for input and output? | ||
| 19 | our ($vga); # VGA output: window, terminal, or none. | ||
| 20 | our ($jitter); # Seed for random timer interrupts, if set. | ||
| 21 | our ($realtime); # Synchronize timer interrupts with real time? | ||
| 22 | our ($timeout); # Maximum runtime in seconds, if set. | ||
| 23 | our ($kill_on_failure); # Abort quickly on test failure? | ||
| 24 | our ($kernel_test); # Run kernel test instead of user program | ||
| 25 | our (@puts); # Files to copy into the VM. | ||
| 26 | our (@gets); # Files to copy out of the VM. | ||
| 27 | our ($as_ref); # Reference to last addition to @gets or @puts. | ||
| 28 | our (@kernel_args); # Arguments to pass to kernel. | ||
| 29 | our (%parts); # Partitions. | ||
| 30 | our ($make_disk); # Name of disk to create. | ||
| 31 | our ($tmp_disk) = 1; # Delete $make_disk after run? | ||
| 32 | our (@disks); # Extra disk images to pass to simulator. | ||
| 33 | our ($loader_fn); # Bootstrap loader. | ||
| 34 | our (%geometry); # IDE disk geometry. | ||
| 35 | our ($align); # Partition alignment. | ||
| 36 | |||
| 37 | parse_command_line (); | ||
| 38 | prepare_scratch_disk (); | ||
| 39 | find_disks (); | ||
| 40 | run_vm (); | ||
| 41 | finish_scratch_disk (); | ||
| 42 | |||
| 43 | exit 0; | ||
| 44 | |||
| 45 | # Parses the command line. | ||
| 46 | sub parse_command_line { | ||
| 47 | usage (0) if @ARGV == 0 || (@ARGV == 1 && $ARGV[0] eq '--help'); | ||
| 48 | |||
| 49 | @kernel_args = @ARGV; | ||
| 50 | if (grep ($_ eq '--', @kernel_args)) { | ||
| 51 | @ARGV = (); | ||
| 52 | while ((my $arg = shift (@kernel_args)) ne '--') { | ||
| 53 | push (@ARGV, $arg); | ||
| 54 | } | ||
| 55 | GetOptions ("sim=s" => sub { set_sim ($_[1]) }, | ||
| 56 | "bochs" => sub { set_sim ("bochs") }, | ||
| 57 | "qemu" => sub { set_sim ("qemu") }, | ||
| 58 | "player" => sub { set_sim ("player") }, | ||
| 59 | |||
| 60 | "debug=s" => sub { set_debug ($_[1]) }, | ||
| 61 | "no-debug" => sub { set_debug ("none") }, | ||
| 62 | "monitor" => sub { set_debug ("monitor") }, | ||
| 63 | "gdb" => sub { set_debug ("gdb") }, | ||
| 64 | |||
| 65 | "m|memory=i" => \$mem, | ||
| 66 | "j|jitter=i" => sub { set_jitter ($_[1]) }, | ||
| 67 | "r|realtime" => sub { set_realtime () }, | ||
| 68 | |||
| 69 | "T|timeout=i" => \$timeout, | ||
| 70 | "k|kill-on-failure" => \$kill_on_failure, | ||
| 71 | |||
| 72 | "v|no-vga" => sub { set_vga ('none'); }, | ||
| 73 | "s|no-serial" => sub { $serial = 0; }, | ||
| 74 | "t|terminal" => sub { set_vga ('terminal'); }, | ||
| 75 | |||
| 76 | "kernel-test" => sub { set_kernel_test(); }, | ||
| 77 | "p|put-file=s" => sub { add_file (\@puts, $_[1]); }, | ||
| 78 | "g|get-file=s" => sub { add_file (\@gets, $_[1]); }, | ||
| 79 | "a|as=s" => sub { set_as ($_[1]); }, | ||
| 80 | |||
| 81 | "h|help" => sub { usage (0); }, | ||
| 82 | |||
| 83 | "kernel=s" => \&set_part, | ||
| 84 | "filesys=s" => \&set_part, | ||
| 85 | "swap=s" => \&set_part, | ||
| 86 | |||
| 87 | "filesys-size=s" => \&set_part, | ||
| 88 | "scratch-size=s" => \&set_part, | ||
| 89 | "swap-size=s" => \&set_part, | ||
| 90 | |||
| 91 | "kernel-from=s" => \&set_part, | ||
| 92 | "filesys-from=s" => \&set_part, | ||
| 93 | "swap-from=s" => \&set_part, | ||
| 94 | |||
| 95 | "make-disk=s" => sub { $make_disk = $_[1]; | ||
| 96 | $tmp_disk = 0; }, | ||
| 97 | "disk=s" => sub { set_disk ($_[1]); }, | ||
| 98 | "loader=s" => \$loader_fn, | ||
| 99 | |||
| 100 | "geometry=s" => \&set_geometry, | ||
| 101 | "align=s" => \&set_align) | ||
| 102 | or exit 1; | ||
| 103 | } | ||
| 104 | |||
| 105 | $sim = "bochs" if !defined $sim; | ||
| 106 | $debug = "none" if !defined $debug; | ||
| 107 | $vga = exists ($ENV{DISPLAY}) ? "window" : "none" if !defined $vga; | ||
| 108 | |||
| 109 | undef $timeout, print "warning: disabling timeout with --$debug\n" | ||
| 110 | if defined ($timeout) && $debug ne 'none'; | ||
| 111 | |||
| 112 | print "warning: enabling serial port for -k or --kill-on-failure\n" | ||
| 113 | if $kill_on_failure && !$serial; | ||
| 114 | |||
| 115 | $align = "bochs", | ||
| 116 | print STDERR "warning: setting --align=bochs for Bochs support\n" | ||
| 117 | if $sim eq 'bochs' && defined ($align) && $align eq 'none'; | ||
| 118 | } | ||
| 119 | |||
| 120 | # usage($exitcode). | ||
| 121 | # Prints a usage message and exits with $exitcode. | ||
| 122 | sub usage { | ||
| 123 | my ($exitcode) = @_; | ||
| 124 | $exitcode = 1 unless defined $exitcode; | ||
| 125 | print <<'EOF'; | ||
| 126 | pintos, a utility for running Pintos in a simulator | ||
| 127 | Usage: pintos [OPTION...] -- [ARGUMENT...] | ||
| 128 | where each OPTION is one of the following options | ||
| 129 | and each ARGUMENT is passed to Pintos kernel verbatim. | ||
| 130 | Simulator selection: | ||
| 131 | --bochs (default) Use Bochs as simulator | ||
| 132 | --qemu Use QEMU as simulator | ||
| 133 | --player Use VMware Player as simulator | ||
| 134 | Debugger selection: | ||
| 135 | --no-debug (default) No debugger | ||
| 136 | --monitor Debug with simulator's monitor | ||
| 137 | --gdb Debug with gdb | ||
| 138 | Display options: (default is both VGA and serial) | ||
| 139 | -v, --no-vga No VGA display or keyboard | ||
| 140 | -s, --no-serial No serial input or output | ||
| 141 | -t, --terminal Display VGA in terminal (Bochs only) | ||
| 142 | Timing options: (Bochs only) | ||
| 143 | -j SEED Randomize timer interrupts | ||
| 144 | -r, --realtime Use realistic, not reproducible, timings | ||
| 145 | Testing options: | ||
| 146 | -T, --timeout=N Kill Pintos after N seconds CPU time or N*load_avg | ||
| 147 | seconds wall-clock time (whichever comes first) | ||
| 148 | -k, --kill-on-failure Kill Pintos a few seconds after a kernel or user | ||
| 149 | panic, test failure, or triple fault | ||
| 150 | --kernel-test Run kernel test, even though user programs are | ||
| 151 | enabled. | ||
| 152 | Configuration options: | ||
| 153 | -m, --mem=N Give Pintos N MB physical RAM (default: 4) | ||
| 154 | File system commands: | ||
| 155 | -p, --put-file=HOSTFN Copy HOSTFN into VM, by default under same name | ||
| 156 | -g, --get-file=GUESTFN Copy GUESTFN out of VM, by default under same name | ||
| 157 | -a, --as=FILENAME Specifies guest (for -p) or host (for -g) file name | ||
| 158 | Partition options: (where PARTITION is one of: kernel filesys scratch swap) | ||
| 159 | --PARTITION=FILE Use a copy of FILE for the given PARTITION | ||
| 160 | --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB | ||
| 161 | --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK | ||
| 162 | (There is no --kernel-size, --scratch, or --scratch-from option.) | ||
| 163 | Disk configuration options: | ||
| 164 | --make-disk=DISK Name the new DISK and don't delete it after the run | ||
| 165 | --disk=DISK Also use existing DISK (may be used multiple times) | ||
| 166 | Advanced disk configuration options: | ||
| 167 | --loader=FILE Use FILE as bootstrap loader (default: loader.bin) | ||
| 168 | --geometry=H,S Use H head, S sector geometry (default: 16,63) | ||
| 169 | --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot | ||
| 170 | (see http://syslinux.zytor.com/usbkey.php) | ||
| 171 | --align=bochs Pad out disk to cylinder to support Bochs (default) | ||
| 172 | --align=full Align partition boundaries to cylinder boundary to | ||
| 173 | let fdisk guess correct geometry and quiet warnings | ||
| 174 | --align=none Don't align partitions at all, to save space | ||
| 175 | Other options: | ||
| 176 | -h, --help Display this help message. | ||
| 177 | EOF | ||
| 178 | exit $exitcode; | ||
| 179 | } | ||
| 180 | |||
| 181 | # Sets the simulator. | ||
| 182 | sub set_sim { | ||
| 183 | my ($new_sim) = @_; | ||
| 184 | die "--$new_sim conflicts with --$sim\n" | ||
| 185 | if defined ($sim) && $sim ne $new_sim; | ||
| 186 | $sim = $new_sim; | ||
| 187 | } | ||
| 188 | |||
| 189 | # Sets the debugger. | ||
| 190 | sub set_debug { | ||
| 191 | my ($new_debug) = @_; | ||
| 192 | die "--$new_debug conflicts with --$debug\n" | ||
| 193 | if $debug ne 'none' && $new_debug ne 'none' && $debug ne $new_debug; | ||
| 194 | $debug = $new_debug; | ||
| 195 | } | ||
| 196 | |||
| 197 | # Sets VGA output destination. | ||
| 198 | sub set_vga { | ||
| 199 | my ($new_vga) = @_; | ||
| 200 | if (defined ($vga) && $vga ne $new_vga) { | ||
| 201 | print "warning: conflicting vga display options\n"; | ||
| 202 | } | ||
| 203 | $vga = $new_vga; | ||
| 204 | } | ||
| 205 | |||
| 206 | # Sets randomized timer interrupts. | ||
| 207 | sub set_jitter { | ||
| 208 | my ($new_jitter) = @_; | ||
| 209 | die "--realtime conflicts with --jitter\n" if defined $realtime; | ||
| 210 | die "different --jitter already defined\n" | ||
| 211 | if defined $jitter && $jitter != $new_jitter; | ||
| 212 | $jitter = $new_jitter; | ||
| 213 | } | ||
| 214 | |||
| 215 | # Sets real-time timer interrupts. | ||
| 216 | sub set_realtime { | ||
| 217 | die "--realtime conflicts with --jitter\n" if defined $jitter; | ||
| 218 | $realtime = 1; | ||
| 219 | } | ||
| 220 | |||
| 221 | # Sets load to run kernel test instead of user program. | ||
| 222 | # If user programs are disabled, pintos always runs a kernel test. | ||
| 223 | sub set_kernel_test { | ||
| 224 | $kernel_test = 1; | ||
| 225 | } | ||
| 226 | |||
| 227 | # add_file(\@list, $file) | ||
| 228 | # | ||
| 229 | # Adds [$file] to @list, which should be @puts or @gets. | ||
| 230 | # Sets $as_ref to point to the added element. | ||
| 231 | sub add_file { | ||
| 232 | my ($list, $file) = @_; | ||
| 233 | $as_ref = [$file]; | ||
| 234 | push (@$list, $as_ref); | ||
| 235 | } | ||
| 236 | |||
| 237 | # Sets the guest/host name for the previous put/get. | ||
| 238 | sub set_as { | ||
| 239 | my ($as) = @_; | ||
| 240 | die "-a (or --as) is only allowed after -p or -g\n" if !defined $as_ref; | ||
| 241 | die "Only one -a (or --as) is allowed after -p or -g\n" | ||
| 242 | if defined $as_ref->[1]; | ||
| 243 | $as_ref->[1] = $as; | ||
| 244 | } | ||
| 245 | |||
| 246 | # Sets $disk as a disk to be included in the VM to run. | ||
| 247 | sub set_disk { | ||
| 248 | my ($disk) = @_; | ||
| 249 | |||
| 250 | push (@disks, $disk); | ||
| 251 | |||
| 252 | my (%pt) = read_partition_table ($disk); | ||
| 253 | for my $role (keys %pt) { | ||
| 254 | die "can't have two sources for \L$role\E partition" | ||
| 255 | if exists $parts{$role}; | ||
| 256 | $parts{$role}{DISK} = $disk; | ||
| 257 | $parts{$role}{START} = $pt{$role}{START}; | ||
| 258 | $parts{$role}{SECTORS} = $pt{$role}{SECTORS}; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | # Locates the files used to back each of the virtual disks, | ||
| 263 | # and creates temporary disks. | ||
| 264 | sub find_disks { | ||
| 265 | # Find kernel, if we don't already have one. | ||
| 266 | if (!exists $parts{KERNEL}) { | ||
| 267 | my $name = find_file ('kernel.bin'); | ||
| 268 | die "Cannot find kernel\n" if !defined $name; | ||
| 269 | do_set_part ('KERNEL', 'file', $name); | ||
| 270 | } | ||
| 271 | |||
| 272 | # Try to find file system and swap disks, if we don't already have | ||
| 273 | # partitions. | ||
| 274 | if (!exists $parts{FILESYS}) { | ||
| 275 | my $name = find_file ('filesys.dsk'); | ||
| 276 | set_disk ($name) if defined $name; | ||
| 277 | } | ||
| 278 | if (!exists $parts{SWAP}) { | ||
| 279 | my $name = find_file ('swap.dsk'); | ||
| 280 | set_disk ($name) if defined $name; | ||
| 281 | } | ||
| 282 | |||
| 283 | # Warn about (potentially) missing partitions. | ||
| 284 | if (my ($project) = `pwd` =~ /\b(threads|userprog|vm|filesys)\b/) { | ||
| 285 | if ((grep ($project eq $_, qw (userprog vm filesys))) | ||
| 286 | && !defined $parts{FILESYS}) { | ||
| 287 | print STDERR "warning: it looks like you're running the $project "; | ||
| 288 | print STDERR "project, but no file system partition is present\n"; | ||
| 289 | } | ||
| 290 | if ($project eq 'vm' && !defined $parts{SWAP}) { | ||
| 291 | print STDERR "warning: it looks like you're running the $project "; | ||
| 292 | print STDERR "project, but no swap partition is present\n"; | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | # Open disk handle. | ||
| 297 | my ($handle); | ||
| 298 | if (!defined $make_disk) { | ||
| 299 | ($handle, $make_disk) = tempfile (UNLINK => $tmp_disk, | ||
| 300 | SUFFIX => '.dsk'); | ||
| 301 | } else { | ||
| 302 | die "$make_disk: already exists\n" if -e $make_disk; | ||
| 303 | open ($handle, '>', $make_disk) or die "$make_disk: create: $!\n"; | ||
| 304 | } | ||
| 305 | |||
| 306 | # Prepare the arguments to pass to the Pintos kernel. | ||
| 307 | my (@args); | ||
| 308 | push (@args, '-kernel-test') if $kernel_test; | ||
| 309 | push (@args, shift (@kernel_args)) | ||
| 310 | while @kernel_args && $kernel_args[0] =~ /^-/; | ||
| 311 | push (@args, 'extract') if @puts; | ||
| 312 | push (@args, @kernel_args); | ||
| 313 | push (@args, 'append', $_->[0]) foreach @gets; | ||
| 314 | |||
| 315 | # Make disk. | ||
| 316 | my (%disk); | ||
| 317 | our (@role_order); | ||
| 318 | for my $role (@role_order) { | ||
| 319 | my $p = $parts{$role}; | ||
| 320 | next if !defined $p; | ||
| 321 | next if exists $p->{DISK}; | ||
| 322 | $disk{$role} = $p; | ||
| 323 | } | ||
| 324 | $disk{DISK} = $make_disk; | ||
| 325 | $disk{HANDLE} = $handle; | ||
| 326 | $disk{ALIGN} = $align; | ||
| 327 | $disk{GEOMETRY} = %geometry; | ||
| 328 | $disk{FORMAT} = 'partitioned'; | ||
| 329 | $disk{LOADER} = read_loader ($loader_fn); | ||
| 330 | $disk{ARGS} = \@args; | ||
| 331 | assemble_disk (%disk); | ||
| 332 | |||
| 333 | # Put the disk at the front of the list of disks. | ||
| 334 | unshift (@disks, $make_disk); | ||
| 335 | die "can't use more than " . scalar (@disks) . "disks\n" if @disks > 4; | ||
| 336 | } | ||
| 337 | |||
| 338 | # Prepare the scratch disk for gets and puts. | ||
| 339 | sub prepare_scratch_disk { | ||
| 340 | return if !@gets && !@puts; | ||
| 341 | |||
| 342 | my ($p) = $parts{SCRATCH}; | ||
| 343 | # Create temporary partition and write the files to put to it, | ||
| 344 | # then write an end-of-archive marker. | ||
| 345 | my ($part_handle, $part_fn) = tempfile (UNLINK => 1, SUFFIX => '.part'); | ||
| 346 | put_scratch_file ($_->[0], defined $_->[1] ? $_->[1] : $_->[0], | ||
| 347 | $part_handle, $part_fn) | ||
| 348 | foreach @puts; | ||
| 349 | write_fully ($part_handle, $part_fn, "\0" x 1024); | ||
| 350 | |||
| 351 | # Make sure the scratch disk is big enough to get big files | ||
| 352 | # and at least as big as any requested size. | ||
| 353 | my ($size) = round_up (max (@gets * 1024 * 1024, $p->{BYTES} || 0), 512); | ||
| 354 | extend_file ($part_handle, $part_fn, $size); | ||
| 355 | close ($part_handle); | ||
| 356 | |||
| 357 | if (exists $p->{DISK}) { | ||
| 358 | # Copy the scratch partition to the disk. | ||
| 359 | die "$p->{DISK}: scratch partition too small\n" | ||
| 360 | if $p->{SECTORS} * 512 < $size; | ||
| 361 | |||
| 362 | my ($disk_handle); | ||
| 363 | open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; | ||
| 364 | open ($disk_handle, '+<', $p->{DISK}) or die "$p->{DISK}: open: $!\n"; | ||
| 365 | my ($start) = $p->{START} * 512; | ||
| 366 | sysseek ($disk_handle, $start, SEEK_SET) == $start | ||
| 367 | or die "$p->{DISK}: seek: $!\n"; | ||
| 368 | copy_file ($part_handle, $part_fn, $disk_handle, $p->{DISK}, $size); | ||
| 369 | close ($disk_handle) or die "$p->{DISK}: close: $!\n"; | ||
| 370 | close ($part_handle) or die "$part_fn: close: $!\n"; | ||
| 371 | } else { | ||
| 372 | # Set $part_fn as the source for the scratch partition. | ||
| 373 | do_set_part ('SCRATCH', 'file', $part_fn); | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | # Read "get" files from the scratch disk. | ||
| 378 | sub finish_scratch_disk { | ||
| 379 | return if !@gets; | ||
| 380 | |||
| 381 | # Open scratch partition. | ||
| 382 | my ($p) = $parts{SCRATCH}; | ||
| 383 | my ($part_handle); | ||
| 384 | my ($part_fn) = $p->{DISK}; | ||
| 385 | open ($part_handle, '<', $part_fn) or die "$part_fn: open: $!\n"; | ||
| 386 | sysseek ($part_handle, $p->{START} * 512, SEEK_SET) == $p->{START} * 512 | ||
| 387 | or die "$part_fn: seek: $!\n"; | ||
| 388 | |||
| 389 | # Read each file. | ||
| 390 | # If reading fails, delete that file and all subsequent files, but | ||
| 391 | # don't die with an error, because that's a guest error not a host | ||
| 392 | # error. (If we do exit with an error code, it fouls up the | ||
| 393 | # grading process.) Instead, just make sure that the host file(s) | ||
| 394 | # we were supposed to retrieve is unlinked. | ||
| 395 | my ($ok) = 1; | ||
| 396 | my ($part_end) = ($p->{START} + $p->{SECTORS}) * 512; | ||
| 397 | foreach my $get (@gets) { | ||
| 398 | my ($name) = defined ($get->[1]) ? $get->[1] : $get->[0]; | ||
| 399 | if ($ok) { | ||
| 400 | my ($error) = get_scratch_file ($name, $part_handle, $part_fn); | ||
| 401 | if (!$error && sysseek ($part_handle, 0, SEEK_CUR) > $part_end) { | ||
| 402 | $error = "$part_fn: scratch data overflows partition"; | ||
| 403 | } | ||
| 404 | if ($error) { | ||
| 405 | print STDERR "getting $name failed ($error)\n"; | ||
| 406 | $ok = 0; | ||
| 407 | } | ||
| 408 | } | ||
| 409 | die "$name: unlink: $!\n" if !$ok && !unlink ($name) && !$!{ENOENT}; | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | # mk_ustar_field($number, $size) | ||
| 414 | # | ||
| 415 | # Returns $number in a $size-byte numeric field in the format used by | ||
| 416 | # the standard ustar archive header. | ||
| 417 | sub mk_ustar_field { | ||
| 418 | my ($number, $size) = @_; | ||
| 419 | my ($len) = $size - 1; | ||
| 420 | my ($out) = sprintf ("%0${len}o", $number) . "\0"; | ||
| 421 | die "$number: too large for $size-byte octal ustar field\n" | ||
| 422 | if length ($out) != $size; | ||
| 423 | return $out; | ||
| 424 | } | ||
| 425 | |||
| 426 | # calc_ustar_chksum($s) | ||
| 427 | # | ||
| 428 | # Calculates and returns the ustar checksum of 512-byte ustar archive | ||
| 429 | # header $s. | ||
| 430 | sub calc_ustar_chksum { | ||
| 431 | my ($s) = @_; | ||
| 432 | die if length ($s) != 512; | ||
| 433 | substr ($s, 148, 8, ' ' x 8); | ||
| 434 | return unpack ("%32a*", $s); | ||
| 435 | } | ||
| 436 | |||
| 437 | # put_scratch_file($src_file_name, $dst_file_name, | ||
| 438 | # $disk_handle, $disk_file_name). | ||
| 439 | # | ||
| 440 | # Copies $src_file_name into $disk_handle for extraction as | ||
| 441 | # $dst_file_name. $disk_file_name is used for error messages. | ||
| 442 | sub put_scratch_file { | ||
| 443 | my ($src_file_name, $dst_file_name, $disk_handle, $disk_file_name) = @_; | ||
| 444 | |||
| 445 | print "Copying $src_file_name to scratch partition...\n"; | ||
| 446 | |||
| 447 | # ustar format supports up to 100 characters for a file name, and | ||
| 448 | # even longer names given some common properties, but our code in | ||
| 449 | # the Pintos kernel only supports at most 99 characters. | ||
| 450 | die "$dst_file_name: name too long (max 99 characters)\n" | ||
| 451 | if length ($dst_file_name) > 99; | ||
| 452 | |||
| 453 | # Compose and write ustar header. | ||
| 454 | stat $src_file_name or die "$src_file_name: stat: $!\n"; | ||
| 455 | my ($size) = -s _; | ||
| 456 | my ($header) = (pack ("a100", $dst_file_name) # name | ||
| 457 | . mk_ustar_field (0644, 8) # mode | ||
| 458 | . mk_ustar_field (0, 8) # uid | ||
| 459 | . mk_ustar_field (0, 8) # gid | ||
| 460 | . mk_ustar_field ($size, 12) # size | ||
| 461 | . mk_ustar_field (1136102400, 12) # mtime | ||
| 462 | . (' ' x 8) # chksum | ||
| 463 | . '0' # typeflag | ||
| 464 | . ("\0" x 100) # linkname | ||
| 465 | . "ustar\0" # magic | ||
| 466 | . "00" # version | ||
| 467 | . "root" . ("\0" x 28) # uname | ||
| 468 | . "root" . ("\0" x 28) # gname | ||
| 469 | . "\0" x 8 # devmajor | ||
| 470 | . "\0" x 8 # devminor | ||
| 471 | . ("\0" x 155)) # prefix | ||
| 472 | . "\0" x 12; # pad to 512 bytes | ||
| 473 | substr ($header, 148, 8) = mk_ustar_field (calc_ustar_chksum ($header), 8); | ||
| 474 | write_fully ($disk_handle, $disk_file_name, $header); | ||
| 475 | |||
| 476 | # Copy file data. | ||
| 477 | my ($put_handle); | ||
| 478 | sysopen ($put_handle, $src_file_name, O_RDONLY) | ||
| 479 | or die "$src_file_name: open: $!\n"; | ||
| 480 | copy_file ($put_handle, $src_file_name, $disk_handle, $disk_file_name, | ||
| 481 | $size); | ||
| 482 | die "$src_file_name: changed size while being read\n" | ||
| 483 | if $size != -s $put_handle; | ||
| 484 | close ($put_handle); | ||
| 485 | |||
| 486 | # Round up disk data to beginning of next sector. | ||
| 487 | write_fully ($disk_handle, $disk_file_name, "\0" x (512 - $size % 512)) | ||
| 488 | if $size % 512; | ||
| 489 | } | ||
| 490 | |||
| 491 | # get_scratch_file($get_file_name, $disk_handle, $disk_file_name) | ||
| 492 | # | ||
| 493 | # Copies from $disk_handle to $get_file_name (which is created). | ||
| 494 | # $disk_file_name is used for error messages. | ||
| 495 | # Returns 1 if successful, 0 on failure. | ||
| 496 | sub get_scratch_file { | ||
| 497 | my ($get_file_name, $disk_handle, $disk_file_name) = @_; | ||
| 498 | |||
| 499 | print "Copying $get_file_name out of $disk_file_name...\n"; | ||
| 500 | |||
| 501 | # Read ustar header sector. | ||
| 502 | my ($header) = read_fully ($disk_handle, $disk_file_name, 512); | ||
| 503 | return "scratch disk tar archive ends unexpectedly" | ||
| 504 | if $header eq ("\0" x 512); | ||
| 505 | |||
| 506 | # Verify magic numbers. | ||
| 507 | return "corrupt ustar signature" if substr ($header, 257, 6) ne "ustar\0"; | ||
| 508 | return "invalid ustar version" if substr ($header, 263, 2) ne '00'; | ||
| 509 | |||
| 510 | # Verify checksum. | ||
| 511 | my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8))); | ||
| 512 | my ($correct_chksum) = calc_ustar_chksum ($header); | ||
| 513 | return "checksum mismatch" if $chksum != $correct_chksum; | ||
| 514 | |||
| 515 | # Get type. | ||
| 516 | my ($typeflag) = substr ($header, 156, 1); | ||
| 517 | return "not a regular file" if $typeflag ne '0' && $typeflag ne "\0"; | ||
| 518 | |||
| 519 | # Get size. | ||
| 520 | my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); | ||
| 521 | return "bad size $size\n" if $size < 0; | ||
| 522 | |||
| 523 | # Copy file data. | ||
| 524 | my ($get_handle); | ||
| 525 | sysopen ($get_handle, $get_file_name, O_WRONLY | O_CREAT, 0666) | ||
| 526 | or die "$get_file_name: create: $!\n"; | ||
| 527 | copy_file ($disk_handle, $disk_file_name, $get_handle, $get_file_name, | ||
| 528 | $size); | ||
| 529 | close ($get_handle); | ||
| 530 | |||
| 531 | # Skip forward in disk up to beginning of next sector. | ||
| 532 | read_fully ($disk_handle, $disk_file_name, 512 - $size % 512) | ||
| 533 | if $size % 512; | ||
| 534 | |||
| 535 | return 0; | ||
| 536 | } | ||
| 537 | |||
| 538 | # Running simulators. | ||
| 539 | |||
| 540 | # Runs the selected simulator. | ||
| 541 | sub run_vm { | ||
| 542 | if ($sim eq 'bochs') { | ||
| 543 | run_bochs (); | ||
| 544 | } elsif ($sim eq 'qemu') { | ||
| 545 | run_qemu (); | ||
| 546 | } elsif ($sim eq 'player') { | ||
| 547 | run_player (); | ||
| 548 | } else { | ||
| 549 | die "unknown simulator `$sim'\n"; | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | # Runs Bochs. | ||
| 554 | sub run_bochs { | ||
| 555 | # Select Bochs binary based on the chosen debugger. | ||
| 556 | my ($bin) = $debug eq 'monitor' ? 'bochs-dbg' : 'bochs'; | ||
| 557 | |||
| 558 | my ($squish_pty); | ||
| 559 | if ($serial) { | ||
| 560 | $squish_pty = find_in_path ("squish-pty"); | ||
| 561 | print "warning: can't find squish-pty, so terminal input will fail\n" | ||
| 562 | if !defined $squish_pty; | ||
| 563 | } | ||
| 564 | |||
| 565 | # Write bochsrc.txt configuration file. | ||
| 566 | open (BOCHSRC, ">", "bochsrc.txt") or die "bochsrc.txt: create: $!\n"; | ||
| 567 | print BOCHSRC <<EOF; | ||
| 568 | romimage: file=\$BXSHARE/BIOS-bochs-latest | ||
| 569 | vgaromimage: file=\$BXSHARE/VGABIOS-lgpl-latest | ||
| 570 | boot: disk | ||
| 571 | cpu: ips=1000000 | ||
| 572 | megs: $mem | ||
| 573 | log: bochsout.txt | ||
| 574 | panic: action=fatal | ||
| 575 | user_shortcut: keys=ctrlaltdel | ||
| 576 | EOF | ||
| 577 | print BOCHSRC "gdbstub: enabled=1\n" if $debug eq 'gdb'; | ||
| 578 | print BOCHSRC "clock: sync=", $realtime ? 'realtime' : 'none', | ||
| 579 | ", time0=0\n"; | ||
| 580 | print BOCHSRC "ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15\n" | ||
| 581 | if @disks > 2; | ||
| 582 | print_bochs_disk_line ("ata0-master", $disks[0]); | ||
| 583 | print_bochs_disk_line ("ata0-slave", $disks[1]); | ||
| 584 | print_bochs_disk_line ("ata1-master", $disks[2]); | ||
| 585 | print_bochs_disk_line ("ata1-slave", $disks[3]); | ||
| 586 | if ($vga ne 'terminal') { | ||
| 587 | if ($serial) { | ||
| 588 | my $mode = defined ($squish_pty) ? "term" : "file"; | ||
| 589 | print BOCHSRC "com1: enabled=1, mode=$mode, dev=/dev/stdout\n"; | ||
| 590 | } | ||
| 591 | print BOCHSRC "display_library: nogui\n" if $vga eq 'none'; | ||
| 592 | } else { | ||
| 593 | print BOCHSRC "display_library: term\n"; | ||
| 594 | } | ||
| 595 | close (BOCHSRC); | ||
| 596 | |||
| 597 | # Compose Bochs command line. | ||
| 598 | my (@cmd) = ($bin, '-q'); | ||
| 599 | unshift (@cmd, $squish_pty) if defined $squish_pty; | ||
| 600 | push (@cmd, '-j', $jitter) if defined $jitter; | ||
| 601 | |||
| 602 | # Run Bochs. | ||
| 603 | print join (' ', @cmd), "\n"; | ||
| 604 | my ($exit) = xsystem (@cmd); | ||
| 605 | if (WIFEXITED ($exit)) { | ||
| 606 | # Bochs exited normally. | ||
| 607 | # Ignore the exit code; Bochs normally exits with status 1, | ||
| 608 | # which is weird. | ||
| 609 | } elsif (WIFSIGNALED ($exit)) { | ||
| 610 | die "Bochs died with signal ", WTERMSIG ($exit), "\n"; | ||
| 611 | } else { | ||
| 612 | die "Bochs died: code $exit\n"; | ||
| 613 | } | ||
| 614 | } | ||
| 615 | |||
| 616 | sub print_bochs_disk_line { | ||
| 617 | my ($device, $disk) = @_; | ||
| 618 | if (defined $disk) { | ||
| 619 | my (%geom) = disk_geometry ($disk); | ||
| 620 | print BOCHSRC "$device: type=disk, path=$disk, mode=flat, "; | ||
| 621 | print BOCHSRC "cylinders=$geom{C}, heads=$geom{H}, spt=$geom{S}, "; | ||
| 622 | print BOCHSRC "translation=none\n"; | ||
| 623 | } | ||
| 624 | } | ||
| 625 | |||
| 626 | # Runs QEMU. | ||
| 627 | sub run_qemu { | ||
| 628 | print "warning: qemu doesn't support --terminal\n" | ||
| 629 | if $vga eq 'terminal'; | ||
| 630 | print "warning: qemu doesn't support jitter\n" | ||
| 631 | if defined $jitter; | ||
| 632 | my (@cmd) = ('qemu'); | ||
| 633 | # push (@cmd, '-no-kqemu'); | ||
| 634 | push (@cmd, '-hda', $disks[0]) if defined $disks[0]; | ||
| 635 | push (@cmd, '-hdb', $disks[1]) if defined $disks[1]; | ||
| 636 | push (@cmd, '-hdc', $disks[2]) if defined $disks[2]; | ||
| 637 | push (@cmd, '-hdd', $disks[3]) if defined $disks[3]; | ||
| 638 | push (@cmd, '-m', $mem); | ||
| 639 | push (@cmd, '-net', 'none'); | ||
| 640 | push (@cmd, '-nographic') if $vga eq 'none'; | ||
| 641 | push (@cmd, '-serial', 'stdio') if $serial && $vga ne 'none'; | ||
| 642 | push (@cmd, '-S') if $debug eq 'monitor'; | ||
| 643 | push (@cmd, '-s', '-S') if $debug eq 'gdb'; | ||
| 644 | push (@cmd, '-monitor', 'null') if $vga eq 'none' && $debug eq 'none'; | ||
| 645 | run_command (@cmd); | ||
| 646 | } | ||
| 647 | |||
| 648 | # player_unsup($flag) | ||
| 649 | # | ||
| 650 | # Prints a message that $flag is unsupported by VMware Player. | ||
| 651 | sub player_unsup { | ||
| 652 | my ($flag) = @_; | ||
| 653 | print "warning: no support for $flag with VMware Player\n"; | ||
| 654 | } | ||
| 655 | |||
| 656 | # Runs VMware Player. | ||
| 657 | sub run_player { | ||
| 658 | player_unsup ("--$debug") if $debug ne 'none'; | ||
| 659 | player_unsup ("--no-vga") if $vga eq 'none'; | ||
| 660 | player_unsup ("--terminal") if $vga eq 'terminal'; | ||
| 661 | player_unsup ("--jitter") if defined $jitter; | ||
| 662 | player_unsup ("--timeout"), undef $timeout if defined $timeout; | ||
| 663 | player_unsup ("--kill-on-failure"), undef $kill_on_failure | ||
| 664 | if defined $kill_on_failure; | ||
| 665 | |||
| 666 | $mem = round_up ($mem, 4); # Memory must be multiple of 4 MB. | ||
| 667 | |||
| 668 | open (VMX, ">", "pintos.vmx") or die "pintos.vmx: create: $!\n"; | ||
| 669 | chmod 0777 & ~umask, "pintos.vmx"; | ||
| 670 | print VMX <<EOF; | ||
| 671 | #! /usr/bin/vmware -G | ||
| 672 | config.version = 8 | ||
| 673 | guestOS = "linux" | ||
| 674 | memsize = $mem | ||
| 675 | floppy0.present = FALSE | ||
| 676 | usb.present = FALSE | ||
| 677 | sound.present = FALSE | ||
| 678 | gui.exitAtPowerOff = TRUE | ||
| 679 | gui.exitOnCLIHLT = TRUE | ||
| 680 | gui.powerOnAtStartUp = TRUE | ||
| 681 | EOF | ||
| 682 | |||
| 683 | print VMX <<EOF if $serial; | ||
| 684 | serial0.present = TRUE | ||
| 685 | serial0.fileType = "pipe" | ||
| 686 | serial0.fileName = "pintos.socket" | ||
| 687 | serial0.pipe.endPoint = "client" | ||
| 688 | serial0.tryNoRxLoss = "TRUE" | ||
| 689 | EOF | ||
| 690 | |||
| 691 | for (my ($i) = 0; $i < 4; $i++) { | ||
| 692 | my ($dsk) = $disks[$i]; | ||
| 693 | last if !defined $dsk; | ||
| 694 | |||
| 695 | my ($device) = "ide" . int ($i / 2) . ":" . ($i % 2); | ||
| 696 | my ($pln) = "$device.pln"; | ||
| 697 | print VMX <<EOF; | ||
| 698 | |||
| 699 | $device.present = TRUE | ||
| 700 | $device.deviceType = "plainDisk" | ||
| 701 | $device.fileName = "$pln" | ||
| 702 | EOF | ||
| 703 | |||
| 704 | open (URANDOM, '<', '/dev/urandom') or die "/dev/urandom: open: $!\n"; | ||
| 705 | my ($bytes); | ||
| 706 | sysread (URANDOM, $bytes, 4) == 4 or die "/dev/urandom: read: $!\n"; | ||
| 707 | close (URANDOM); | ||
| 708 | my ($cid) = unpack ("L", $bytes); | ||
| 709 | |||
| 710 | my (%geom) = disk_geometry ($dsk); | ||
| 711 | open (PLN, ">", $pln) or die "$pln: create: $!\n"; | ||
| 712 | print PLN <<EOF; | ||
| 713 | version=1 | ||
| 714 | CID=$cid | ||
| 715 | parentCID=ffffffff | ||
| 716 | createType="monolithicFlat" | ||
| 717 | |||
| 718 | RW $geom{CAPACITY} FLAT "$dsk" 0 | ||
| 719 | |||
| 720 | # The Disk Data Base | ||
| 721 | #DDB | ||
| 722 | |||
| 723 | ddb.adapterType = "ide" | ||
| 724 | ddb.virtualHWVersion = "4" | ||
| 725 | ddb.toolsVersion = "2" | ||
| 726 | ddb.geometry.cylinders = "$geom{C}" | ||
| 727 | ddb.geometry.heads = "$geom{H}" | ||
| 728 | ddb.geometry.sectors = "$geom{S}" | ||
| 729 | EOF | ||
| 730 | close (PLN); | ||
| 731 | } | ||
| 732 | close (VMX); | ||
| 733 | |||
| 734 | my ($squish_unix); | ||
| 735 | if ($serial) { | ||
| 736 | $squish_unix = find_in_path ("squish-unix"); | ||
| 737 | print "warning: can't find squish-unix, so terminal input ", | ||
| 738 | "and output will fail\n" if !defined $squish_unix; | ||
| 739 | } | ||
| 740 | |||
| 741 | my ($vmx) = getcwd () . "/pintos.vmx"; | ||
| 742 | my (@cmd) = ("vmplayer", $vmx); | ||
| 743 | unshift (@cmd, $squish_unix, "pintos.socket") if $squish_unix; | ||
| 744 | print join (' ', @cmd), "\n"; | ||
| 745 | xsystem (@cmd); | ||
| 746 | } | ||
| 747 | |||
| 748 | # Disk utilities. | ||
| 749 | |||
| 750 | sub extend_file { | ||
| 751 | my ($handle, $file_name, $size) = @_; | ||
| 752 | if (-s ($handle) < $size) { | ||
| 753 | sysseek ($handle, $size - 1, 0) == $size - 1 | ||
| 754 | or die "$file_name: seek: $!\n"; | ||
| 755 | syswrite ($handle, "\0") == 1 | ||
| 756 | or die "$file_name: write: $!\n"; | ||
| 757 | } | ||
| 758 | } | ||
| 759 | |||
| 760 | # disk_geometry($file) | ||
| 761 | # | ||
| 762 | # Examines $file and returns a valid IDE disk geometry for it, as a | ||
| 763 | # hash. | ||
| 764 | sub disk_geometry { | ||
| 765 | my ($file) = @_; | ||
| 766 | my ($size) = -s $file; | ||
| 767 | die "$file: stat: $!\n" if !defined $size; | ||
| 768 | die "$file: size $size not a multiple of 512 bytes\n" if $size % 512; | ||
| 769 | my ($cyl_size) = 512 * 16 * 63; | ||
| 770 | my ($cylinders) = ceil ($size / $cyl_size); | ||
| 771 | |||
| 772 | return (CAPACITY => $size / 512, | ||
| 773 | C => $cylinders, | ||
| 774 | H => 16, | ||
| 775 | S => 63); | ||
| 776 | } | ||
| 777 | |||
| 778 | # Subprocess utilities. | ||
| 779 | |||
| 780 | # run_command(@args) | ||
| 781 | # | ||
| 782 | # Runs xsystem(@args). | ||
| 783 | # Also prints the command it's running and checks that it succeeded. | ||
| 784 | sub run_command { | ||
| 785 | print join (' ', @_), "\n"; | ||
| 786 | die "command failed\n" if xsystem (@_); | ||
| 787 | } | ||
| 788 | |||
| 789 | # xsystem(@args) | ||
| 790 | # | ||
| 791 | # Creates a subprocess via exec(@args) and waits for it to complete. | ||
| 792 | # Relays common signals to the subprocess. | ||
| 793 | # If $timeout is set then the subprocess will be killed after that long. | ||
| 794 | sub xsystem { | ||
| 795 | # QEMU turns off local echo and does not restore it if killed by a signal. | ||
| 796 | # We compensate by restoring it ourselves. | ||
| 797 | my $cleanup = sub {}; | ||
| 798 | if (isatty (0)) { | ||
| 799 | my $termios = POSIX::Termios->new; | ||
| 800 | $termios->getattr (0); | ||
| 801 | $cleanup = sub { $termios->setattr (0, &POSIX::TCSANOW); } | ||
| 802 | } | ||
| 803 | |||
| 804 | # Create pipe for filtering output. | ||
| 805 | pipe (my $in, my $out) or die "pipe: $!\n" if $kill_on_failure; | ||
| 806 | |||
| 807 | my ($pid) = fork; | ||
| 808 | if (!defined ($pid)) { | ||
| 809 | # Fork failed. | ||
| 810 | die "fork: $!\n"; | ||
| 811 | } elsif (!$pid) { | ||
| 812 | # Running in child process. | ||
| 813 | dup2 (fileno ($out), STDOUT_FILENO) or die "dup2: $!\n" | ||
| 814 | if $kill_on_failure; | ||
| 815 | exec_setitimer (@_); | ||
| 816 | } else { | ||
| 817 | # Running in parent process. | ||
| 818 | close $out if $kill_on_failure; | ||
| 819 | |||
| 820 | my ($cause); | ||
| 821 | local $SIG{ALRM} = sub { timeout ($pid, $cause, $cleanup); }; | ||
| 822 | local $SIG{INT} = sub { relay_signal ($pid, "INT", $cleanup); }; | ||
| 823 | local $SIG{TERM} = sub { relay_signal ($pid, "TERM", $cleanup); }; | ||
| 824 | alarm ($timeout * get_load_average () + 1) if defined ($timeout); | ||
| 825 | |||
| 826 | if ($kill_on_failure) { | ||
| 827 | # Filter output. | ||
| 828 | my ($buf) = ""; | ||
| 829 | my ($boots) = 0; | ||
| 830 | local ($|) = 1; | ||
| 831 | for (;;) { | ||
| 832 | if (waitpid ($pid, WNOHANG) != 0) { | ||
| 833 | # Subprocess died. Pass through any remaining data. | ||
| 834 | print $buf while sysread ($in, $buf, 4096) > 0; | ||
| 835 | last; | ||
| 836 | } | ||
| 837 | |||
| 838 | # Read and print out pipe data. | ||
| 839 | my ($len) = length ($buf); | ||
| 840 | waitpid ($pid, 0), last | ||
| 841 | if sysread ($in, $buf, 4096, $len) <= 0; | ||
| 842 | print substr ($buf, $len); | ||
| 843 | |||
| 844 | # Remove full lines from $buf and scan them for keywords. | ||
| 845 | while ((my $idx = index ($buf, "\n")) >= 0) { | ||
| 846 | local $_ = substr ($buf, 0, $idx + 1, ''); | ||
| 847 | next if defined ($cause); | ||
| 848 | if (/(Kernel PANIC|User process ABORT)/ ) { | ||
| 849 | $cause = "\L$1\E"; | ||
| 850 | alarm (5); | ||
| 851 | } elsif (/Pintos booting/ && ++$boots > 1) { | ||
| 852 | $cause = "triple fault"; | ||
| 853 | alarm (5); | ||
| 854 | } elsif (/FAILED/) { | ||
| 855 | $cause = "test failure"; | ||
| 856 | alarm (5); | ||
| 857 | } | ||
| 858 | } | ||
| 859 | } | ||
| 860 | } else { | ||
| 861 | waitpid ($pid, 0); | ||
| 862 | } | ||
| 863 | alarm (0); | ||
| 864 | &$cleanup (); | ||
| 865 | |||
| 866 | if (WIFSIGNALED ($?) && WTERMSIG ($?) == SIGVTALRM ()) { | ||
| 867 | seek (STDOUT, 0, 2); | ||
| 868 | print "\nTIMEOUT after $timeout seconds of host CPU time\n"; | ||
| 869 | exit 0; | ||
| 870 | } | ||
| 871 | |||
| 872 | return $?; | ||
| 873 | } | ||
| 874 | } | ||
| 875 | |||
| 876 | # relay_signal($pid, $signal, &$cleanup) | ||
| 877 | # | ||
| 878 | # Relays $signal to $pid and then reinvokes it for us with the default | ||
| 879 | # handler. Also cleans up temporary files and invokes $cleanup. | ||
| 880 | sub relay_signal { | ||
| 881 | my ($pid, $signal, $cleanup) = @_; | ||
| 882 | kill $signal, $pid; | ||
| 883 | eval { File::Temp::cleanup() }; # Not defined in old File::Temp. | ||
| 884 | &$cleanup (); | ||
| 885 | $SIG{$signal} = 'DEFAULT'; | ||
| 886 | kill $signal, getpid (); | ||
| 887 | } | ||
| 888 | |||
| 889 | # timeout($pid, $cause, &$cleanup) | ||
| 890 | # | ||
| 891 | # Interrupts $pid and dies with a timeout error message, | ||
| 892 | # after invoking $cleanup. | ||
| 893 | sub timeout { | ||
| 894 | my ($pid, $cause, $cleanup) = @_; | ||
| 895 | kill "INT", $pid; | ||
| 896 | waitpid ($pid, 0); | ||
| 897 | &$cleanup (); | ||
| 898 | seek (STDOUT, 0, 2); | ||
| 899 | if (!defined ($cause)) { | ||
| 900 | my ($load_avg) = `uptime` =~ /(load average:.*)$/i; | ||
| 901 | print "\nTIMEOUT after ", time () - $start_time, | ||
| 902 | " seconds of wall-clock time"; | ||
| 903 | print " - $load_avg" if defined $load_avg; | ||
| 904 | print "\n"; | ||
| 905 | } else { | ||
| 906 | print "Simulation terminated due to $cause.\n"; | ||
| 907 | } | ||
| 908 | exit 0; | ||
| 909 | } | ||
| 910 | |||
| 911 | # Returns the system load average over the last minute. | ||
| 912 | # If the load average is less than 1.0 or cannot be determined, returns 1.0. | ||
| 913 | sub get_load_average { | ||
| 914 | my ($avg) = `uptime` =~ /load average:\s*([^,]+),/; | ||
| 915 | return $avg >= 1.0 ? $avg : 1.0; | ||
| 916 | } | ||
| 917 | |||
| 918 | # Calls setitimer to set a timeout, then execs what was passed to us. | ||
| 919 | sub exec_setitimer { | ||
| 920 | if (defined $timeout) { | ||
| 921 | if ($ ge 5.8.0) { | ||
| 922 | eval " | ||
| 923 | use Time::HiRes qw(setitimer ITIMER_VIRTUAL); | ||
| 924 | setitimer (ITIMER_VIRTUAL, $timeout, 0); | ||
| 925 | "; | ||
| 926 | } else { | ||
| 927 | { exec ("setitimer-helper", $timeout, @_); }; | ||
| 928 | exit 1 if !$!{ENOENT}; | ||
| 929 | print STDERR "warning: setitimer-helper is not installed, so ", | ||
| 930 | "CPU time limit will not be enforced\n"; | ||
| 931 | } | ||
| 932 | } | ||
| 933 | exec (@_); | ||
| 934 | exit (1); | ||
| 935 | } | ||
| 936 | |||
| 937 | sub SIGVTALRM { | ||
| 938 | use Config; | ||
| 939 | my $i = 0; | ||
| 940 | foreach my $name (split(' ', $Config{sig_name})) { | ||
| 941 | return $i if $name eq 'VTALRM'; | ||
| 942 | $i++; | ||
| 943 | } | ||
| 944 | return 0; | ||
| 945 | } | ||
| 946 | |||
| 947 | # find_in_path ($program) | ||
| 948 | # | ||
| 949 | # Searches for $program in $ENV{PATH}. | ||
| 950 | # Returns $program if found, otherwise undef. | ||
| 951 | sub find_in_path { | ||
| 952 | my ($program) = @_; | ||
| 953 | -x "$_/$program" and return $program foreach split (':', $ENV{PATH}); | ||
| 954 | return; | ||
| 955 | } | ||
