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 | |
| 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')
| -rw-r--r-- | utils/.gitignore | 3 | ||||
| -rw-r--r-- | utils/Makefile | 11 | ||||
| -rw-r--r-- | utils/Pintos.pm | 491 | ||||
| -rwxr-xr-x | utils/backtrace | 106 | ||||
| -rwxr-xr-x | utils/pintos | 955 | ||||
| -rwxr-xr-x | utils/pintos-gdb | 21 | ||||
| -rwxr-xr-x | utils/pintos-mkdisk | 134 | ||||
| -rw-r--r-- | utils/pintos-set-cmdline | 42 | ||||
| -rw-r--r-- | utils/setitimer-helper.c | 49 | ||||
| -rw-r--r-- | utils/squish-pty.c | 355 | ||||
| -rw-r--r-- | utils/squish-unix.c | 338 |
11 files changed, 2505 insertions, 0 deletions
diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 0000000..b96f278 --- /dev/null +++ b/utils/.gitignore | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | setitimer-helper | ||
| 2 | squish-pty | ||
| 3 | squish-unix | ||
diff --git a/utils/Makefile b/utils/Makefile new file mode 100644 index 0000000..46a9124 --- /dev/null +++ b/utils/Makefile | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | all: setitimer-helper squish-pty squish-unix | ||
| 2 | |||
| 3 | CC = gcc | ||
| 4 | CFLAGS = -Wall -W | ||
| 5 | LDFLAGS = -lm | ||
| 6 | setitimer-helper: setitimer-helper.o | ||
| 7 | squish-pty: squish-pty.o | ||
| 8 | squish-unix: squish-unix.o | ||
| 9 | |||
| 10 | clean: | ||
| 11 | rm -f *.o setitimer-helper squish-pty squish-unix | ||
diff --git a/utils/Pintos.pm b/utils/Pintos.pm new file mode 100644 index 0000000..70df40d --- /dev/null +++ b/utils/Pintos.pm | |||
| @@ -0,0 +1,491 @@ | |||
| 1 | # Pintos helper subroutines. | ||
| 2 | |||
| 3 | # Number of bytes available for the loader at the beginning of the MBR. | ||
| 4 | # Kernel command-line arguments follow the loader. | ||
| 5 | our $LOADER_SIZE = 314; | ||
| 6 | |||
| 7 | # Partition types. | ||
| 8 | my (%role2type) = (KERNEL => 0x20, | ||
| 9 | FILESYS => 0x21, | ||
| 10 | SCRATCH => 0x22, | ||
| 11 | SWAP => 0x23); | ||
| 12 | my (%type2role) = reverse %role2type; | ||
| 13 | |||
| 14 | # Order of roles within a given disk. | ||
| 15 | our (@role_order) = qw (KERNEL FILESYS SCRATCH SWAP); | ||
| 16 | |||
| 17 | # Partitions. | ||
| 18 | # | ||
| 19 | # Valid keys are KERNEL, FILESYS, SCRATCH, SWAP. Only those | ||
| 20 | # partitions which are in use are included. | ||
| 21 | # | ||
| 22 | # Each value is a reference to a hash. If the partition's contents | ||
| 23 | # are to be obtained from a file (that will be copied into a new | ||
| 24 | # virtual disk), then the hash contains: | ||
| 25 | # | ||
| 26 | # FILE => name of file from which the partition's contents are copied | ||
| 27 | # (perhaps "/dev/zero"), | ||
| 28 | # OFFSET => offset in bytes in FILE, | ||
| 29 | # BYTES => size in bytes of contents from FILE, | ||
| 30 | # | ||
| 31 | # If the partition is taken from a virtual disk directly, then it | ||
| 32 | # contains the following. The same keys are also filled in once a | ||
| 33 | # file-based partition has been copied into a new virtual disk: | ||
| 34 | # | ||
| 35 | # DISK => name of virtual disk file, | ||
| 36 | # START => sector offset of start of partition within DISK, | ||
| 37 | # SECTORS => number of sectors of partition within DISK, which is usually | ||
| 38 | # greater than round_up (BYTES, 512) due to padding. | ||
| 39 | our (%parts); | ||
| 40 | |||
| 41 | # set_part($opt, $arg) | ||
| 42 | # | ||
| 43 | # For use as a helper function for Getopt::Long::GetOptions to set | ||
| 44 | # disk sources. | ||
| 45 | sub set_part { | ||
| 46 | my ($opt, $arg) = @_; | ||
| 47 | my ($role, $source) = $opt =~ /^([a-z]+)(?:-([a-z]+))?/ or die; | ||
| 48 | |||
| 49 | $role = uc $role; | ||
| 50 | $source = 'FILE' if $source eq ''; | ||
| 51 | |||
| 52 | die "can't have two sources for \L$role\E partition" | ||
| 53 | if exists $parts{$role}; | ||
| 54 | |||
| 55 | do_set_part ($role, $source, $arg); | ||
| 56 | } | ||
| 57 | |||
| 58 | # do_set_part($role, $source, $arg) | ||
| 59 | # | ||
| 60 | # Sets partition $role as coming from $source (one of 'file', 'from', | ||
| 61 | # or 'size'). $arg is a file name for 'file' or 'from', a size in | ||
| 62 | # megabytes for 'size'. | ||
| 63 | sub do_set_part { | ||
| 64 | my ($role, $source, $arg) = @_; | ||
| 65 | |||
| 66 | my ($p) = $parts{$role} = {}; | ||
| 67 | if ($source eq 'file') { | ||
| 68 | if (read_mbr ($arg)) { | ||
| 69 | print STDERR "warning: $arg looks like a partitioned disk "; | ||
| 70 | print STDERR "(did you want --$role-from=$arg or --disk=$arg?)\n" | ||
| 71 | } | ||
| 72 | |||
| 73 | $p->{FILE} = $arg; | ||
| 74 | $p->{OFFSET} = 0; | ||
| 75 | $p->{BYTES} = -s $arg; | ||
| 76 | } elsif ($source eq 'from') { | ||
| 77 | my (%pt) = read_partition_table ($arg); | ||
| 78 | my ($sp) = $pt{$role}; | ||
| 79 | die "$arg: does not contain \L$role\E partition\n" if !defined $sp; | ||
| 80 | |||
| 81 | $p->{FILE} = $arg; | ||
| 82 | $p->{OFFSET} = $sp->{START} * 512; | ||
| 83 | $p->{BYTES} = $sp->{SECTORS} * 512; | ||
| 84 | } elsif ($source eq 'size') { | ||
| 85 | $arg =~ /^\d+(\.\d+)?|\.\d+$/ or die "$arg: not a valid size in MB\n"; | ||
| 86 | |||
| 87 | $p->{FILE} = "/dev/zero"; | ||
| 88 | $p->{OFFSET} = 0; | ||
| 89 | $p->{BYTES} = ceil ($arg * 1024 * 1024); | ||
| 90 | } else { | ||
| 91 | die; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | # set_geometry('HEADS,SPT') | ||
| 96 | # set_geometry('zip') | ||
| 97 | # | ||
| 98 | # For use as a helper function for Getopt::Long::GetOptions to set | ||
| 99 | # disk geometry. | ||
| 100 | sub set_geometry { | ||
| 101 | local ($_) = $_[1]; | ||
| 102 | if ($_ eq 'zip') { | ||
| 103 | @geometry{'H', 'S'} = (64, 32); | ||
| 104 | } else { | ||
| 105 | @geometry{'H', 'S'} = /^(\d+)[,\s]+(\d+)$/ | ||
| 106 | or die "bad syntax for geometry\n"; | ||
| 107 | $geometry{H} <= 255 or die "heads limited to 255\n"; | ||
| 108 | $geometry{S} <= 63 or die "sectors per track limited to 63\n"; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | # set_align('bochs|full|none') | ||
| 113 | # | ||
| 114 | # For use as a helper function for Getopt::Long::GetOptions to set | ||
| 115 | # partition alignment. | ||
| 116 | sub set_align { | ||
| 117 | $align = $_[1]; | ||
| 118 | die "unknown alignment type \"$align\"\n" | ||
| 119 | if $align ne 'bochs' && $align ne 'full' && $align ne 'none'; | ||
| 120 | } | ||
| 121 | |||
| 122 | # assemble_disk(%args) | ||
| 123 | # | ||
| 124 | # Creates a virtual disk $args{DISK} containing the partitions | ||
| 125 | # described by @args{KERNEL, FILESYS, SCRATCH, SWAP}. | ||
| 126 | # | ||
| 127 | # Required arguments: | ||
| 128 | # DISK => output disk file name | ||
| 129 | # HANDLE => output file handle (will be closed) | ||
| 130 | # | ||
| 131 | # Normally at least one of the following is included: | ||
| 132 | # KERNEL, FILESYS, SCRATCH, SWAP => {input: | ||
| 133 | # FILE => file to read, | ||
| 134 | # OFFSET => byte offset in file, | ||
| 135 | # BYTES => byte count from file, | ||
| 136 | # | ||
| 137 | # output: | ||
| 138 | # DISK => output disk file name, | ||
| 139 | # START => sector offset in DISK, | ||
| 140 | # SECTORS => sector count in DISK}, | ||
| 141 | # | ||
| 142 | # Optional arguments: | ||
| 143 | # ALIGN => 'bochs' (default), 'full', or 'none' | ||
| 144 | # GEOMETRY => {H => heads, S => sectors per track} (default 16, 63) | ||
| 145 | # FORMAT => 'partitioned' (default) or 'raw' | ||
| 146 | # LOADER => $LOADER_SIZE-byte string containing the loader binary | ||
| 147 | # ARGS => ['arg 1', 'arg 2', ...] | ||
| 148 | sub assemble_disk { | ||
| 149 | my (%args) = @_; | ||
| 150 | |||
| 151 | my (%geometry) = $args{GEOMETRY} || (H => 16, S => 63); | ||
| 152 | |||
| 153 | my ($align); # Align partition start, end to cylinder boundary? | ||
| 154 | my ($pad); # Pad end of disk out to cylinder boundary? | ||
| 155 | if (!defined ($args{ALIGN}) || $args{ALIGN} eq 'bochs') { | ||
| 156 | $align = 0; | ||
| 157 | $pad = 1; | ||
| 158 | } elsif ($args{ALIGN} eq 'full') { | ||
| 159 | $align = 1; | ||
| 160 | $pad = 0; | ||
| 161 | } elsif ($args{ALIGN} eq 'none') { | ||
| 162 | $align = $pad = 0; | ||
| 163 | } else { | ||
| 164 | die; | ||
| 165 | } | ||
| 166 | |||
| 167 | my ($format) = $args{FORMAT} || 'partitioned'; | ||
| 168 | die if $format ne 'partitioned' && $format ne 'raw'; | ||
| 169 | |||
| 170 | # Check that we have apartitions to copy in. | ||
| 171 | my $part_cnt = grep (defined ($args{$_}), keys %role2type); | ||
| 172 | die "must have exactly one partition for raw output\n" | ||
| 173 | if $format eq 'raw' && $part_cnt != 1; | ||
| 174 | |||
| 175 | # Calculate the disk size. | ||
| 176 | my ($total_sectors) = 0; | ||
| 177 | if ($format eq 'partitioned') { | ||
| 178 | $total_sectors += $align ? $geometry{S} : 1; | ||
| 179 | } | ||
| 180 | for my $role (@role_order) { | ||
| 181 | my ($p) = $args{$role}; | ||
| 182 | next if !defined $p; | ||
| 183 | |||
| 184 | die if $p->{DISK}; | ||
| 185 | |||
| 186 | my ($bytes) = $p->{BYTES}; | ||
| 187 | my ($start) = $total_sectors; | ||
| 188 | my ($end) = $start + div_round_up ($bytes, 512); | ||
| 189 | $end = round_up ($end, cyl_sectors (%geometry)) if $align; | ||
| 190 | |||
| 191 | $p->{DISK} = $args{DISK}; | ||
| 192 | $p->{START} = $start; | ||
| 193 | $p->{SECTORS} = $end - $start; | ||
| 194 | $total_sectors = $end; | ||
| 195 | } | ||
| 196 | |||
| 197 | # Write the disk. | ||
| 198 | my ($disk_fn) = $args{DISK}; | ||
| 199 | my ($disk) = $args{HANDLE}; | ||
| 200 | if ($format eq 'partitioned') { | ||
| 201 | # Pack loader into MBR. | ||
| 202 | my ($loader) = $args{LOADER} || "\xcd\x18"; | ||
| 203 | my ($mbr) = pack ("a$LOADER_SIZE", $loader); | ||
| 204 | |||
| 205 | $mbr .= make_kernel_command_line (@{$args{ARGS}}); | ||
| 206 | |||
| 207 | # Pack partition table into MBR. | ||
| 208 | $mbr .= make_partition_table (\%geometry, \%args); | ||
| 209 | |||
| 210 | # Add signature to MBR. | ||
| 211 | $mbr .= pack ("v", 0xaa55); | ||
| 212 | |||
| 213 | die if length ($mbr) != 512; | ||
| 214 | write_fully ($disk, $disk_fn, $mbr); | ||
| 215 | write_zeros ($disk, $disk_fn, 512 * ($geometry{S} - 1)) if $align; | ||
| 216 | } | ||
| 217 | for my $role (@role_order) { | ||
| 218 | my ($p) = $args{$role}; | ||
| 219 | next if !defined $p; | ||
| 220 | |||
| 221 | my ($source); | ||
| 222 | my ($fn) = $p->{FILE}; | ||
| 223 | open ($source, '<', $fn) or die "$fn: open: $!\n"; | ||
| 224 | if ($p->{OFFSET}) { | ||
| 225 | sysseek ($source, $p->{OFFSET}, 0) == $p->{OFFSET} | ||
| 226 | or die "$fn: seek: $!\n"; | ||
| 227 | } | ||
| 228 | copy_file ($source, $fn, $disk, $disk_fn, $p->{BYTES}); | ||
| 229 | close ($source) or die "$fn: close: $!\n"; | ||
| 230 | |||
| 231 | write_zeros ($disk, $disk_fn, $p->{SECTORS} * 512 - $p->{BYTES}); | ||
| 232 | } | ||
| 233 | if ($pad) { | ||
| 234 | my ($pad_sectors) = round_up ($total_sectors, cyl_sectors (%geometry)); | ||
| 235 | write_zeros ($disk, $disk_fn, ($pad_sectors - $total_sectors) * 512); | ||
| 236 | } | ||
| 237 | close ($disk) or die "$disk: close: $!\n"; | ||
| 238 | } | ||
| 239 | |||
| 240 | # make_partition_table({H => heads, S => sectors}, {KERNEL => ..., ...}) | ||
| 241 | # | ||
| 242 | # Creates and returns a partition table for the given partitions and | ||
| 243 | # disk geometry. | ||
| 244 | sub make_partition_table { | ||
| 245 | my ($geometry, $partitions) = @_; | ||
| 246 | my ($table) = ''; | ||
| 247 | for my $role (@role_order) { | ||
| 248 | defined (my $p = $partitions->{$role}) or next; | ||
| 249 | |||
| 250 | my $end = $p->{START} + $p->{SECTORS} - 1; | ||
| 251 | my $bootable = $role eq 'KERNEL'; | ||
| 252 | |||
| 253 | $table .= pack ("C", $bootable ? 0x80 : 0); # Bootable? | ||
| 254 | $table .= pack_chs ($p->{START}, $geometry); # CHS of partition start | ||
| 255 | $table .= pack ("C", $role2type{$role}); # Partition type | ||
| 256 | $table .= pack_chs($end, $geometry); # CHS of partition end | ||
| 257 | $table .= pack ("V", $p->{START}); # LBA of partition start | ||
| 258 | $table .= pack ("V", $p->{SECTORS}); # Length in sectors | ||
| 259 | die if length ($table) % 16; | ||
| 260 | } | ||
| 261 | return pack ("a64", $table); | ||
| 262 | } | ||
| 263 | |||
| 264 | # make_kernel_command_line(@args) | ||
| 265 | # | ||
| 266 | # Returns the raw bytes to write to an MBR at offset $LOADER_SIZE to | ||
| 267 | # set a Pintos kernel command line. | ||
| 268 | sub make_kernel_command_line { | ||
| 269 | my (@args) = @_; | ||
| 270 | my ($args) = join ('', map ("$_\0", @args)); | ||
| 271 | die "command line exceeds 128 bytes" if length ($args) > 128; | ||
| 272 | return pack ("V a128", scalar (@args), $args); | ||
| 273 | } | ||
| 274 | |||
| 275 | # copy_file($from_handle, $from_file_name, $to_handle, $to_file_name, $size) | ||
| 276 | # | ||
| 277 | # Copies $size bytes from $from_handle to $to_handle. | ||
| 278 | # $from_file_name and $to_file_name are used in error messages. | ||
| 279 | sub copy_file { | ||
| 280 | my ($from_handle, $from_file_name, $to_handle, $to_file_name, $size) = @_; | ||
| 281 | |||
| 282 | while ($size > 0) { | ||
| 283 | my ($chunk_size) = 4096; | ||
| 284 | $chunk_size = $size if $chunk_size > $size; | ||
| 285 | $size -= $chunk_size; | ||
| 286 | |||
| 287 | my ($data) = read_fully ($from_handle, $from_file_name, $chunk_size); | ||
| 288 | write_fully ($to_handle, $to_file_name, $data); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | # read_fully($handle, $file_name, $bytes) | ||
| 293 | # | ||
| 294 | # Reads exactly $bytes bytes from $handle and returns the data read. | ||
| 295 | # $file_name is used in error messages. | ||
| 296 | sub read_fully { | ||
| 297 | my ($handle, $file_name, $bytes) = @_; | ||
| 298 | my ($data); | ||
| 299 | my ($read_bytes) = sysread ($handle, $data, $bytes); | ||
| 300 | die "$file_name: read: $!\n" if !defined $read_bytes; | ||
| 301 | die "$file_name: unexpected end of file\n" if $read_bytes != $bytes; | ||
| 302 | return $data; | ||
| 303 | } | ||
| 304 | |||
| 305 | # write_fully($handle, $file_name, $data) | ||
| 306 | # | ||
| 307 | # Write $data to $handle. | ||
| 308 | # $file_name is used in error messages. | ||
| 309 | sub write_fully { | ||
| 310 | my ($handle, $file_name, $data) = @_; | ||
| 311 | my ($written_bytes) = syswrite ($handle, $data); | ||
| 312 | die "$file_name: write: $!\n" if !defined $written_bytes; | ||
| 313 | die "$file_name: short write\n" if $written_bytes != length $data; | ||
| 314 | } | ||
| 315 | |||
| 316 | sub write_zeros { | ||
| 317 | my ($handle, $file_name, $size) = @_; | ||
| 318 | |||
| 319 | while ($size > 0) { | ||
| 320 | my ($chunk_size) = 4096; | ||
| 321 | $chunk_size = $size if $chunk_size > $size; | ||
| 322 | $size -= $chunk_size; | ||
| 323 | |||
| 324 | write_fully ($handle, $file_name, "\0" x $chunk_size); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | # div_round_up($x,$y) | ||
| 329 | # | ||
| 330 | # Returns $x / $y, rounded up to the nearest integer. | ||
| 331 | # $y must be an integer. | ||
| 332 | sub div_round_up { | ||
| 333 | my ($x, $y) = @_; | ||
| 334 | return int ((ceil ($x) + $y - 1) / $y); | ||
| 335 | } | ||
| 336 | |||
| 337 | # round_up($x, $y) | ||
| 338 | # | ||
| 339 | # Returns $x rounded up to the nearest multiple of $y. | ||
| 340 | # $y must be an integer. | ||
| 341 | sub round_up { | ||
| 342 | my ($x, $y) = @_; | ||
| 343 | return div_round_up ($x, $y) * $y; | ||
| 344 | } | ||
| 345 | |||
| 346 | # cyl_sectors(H => heads, S => sectors) | ||
| 347 | # | ||
| 348 | # Returns the number of sectors in a cylinder of a disk with the given | ||
| 349 | # geometry. | ||
| 350 | sub cyl_sectors { | ||
| 351 | my (%geometry) = @_; | ||
| 352 | return $geometry{H} * $geometry{S}; | ||
| 353 | } | ||
| 354 | |||
| 355 | # read_loader($file_name) | ||
| 356 | # | ||
| 357 | # Reads and returns the first $LOADER_SIZE bytes in $file_name. | ||
| 358 | # If $file_name is undefined, tries to find the default loader. | ||
| 359 | # Makes sure that the loader is a reasonable size. | ||
| 360 | sub read_loader { | ||
| 361 | my ($name) = @_; | ||
| 362 | $name = find_file ("loader.bin") if !defined $name; | ||
| 363 | die "Cannot find loader\n" if !defined $name; | ||
| 364 | |||
| 365 | my ($handle); | ||
| 366 | open ($handle, '<', $name) or die "$name: open: $!\n"; | ||
| 367 | -s $handle == $LOADER_SIZE || -s $handle == 512 | ||
| 368 | or die "$name: must be exactly $LOADER_SIZE or 512 bytes long\n"; | ||
| 369 | $loader = read_fully ($handle, $name, $LOADER_SIZE); | ||
| 370 | close ($handle) or die "$name: close: $!\n"; | ||
| 371 | return $loader; | ||
| 372 | } | ||
| 373 | |||
| 374 | # pack_chs($lba, {H => heads, S => sectors}) | ||
| 375 | # | ||
| 376 | # Converts logical sector $lba to a 3-byte packed geometrical sector | ||
| 377 | # in the format used in PC partition tables (see [Partitions]) and | ||
| 378 | # returns the geometrical sector as a 3-byte string. | ||
| 379 | sub pack_chs { | ||
| 380 | my ($lba, $geometry) = @_; | ||
| 381 | my ($cyl, $head, $sect) = lba_to_chs ($lba, $geometry); | ||
| 382 | return pack ("CCC", $head, $sect | (($cyl >> 2) & 0xc0), $cyl & 0xff); | ||
| 383 | } | ||
| 384 | |||
| 385 | # lba_to_chs($lba, {H => heads, S => sectors}) | ||
| 386 | # | ||
| 387 | # Returns the geometrical sector corresponding to logical sector $lba | ||
| 388 | # given the specified geometry. | ||
| 389 | sub lba_to_chs { | ||
| 390 | my ($lba, $geometry) = @_; | ||
| 391 | my ($hpc) = $geometry->{H}; | ||
| 392 | my ($spt) = $geometry->{S}; | ||
| 393 | |||
| 394 | # Source: | ||
| 395 | # http://en.wikipedia.org/wiki/CHS_conversion | ||
| 396 | use integer; | ||
| 397 | my $cyl = $lba / ($hpc * $spt); | ||
| 398 | my $temp = $lba % ($hpc * $spt); | ||
| 399 | my $head = $temp / $spt; | ||
| 400 | my $sect = $temp % $spt + 1; | ||
| 401 | |||
| 402 | # Source: | ||
| 403 | # http://www.cgsecurity.org/wiki/Intel_Partition_Table | ||
| 404 | if ($cyl <= 1023) { | ||
| 405 | return ($cyl, $head, $sect); | ||
| 406 | } else { | ||
| 407 | return (1023, 254, 63); ## or should this be (1023, $hpc, $spt)? | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | # read_mbr($file) | ||
| 412 | # | ||
| 413 | # Tries to read an MBR from $file. Returns the 512-byte MBR if | ||
| 414 | # successful, otherwise numeric 0. | ||
| 415 | sub read_mbr { | ||
| 416 | my ($file) = @_; | ||
| 417 | my ($retval) = 0; | ||
| 418 | open (FILE, '<', $file) or die "$file: open: $!\n"; | ||
| 419 | if (-s FILE == 0) { | ||
| 420 | die "$file: file has zero size\n"; | ||
| 421 | } elsif (-s FILE >= 512) { | ||
| 422 | my ($mbr); | ||
| 423 | sysread (FILE, $mbr, 512) == 512 or die "$file: read: $!\n"; | ||
| 424 | $retval = $mbr if unpack ("v", substr ($mbr, 510)) == 0xaa55; | ||
| 425 | } | ||
| 426 | close (FILE); | ||
| 427 | return $retval; | ||
| 428 | } | ||
| 429 | |||
| 430 | # interpret_partition_table($mbr, $disk) | ||
| 431 | # | ||
| 432 | # Parses the partition-table in the specified 512-byte $mbr and | ||
| 433 | # returns the partitions. $disk is used for error messages. | ||
| 434 | sub interpret_partition_table { | ||
| 435 | my ($mbr, $disk) = @_; | ||
| 436 | my (%parts); | ||
| 437 | for my $i (0...3) { | ||
| 438 | my ($bootable, $valid, $type, $lba_start, $lba_length) | ||
| 439 | = unpack ("C X V C x3 V V", substr ($mbr, 446 + 16 * $i, 16)); | ||
| 440 | next if !$valid; | ||
| 441 | |||
| 442 | (print STDERR "warning: invalid partition entry $i in $disk\n"), | ||
| 443 | next if $bootable != 0 && $bootable != 0x80; | ||
| 444 | |||
| 445 | my ($role) = $type2role{$type}; | ||
| 446 | (printf STDERR "warning: non-Pintos partition type 0x%02x in %s\n", | ||
| 447 | $type, $disk), | ||
| 448 | next if !defined $role; | ||
| 449 | |||
| 450 | (print STDERR "warning: duplicate \L$role\E partition in $disk\n"), | ||
| 451 | next if exists $parts{$role}; | ||
| 452 | |||
| 453 | $parts{$role} = {START => $lba_start, | ||
| 454 | SECTORS => $lba_length}; | ||
| 455 | } | ||
| 456 | return %parts; | ||
| 457 | } | ||
| 458 | |||
| 459 | # find_file($base_name) | ||
| 460 | # | ||
| 461 | # Looks for a file named $base_name in a couple of likely spots. If | ||
| 462 | # found, returns the name; otherwise, returns undef. | ||
| 463 | sub find_file { | ||
| 464 | my ($base_name) = @_; | ||
| 465 | -e && return $_ foreach $base_name, "build/$base_name"; | ||
| 466 | return undef; | ||
| 467 | } | ||
| 468 | |||
| 469 | # read_partition_table($file) | ||
| 470 | # | ||
| 471 | # Reads a partition table from $file and returns the parsed | ||
| 472 | # partitions. Dies if partitions can't be read. | ||
| 473 | sub read_partition_table { | ||
| 474 | my ($file) = @_; | ||
| 475 | my ($mbr) = read_mbr ($file); | ||
| 476 | die "$file: not a partitioned disk\n" if !$mbr; | ||
| 477 | return interpret_partition_table ($mbr, $file); | ||
| 478 | } | ||
| 479 | |||
| 480 | # max(@args) | ||
| 481 | # | ||
| 482 | # Returns the numerically largest value in @args. | ||
| 483 | sub max { | ||
| 484 | my ($max) = $_[0]; | ||
| 485 | foreach (@_[1..$#_]) { | ||
| 486 | $max = $_ if $_ > $max; | ||
| 487 | } | ||
| 488 | return $max; | ||
| 489 | } | ||
| 490 | |||
| 491 | 1; | ||
diff --git a/utils/backtrace b/utils/backtrace new file mode 100755 index 0000000..95e422f --- /dev/null +++ b/utils/backtrace | |||
| @@ -0,0 +1,106 @@ | |||
| 1 | #! /usr/bin/perl -w | ||
| 2 | |||
| 3 | use strict; | ||
| 4 | |||
| 5 | # Check command line. | ||
| 6 | if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) { | ||
| 7 | print <<'EOF'; | ||
| 8 | backtrace, for converting raw addresses into symbolic backtraces | ||
| 9 | usage: backtrace [BINARY]... ADDRESS... | ||
| 10 | where BINARY is the binary file or files from which to obtain symbols | ||
| 11 | and ADDRESS is a raw address to convert to a symbol name. | ||
| 12 | |||
| 13 | If no BINARY is unspecified, the default is the first of kernel.o or | ||
| 14 | build/kernel.o that exists. If multiple binaries are specified, each | ||
| 15 | symbol printed is from the first binary that contains a match. | ||
| 16 | |||
| 17 | The ADDRESS list should be taken from the "Call stack:" printed by the | ||
| 18 | kernel. Read "Backtraces" in the "Debugging Tools" chapter of the | ||
| 19 | Pintos documentation for more information. | ||
| 20 | EOF | ||
| 21 | exit 0; | ||
| 22 | } | ||
| 23 | die "backtrace: at least one argument required (use --help for help)\n" | ||
| 24 | if @ARGV == 0; | ||
| 25 | |||
| 26 | # Drop garbage inserted by kernel. | ||
| 27 | @ARGV = grep (!/^(call|stack:?|[-+])$/i, @ARGV); | ||
| 28 | s/\.$// foreach @ARGV; | ||
| 29 | |||
| 30 | # Find binaries. | ||
| 31 | my (@binaries); | ||
| 32 | while ($ARGV[0] !~ /^0x/) { | ||
| 33 | my ($bin) = shift @ARGV; | ||
| 34 | die "backtrace: $bin: not found (use --help for help)\n" if ! -e $bin; | ||
| 35 | push (@binaries, $bin); | ||
| 36 | } | ||
| 37 | if (!@binaries) { | ||
| 38 | my ($bin); | ||
| 39 | if (-e 'kernel.o') { | ||
| 40 | $bin = 'kernel.o'; | ||
| 41 | } elsif (-e 'build/kernel.o') { | ||
| 42 | $bin = 'build/kernel.o'; | ||
| 43 | } else { | ||
| 44 | die "backtrace: no binary specified and neither \"kernel.o\" nor \"build/kernel.o\" exists (use --help for help)\n"; | ||
| 45 | } | ||
| 46 | push (@binaries, $bin); | ||
| 47 | } | ||
| 48 | |||
| 49 | # Find addr2line. | ||
| 50 | my ($a2l) = search_path ("i386-elf-addr2line") || search_path ("addr2line"); | ||
| 51 | if (!$a2l) { | ||
| 52 | die "backtrace: neither `i386-elf-addr2line' nor `addr2line' in PATH\n"; | ||
| 53 | } | ||
| 54 | sub search_path { | ||
| 55 | my ($target) = @_; | ||
| 56 | for my $dir (split (':', $ENV{PATH})) { | ||
| 57 | my ($file) = "$dir/$target"; | ||
| 58 | return $file if -e $file; | ||
| 59 | } | ||
| 60 | return undef; | ||
| 61 | } | ||
| 62 | |||
| 63 | # Figure out backtrace. | ||
| 64 | my (@locs) = map ({ADDR => $_}, @ARGV); | ||
| 65 | for my $bin (@binaries) { | ||
| 66 | open (A2L, "$a2l -fe $bin " . join (' ', map ($_->{ADDR}, @locs)) . "|"); | ||
| 67 | for (my ($i) = 0; <A2L>; $i++) { | ||
| 68 | my ($function, $line); | ||
| 69 | chomp ($function = $_); | ||
| 70 | chomp ($line = <A2L>); | ||
| 71 | next if defined $locs[$i]{BINARY}; | ||
| 72 | |||
| 73 | if ($function ne '??' || $line ne '??:0') { | ||
| 74 | $locs[$i]{FUNCTION} = $function; | ||
| 75 | $locs[$i]{LINE} = $line; | ||
| 76 | $locs[$i]{BINARY} = $bin; | ||
| 77 | } | ||
| 78 | } | ||
| 79 | close (A2L); | ||
| 80 | } | ||
| 81 | |||
| 82 | # Print backtrace. | ||
| 83 | my ($cur_binary); | ||
| 84 | for my $loc (@locs) { | ||
| 85 | if (defined ($loc->{BINARY}) | ||
| 86 | && @binaries > 1 | ||
| 87 | && (!defined ($cur_binary) || $loc->{BINARY} ne $cur_binary)) { | ||
| 88 | $cur_binary = $loc->{BINARY}; | ||
| 89 | print "In $cur_binary:\n"; | ||
| 90 | } | ||
| 91 | |||
| 92 | my ($addr) = $loc->{ADDR}; | ||
| 93 | $addr = sprintf ("0x%08x", hex ($addr)) if $addr =~ /^0x[0-9a-f]+$/i; | ||
| 94 | |||
| 95 | print $addr, ": "; | ||
| 96 | if (defined ($loc->{BINARY})) { | ||
| 97 | my ($function) = $loc->{FUNCTION}; | ||
| 98 | my ($line) = $loc->{LINE}; | ||
| 99 | $line =~ s/^(\.\.\/)*//; | ||
| 100 | $line = "..." . substr ($line, -25) if length ($line) > 28; | ||
| 101 | print "$function ($line)"; | ||
| 102 | } else { | ||
| 103 | print "(unknown)"; | ||
| 104 | } | ||
| 105 | print "\n"; | ||
| 106 | } | ||
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 | } | ||
diff --git a/utils/pintos-gdb b/utils/pintos-gdb new file mode 100755 index 0000000..9c9555b --- /dev/null +++ b/utils/pintos-gdb | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | #! /bin/sh | ||
| 2 | |||
| 3 | # Path to GDB macros file. Customize for your site. | ||
| 4 | PINTOS_SRC="$(dirname $(dirname $(which pintos-gdb)))" | ||
| 5 | GDBMACROS="${PINTOS_SRC}/misc/gdb-macros" | ||
| 6 | |||
| 7 | # Choose correct GDB. | ||
| 8 | if command -v i386-elf-gdb >/dev/null 2>&1; then | ||
| 9 | GDB=i386-elf-gdb | ||
| 10 | else | ||
| 11 | GDB=gdb | ||
| 12 | fi | ||
| 13 | |||
| 14 | # Run GDB. | ||
| 15 | if test -f "$GDBMACROS"; then | ||
| 16 | exec $GDB -x "$GDBMACROS" "$@" | ||
| 17 | else | ||
| 18 | echo "*** $GDBMACROS does not exist ***" | ||
| 19 | echo "*** Pintos GDB macros will not be available ***" | ||
| 20 | exec $GDB "$@" | ||
| 21 | fi | ||
diff --git a/utils/pintos-mkdisk b/utils/pintos-mkdisk new file mode 100755 index 0000000..87b1563 --- /dev/null +++ b/utils/pintos-mkdisk | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | #! /usr/bin/perl | ||
| 2 | |||
| 3 | use strict; | ||
| 4 | use warnings; | ||
| 5 | use POSIX; | ||
| 6 | use Getopt::Long qw(:config bundling); | ||
| 7 | use Fcntl 'SEEK_SET'; | ||
| 8 | |||
| 9 | # Read Pintos.pm from the same directory as this program. | ||
| 10 | BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } | ||
| 11 | |||
| 12 | our ($disk_fn); # Output disk file name. | ||
| 13 | our (%parts); # Partitions. | ||
| 14 | our ($format); # "partitioned" (default) or "raw" | ||
| 15 | our (%geometry); # IDE disk geometry. | ||
| 16 | our ($align); # Align partitions on cylinders? | ||
| 17 | our ($loader_fn); # File name of loader. | ||
| 18 | our ($include_loader); # Include loader? | ||
| 19 | our (@kernel_args); # Kernel arguments. | ||
| 20 | |||
| 21 | if (grep ($_ eq '--', @ARGV)) { | ||
| 22 | @kernel_args = @ARGV; | ||
| 23 | @ARGV = (); | ||
| 24 | while ((my $arg = shift (@kernel_args)) ne '--') { | ||
| 25 | push (@ARGV, $arg); | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | GetOptions ("h|help" => sub { usage (0); }, | ||
| 30 | |||
| 31 | "kernel=s" => \&set_part, | ||
| 32 | "filesys=s" => \&set_part, | ||
| 33 | "scratch=s" => \&set_part, | ||
| 34 | "swap=s" => \&set_part, | ||
| 35 | |||
| 36 | "filesys-size=s" => \&set_part, | ||
| 37 | "scratch-size=s" => \&set_part, | ||
| 38 | "swap-size=s" => \&set_part, | ||
| 39 | |||
| 40 | "kernel-from=s" => \&set_part, | ||
| 41 | "filesys-from=s" => \&set_part, | ||
| 42 | "scratch-from=s" => \&set_part, | ||
| 43 | "swap-from=s" => \&set_part, | ||
| 44 | |||
| 45 | "format=s" => \$format, | ||
| 46 | "loader:s" => \&set_loader, | ||
| 47 | "no-loader" => \&set_no_loader, | ||
| 48 | "geometry=s" => \&set_geometry, | ||
| 49 | "align=s" => \&set_align) | ||
| 50 | or exit 1; | ||
| 51 | usage (1) if @ARGV != 1; | ||
| 52 | |||
| 53 | $disk_fn = $ARGV[0]; | ||
| 54 | die "$disk_fn: already exists\n" if -e $disk_fn; | ||
| 55 | |||
| 56 | # Sets the loader to copy to the MBR. | ||
| 57 | sub set_loader { | ||
| 58 | die "can't specify both --loader and --no-loader\n" | ||
| 59 | if defined ($include_loader) && !$include_loader; | ||
| 60 | $include_loader = 1; | ||
| 61 | $loader_fn = $_[1] if $_[1] ne ''; | ||
| 62 | } | ||
| 63 | |||
| 64 | # Disables copying a loader to the MBR. | ||
| 65 | sub set_no_loader { | ||
| 66 | die "can't specify both --loader and --no-loader\n" | ||
| 67 | if defined ($include_loader) && $include_loader; | ||
| 68 | $include_loader = 0; | ||
| 69 | } | ||
| 70 | |||
| 71 | # Figure out whether to include a loader. | ||
| 72 | $include_loader = exists ($parts{KERNEL}) && $format eq 'partitioned' | ||
| 73 | if !defined ($include_loader); | ||
| 74 | die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw'; | ||
| 75 | die "can't write command-line arguments without --loader or --kernel\n" | ||
| 76 | if @kernel_args && !$include_loader; | ||
| 77 | print STDERR "warning: --loader only makes sense without --kernel " | ||
| 78 | . "if this disk will be used to load a kernel from another disk\n" | ||
| 79 | if $include_loader && !exists ($parts{KERNEL}); | ||
| 80 | |||
| 81 | # Open disk. | ||
| 82 | my ($disk_handle); | ||
| 83 | open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n"; | ||
| 84 | |||
| 85 | # Read loader. | ||
| 86 | my ($loader); | ||
| 87 | $loader = read_loader ($loader_fn) if $include_loader; | ||
| 88 | |||
| 89 | # Write disk. | ||
| 90 | my (%disk) = %parts; | ||
| 91 | $disk{DISK} = $disk_fn; | ||
| 92 | $disk{HANDLE} = $disk_handle; | ||
| 93 | $disk{ALIGN} = $align; | ||
| 94 | $disk{GEOMETRY} = %geometry; | ||
| 95 | $disk{FORMAT} = $format; | ||
| 96 | $disk{LOADER} = $loader; | ||
| 97 | $disk{ARGS} = \@kernel_args; | ||
| 98 | assemble_disk (%disk); | ||
| 99 | |||
| 100 | # Done. | ||
| 101 | exit 0; | ||
| 102 | |||
| 103 | sub usage { | ||
| 104 | print <<'EOF'; | ||
| 105 | pintos-mkdisk, a utility for creating Pintos virtual disks | ||
| 106 | Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...] | ||
| 107 | where DISK is the virtual disk to create, | ||
| 108 | each ARGUMENT is inserted into the command line written to DISK, | ||
| 109 | and each OPTION is one of the following options. | ||
| 110 | Partition options: (where PARTITION is one of: kernel filesys scratch swap) | ||
| 111 | --PARTITION=FILE Use a copy of FILE for the given PARTITION | ||
| 112 | --PARTITION-size=SIZE Create an empty PARTITION of the given SIZE in MB | ||
| 113 | --PARTITION-from=DISK Use of a copy of the given PARTITION in DISK | ||
| 114 | (There is no --kernel-size option.) | ||
| 115 | Output disk options: | ||
| 116 | --format=partitioned Write partition table to output (default) | ||
| 117 | --format=raw Do not write partition table to output | ||
| 118 | (Pintos can only use partitioned disks.) | ||
| 119 | Partitioned format output options: | ||
| 120 | --loader[=FILE] Get bootstrap loader from FILE (default: loader.bin | ||
| 121 | if --kernel option is specified, empty otherwise) | ||
| 122 | --no-loader Do not include a bootstrap loader | ||
| 123 | --geometry=H,S Use H head, S sector geometry (default: 16, 63) | ||
| 124 | --geometry=zip Use 64 head, 32 sector geometry for USB-ZIP boot | ||
| 125 | per http://syslinux.zytor.com/usbkey.php | ||
| 126 | --align=bochs Round size to cylinder for Bochs support (default) | ||
| 127 | --align=full Align partition boundaries to cylinder boundary to | ||
| 128 | let fdisk guess correct geometry and quiet warnings | ||
| 129 | --align=none Don't align partitions at all, to save space | ||
| 130 | Other options: | ||
| 131 | -h, --help Display this help message. | ||
| 132 | EOF | ||
| 133 | exit ($_[0]); | ||
| 134 | } | ||
diff --git a/utils/pintos-set-cmdline b/utils/pintos-set-cmdline new file mode 100644 index 0000000..8c8f702 --- /dev/null +++ b/utils/pintos-set-cmdline | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | #! /usr/bin/perl -w | ||
| 2 | |||
| 3 | use strict; | ||
| 4 | use Fcntl 'SEEK_SET'; | ||
| 5 | |||
| 6 | # Read Pintos.pm from the same directory as this program. | ||
| 7 | BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; } | ||
| 8 | |||
| 9 | # Get command-line arguments. | ||
| 10 | usage (0) if @ARGV == 1 && $ARGV[0] eq '--help'; | ||
| 11 | usage (1) if @ARGV < 2 || $ARGV[1] ne '--'; | ||
| 12 | my ($disk, undef, @kernel_args) = @ARGV; | ||
| 13 | |||
| 14 | # Open disk. | ||
| 15 | my ($handle); | ||
| 16 | open ($handle, '+<', $disk) or die "$disk: open: $!\n"; | ||
| 17 | |||
| 18 | # Check that it's a partitioned disk with a Pintos loader. | ||
| 19 | my ($buffer) = read_fully ($handle, $disk, 512); | ||
| 20 | unpack ("x510 v", $buffer) == 0xaa55 or die "$disk: not a partitioned disk\n"; | ||
| 21 | $buffer =~ /Pintos/ or die "$disk: does not contain Pintos loader\n"; | ||
| 22 | |||
| 23 | # Write the command line. | ||
| 24 | our ($LOADER_SIZE); | ||
| 25 | sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE | ||
| 26 | or die "$disk: seek: $!\n"; | ||
| 27 | write_fully ($handle, $disk, make_kernel_command_line (@kernel_args)); | ||
| 28 | |||
| 29 | # Close disk. | ||
| 30 | close ($handle) or die "$disk: close: $!\n"; | ||
| 31 | |||
| 32 | exit 0; | ||
| 33 | |||
| 34 | sub usage { | ||
| 35 | print <<'EOF'; | ||
| 36 | pintos-set-cmdline, a utility for changing the command line in Pintos disks | ||
| 37 | Usage: pintos-set-cmdline DISK -- [ARGUMENT...] | ||
| 38 | where DISK is a bootable disk containing a Pintos loader | ||
| 39 | and each ARGUMENT is inserted into the command line written to DISK. | ||
| 40 | EOF | ||
| 41 | exit ($_[0]); | ||
| 42 | } | ||
diff --git a/utils/setitimer-helper.c b/utils/setitimer-helper.c new file mode 100644 index 0000000..772d736 --- /dev/null +++ b/utils/setitimer-helper.c | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | #include <errno.h> | ||
| 2 | #include <limits.h> | ||
| 3 | #include <math.h> | ||
| 4 | #include <stdio.h> | ||
| 5 | #include <stdlib.h> | ||
| 6 | #include <string.h> | ||
| 7 | #include <sys/time.h> | ||
| 8 | #include <unistd.h> | ||
| 9 | |||
| 10 | int | ||
| 11 | main (int argc, char *argv[]) | ||
| 12 | { | ||
| 13 | const char *program_name = argv[0]; | ||
| 14 | double timeout; | ||
| 15 | |||
| 16 | if (argc < 3) | ||
| 17 | { | ||
| 18 | fprintf (stderr, | ||
| 19 | "setitimer-helper: runs a program with a virtual CPU limit\n" | ||
| 20 | "usage: %s TIMEOUT PROGRAM [ARG...]\n" | ||
| 21 | " where TIMEOUT is the virtual CPU limit, in seconds,\n" | ||
| 22 | " and remaining arguments specify the program to run\n" | ||
| 23 | " and its argument.\n", | ||
| 24 | program_name); | ||
| 25 | return EXIT_FAILURE; | ||
| 26 | } | ||
| 27 | |||
| 28 | timeout = strtod (argv[1], NULL); | ||
| 29 | if (timeout >= 0.0 && timeout < LONG_MAX) | ||
| 30 | { | ||
| 31 | struct itimerval it; | ||
| 32 | |||
| 33 | it.it_interval.tv_sec = 0; | ||
| 34 | it.it_interval.tv_usec = 0; | ||
| 35 | it.it_value.tv_sec = timeout; | ||
| 36 | it.it_value.tv_usec = (timeout - floor (timeout)) * 1000000; | ||
| 37 | if (setitimer (ITIMER_VIRTUAL, &it, NULL) < 0) | ||
| 38 | fprintf (stderr, "%s: setitimer: %s\n", | ||
| 39 | program_name, strerror (errno)); | ||
| 40 | } | ||
| 41 | else | ||
| 42 | fprintf (stderr, "%s: invalid timeout value \"%s\"\n", | ||
| 43 | program_name, argv[1]); | ||
| 44 | |||
| 45 | execvp (argv[2], &argv[2]); | ||
| 46 | fprintf (stderr, "%s: couldn't exec \"%s\": %s\n", | ||
| 47 | program_name, argv[2], strerror (errno)); | ||
| 48 | return EXIT_FAILURE; | ||
| 49 | } | ||
diff --git a/utils/squish-pty.c b/utils/squish-pty.c new file mode 100644 index 0000000..c8375a5 --- /dev/null +++ b/utils/squish-pty.c | |||
| @@ -0,0 +1,355 @@ | |||
| 1 | #define _GNU_SOURCE 1 | ||
| 2 | #include <errno.h> | ||
| 3 | #include <fcntl.h> | ||
| 4 | #include <signal.h> | ||
| 5 | #include <stdarg.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | #include <stdio.h> | ||
| 8 | #include <stdlib.h> | ||
| 9 | #include <string.h> | ||
| 10 | #include <stropts.h> | ||
| 11 | #include <sys/ioctl.h> | ||
| 12 | #include <sys/stat.h> | ||
| 13 | #include <sys/time.h> | ||
| 14 | #include <sys/types.h> | ||
| 15 | #include <sys/wait.h> | ||
| 16 | #include <termios.h> | ||
| 17 | #include <unistd.h> | ||
| 18 | |||
| 19 | static void | ||
| 20 | fail_io (const char *msg, ...) | ||
| 21 | __attribute__ ((noreturn)) | ||
| 22 | __attribute__ ((format (printf, 1, 2))); | ||
| 23 | |||
| 24 | /* Prints MSG, formatting as with printf(), | ||
| 25 | plus an error message based on errno, | ||
| 26 | and exits. */ | ||
| 27 | static void | ||
| 28 | fail_io (const char *msg, ...) | ||
| 29 | { | ||
| 30 | va_list args; | ||
| 31 | |||
| 32 | va_start (args, msg); | ||
| 33 | vfprintf (stderr, msg, args); | ||
| 34 | va_end (args); | ||
| 35 | |||
| 36 | if (errno != 0) | ||
| 37 | fprintf (stderr, ": %s", strerror (errno)); | ||
| 38 | putc ('\n', stderr); | ||
| 39 | exit (EXIT_FAILURE); | ||
| 40 | } | ||
| 41 | |||
| 42 | /* If FD is a terminal, configures it for noncanonical input mode | ||
| 43 | with VMIN and VTIME set as indicated. | ||
| 44 | If FD is not a terminal, has no effect. */ | ||
| 45 | static void | ||
| 46 | make_noncanon (int fd, int vmin, int vtime) | ||
| 47 | { | ||
| 48 | if (isatty (fd)) | ||
| 49 | { | ||
| 50 | struct termios termios; | ||
| 51 | if (tcgetattr (fd, &termios) < 0) | ||
| 52 | fail_io ("tcgetattr"); | ||
| 53 | termios.c_lflag &= ~(ICANON | ECHO); | ||
| 54 | termios.c_cc[VMIN] = vmin; | ||
| 55 | termios.c_cc[VTIME] = vtime; | ||
| 56 | if (tcsetattr (fd, TCSANOW, &termios) < 0) | ||
| 57 | fail_io ("tcsetattr"); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | /* Make FD non-blocking if NONBLOCKING is true, | ||
| 62 | or blocking if NONBLOCKING is false. */ | ||
| 63 | static void | ||
| 64 | make_nonblocking (int fd, bool nonblocking) | ||
| 65 | { | ||
| 66 | int flags = fcntl (fd, F_GETFL); | ||
| 67 | if (flags < 0) | ||
| 68 | fail_io ("fcntl"); | ||
| 69 | if (nonblocking) | ||
| 70 | flags |= O_NONBLOCK; | ||
| 71 | else | ||
| 72 | flags &= ~O_NONBLOCK; | ||
| 73 | if (fcntl (fd, F_SETFL, flags) < 0) | ||
| 74 | fail_io ("fcntl"); | ||
| 75 | } | ||
| 76 | |||
| 77 | /* Handle a read or write on *FD, which is the pty if FD_IS_PTY | ||
| 78 | is true, that returned end-of-file or error indication RETVAL. | ||
| 79 | The system call is named CALL, for use in error messages. | ||
| 80 | Returns true if processing may continue, false if we're all | ||
| 81 | done. */ | ||
| 82 | static bool | ||
| 83 | handle_error (ssize_t retval, int *fd, bool fd_is_pty, const char *call) | ||
| 84 | { | ||
| 85 | if (fd_is_pty) | ||
| 86 | { | ||
| 87 | if (retval < 0) | ||
| 88 | { | ||
| 89 | if (errno == EIO) | ||
| 90 | { | ||
| 91 | /* Slave side of pty has been closed. */ | ||
| 92 | return false; | ||
| 93 | } | ||
| 94 | else | ||
| 95 | fail_io (call); | ||
| 96 | } | ||
| 97 | else | ||
| 98 | return true; | ||
| 99 | } | ||
| 100 | else | ||
| 101 | { | ||
| 102 | if (retval == 0) | ||
| 103 | { | ||
| 104 | close (*fd); | ||
| 105 | *fd = -1; | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | else | ||
| 109 | fail_io (call); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | /* Copies data from stdin to PTY and from PTY to stdout until no | ||
| 114 | more data can be read or written. */ | ||
| 115 | static void | ||
| 116 | relay (int pty, int dead_child_fd) | ||
| 117 | { | ||
| 118 | struct pipe | ||
| 119 | { | ||
| 120 | int in, out; | ||
| 121 | char buf[BUFSIZ]; | ||
| 122 | size_t size, ofs; | ||
| 123 | bool active; | ||
| 124 | }; | ||
| 125 | struct pipe pipes[2]; | ||
| 126 | |||
| 127 | /* Make PTY, stdin, and stdout non-blocking. */ | ||
| 128 | make_nonblocking (pty, true); | ||
| 129 | make_nonblocking (STDIN_FILENO, true); | ||
| 130 | make_nonblocking (STDOUT_FILENO, true); | ||
| 131 | |||
| 132 | /* Configure noncanonical mode on PTY and stdin to avoid | ||
| 133 | waiting for end-of-line. We want to minimize context | ||
| 134 | switching on PTY (for efficiency) and minimize latency on | ||
| 135 | stdin to avoid a laggy user experience. */ | ||
| 136 | make_noncanon (pty, 16, 1); | ||
| 137 | make_noncanon (STDIN_FILENO, 1, 0); | ||
| 138 | |||
| 139 | memset (pipes, 0, sizeof pipes); | ||
| 140 | pipes[0].in = STDIN_FILENO; | ||
| 141 | pipes[0].out = pty; | ||
| 142 | pipes[1].in = pty; | ||
| 143 | pipes[1].out = STDOUT_FILENO; | ||
| 144 | |||
| 145 | while (pipes[0].in != -1 || pipes[1].in != -1) | ||
| 146 | { | ||
| 147 | fd_set read_fds, write_fds; | ||
| 148 | int retval; | ||
| 149 | int i; | ||
| 150 | |||
| 151 | FD_ZERO (&read_fds); | ||
| 152 | FD_ZERO (&write_fds); | ||
| 153 | for (i = 0; i < 2; i++) | ||
| 154 | { | ||
| 155 | struct pipe *p = &pipes[i]; | ||
| 156 | |||
| 157 | /* Don't do anything with the stdin->pty pipe until we | ||
| 158 | have some data for the pty->stdout pipe. If we get | ||
| 159 | too eager, Bochs will throw away our input. */ | ||
| 160 | if (i == 0 && !pipes[1].active) | ||
| 161 | continue; | ||
| 162 | |||
| 163 | if (p->in != -1 && p->size + p->ofs < sizeof p->buf) | ||
| 164 | FD_SET (p->in, &read_fds); | ||
| 165 | if (p->out != -1 && p->size > 0) | ||
| 166 | FD_SET (p->out, &write_fds); | ||
| 167 | } | ||
| 168 | FD_SET (dead_child_fd, &read_fds); | ||
| 169 | |||
| 170 | do | ||
| 171 | { | ||
| 172 | retval = select (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL); | ||
| 173 | } | ||
| 174 | while (retval < 0 && errno == EINTR); | ||
| 175 | if (retval < 0) | ||
| 176 | fail_io ("select"); | ||
| 177 | |||
| 178 | if (FD_ISSET (dead_child_fd, &read_fds)) | ||
| 179 | { | ||
| 180 | /* Child died. Do final relaying. */ | ||
| 181 | struct pipe *p = &pipes[1]; | ||
| 182 | if (p->out == -1) | ||
| 183 | return; | ||
| 184 | make_nonblocking (STDOUT_FILENO, false); | ||
| 185 | for (;;) | ||
| 186 | { | ||
| 187 | ssize_t n; | ||
| 188 | |||
| 189 | /* Write buffer. */ | ||
| 190 | while (p->size > 0) | ||
| 191 | { | ||
| 192 | n = write (p->out, p->buf + p->ofs, p->size); | ||
| 193 | if (n < 0) | ||
| 194 | fail_io ("write"); | ||
| 195 | else if (n == 0) | ||
| 196 | fail_io ("zero-length write"); | ||
| 197 | p->ofs += n; | ||
| 198 | p->size -= n; | ||
| 199 | } | ||
| 200 | p->ofs = 0; | ||
| 201 | |||
| 202 | p->size = n = read (p->in, p->buf, sizeof p->buf); | ||
| 203 | if (n <= 0) | ||
| 204 | return; | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | for (i = 0; i < 2; i++) | ||
| 209 | { | ||
| 210 | struct pipe *p = &pipes[i]; | ||
| 211 | if (p->in != -1 && FD_ISSET (p->in, &read_fds)) | ||
| 212 | { | ||
| 213 | ssize_t n = read (p->in, p->buf + p->ofs + p->size, | ||
| 214 | sizeof p->buf - p->ofs - p->size); | ||
| 215 | if (n > 0) | ||
| 216 | { | ||
| 217 | p->active = true; | ||
| 218 | p->size += n; | ||
| 219 | if (p->size == BUFSIZ && p->ofs != 0) | ||
| 220 | { | ||
| 221 | memmove (p->buf, p->buf + p->ofs, p->size); | ||
| 222 | p->ofs = 0; | ||
| 223 | } | ||
| 224 | } | ||
| 225 | else if (!handle_error (n, &p->in, p->in == pty, "read")) | ||
| 226 | return; | ||
| 227 | } | ||
| 228 | if (p->out != -1 && FD_ISSET (p->out, &write_fds)) | ||
| 229 | { | ||
| 230 | ssize_t n = write (p->out, p->buf + p->ofs, p->size); | ||
| 231 | if (n > 0) | ||
| 232 | { | ||
| 233 | p->ofs += n; | ||
| 234 | p->size -= n; | ||
| 235 | if (p->size == 0) | ||
| 236 | p->ofs = 0; | ||
| 237 | } | ||
| 238 | else if (!handle_error (n, &p->out, p->out == pty, "write")) | ||
| 239 | return; | ||
| 240 | } | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | static int dead_child_fd; | ||
| 246 | |||
| 247 | static void | ||
| 248 | sigchld_handler (int signo __attribute__ ((unused))) | ||
| 249 | { | ||
| 250 | if (write (dead_child_fd, "", 1) < 0) | ||
| 251 | _exit (1); | ||
| 252 | } | ||
| 253 | |||
| 254 | int | ||
| 255 | main (int argc __attribute__ ((unused)), char *argv[]) | ||
| 256 | { | ||
| 257 | int master, slave; | ||
| 258 | char *name; | ||
| 259 | pid_t pid; | ||
| 260 | struct sigaction sa; | ||
| 261 | int pipe_fds[2]; | ||
| 262 | struct itimerval zero_itimerval, old_itimerval; | ||
| 263 | |||
| 264 | if (argc < 2) | ||
| 265 | { | ||
| 266 | fprintf (stderr, | ||
| 267 | "usage: squish-pty COMMAND [ARG]...\n" | ||
| 268 | "Squishes both stdin and stdout into a single pseudoterminal,\n" | ||
| 269 | "which is passed as stdout to run the specified COMMAND.\n"); | ||
| 270 | return EXIT_FAILURE; | ||
| 271 | } | ||
| 272 | |||
| 273 | /* Open master side of pty and get ready to open slave. */ | ||
| 274 | master = open ("/dev/ptmx", O_RDWR | O_NOCTTY); | ||
| 275 | if (master < 0) | ||
| 276 | fail_io ("open \"/dev/ptmx\""); | ||
| 277 | if (grantpt (master) < 0) | ||
| 278 | fail_io ("grantpt"); | ||
| 279 | if (unlockpt (master) < 0) | ||
| 280 | fail_io ("unlockpt"); | ||
| 281 | |||
| 282 | /* Open slave side of pty. */ | ||
| 283 | name = ptsname (master); | ||
| 284 | if (name == NULL) | ||
| 285 | fail_io ("ptsname"); | ||
| 286 | slave = open (name, O_RDWR); | ||
| 287 | if (slave < 0) | ||
| 288 | fail_io ("open \"%s\"", name); | ||
| 289 | |||
| 290 | /* System V implementations need STREAMS configuration for the | ||
| 291 | slave. */ | ||
| 292 | if (isastream (slave)) | ||
| 293 | { | ||
| 294 | if (ioctl (slave, I_PUSH, "ptem") < 0 | ||
| 295 | || ioctl (slave, I_PUSH, "ldterm") < 0) | ||
| 296 | fail_io ("ioctl"); | ||
| 297 | } | ||
| 298 | |||
| 299 | /* Arrange to get notified when a child dies, by writing a byte | ||
| 300 | to a pipe fd. We really want to use pselect() and | ||
| 301 | sigprocmask(), but Solaris 2.7 doesn't have it. */ | ||
| 302 | if (pipe (pipe_fds) < 0) | ||
| 303 | fail_io ("pipe"); | ||
| 304 | dead_child_fd = pipe_fds[1]; | ||
| 305 | |||
| 306 | memset (&sa, 0, sizeof sa); | ||
| 307 | sa.sa_handler = sigchld_handler; | ||
| 308 | sigemptyset (&sa.sa_mask); | ||
| 309 | sa.sa_flags = SA_RESTART; | ||
| 310 | if (sigaction (SIGCHLD, &sa, NULL) < 0) | ||
| 311 | fail_io ("sigaction"); | ||
| 312 | |||
| 313 | /* Save the virtual interval timer, which might have been set | ||
| 314 | by the process that ran us. It really should be applied to | ||
| 315 | our child process. */ | ||
| 316 | memset (&zero_itimerval, 0, sizeof zero_itimerval); | ||
| 317 | if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, &old_itimerval) < 0) | ||
| 318 | fail_io ("setitimer"); | ||
| 319 | |||
| 320 | pid = fork (); | ||
| 321 | if (pid < 0) | ||
| 322 | fail_io ("fork"); | ||
| 323 | else if (pid != 0) | ||
| 324 | { | ||
| 325 | /* Running in parent process. */ | ||
| 326 | int status; | ||
| 327 | close (slave); | ||
| 328 | relay (master, pipe_fds[0]); | ||
| 329 | |||
| 330 | /* If the subprocess has died, die in the same fashion. | ||
| 331 | In particular, dying from SIGVTALRM tells the pintos | ||
| 332 | script that we ran out of CPU time. */ | ||
| 333 | if (waitpid (pid, &status, WNOHANG) > 0) | ||
| 334 | { | ||
| 335 | if (WIFEXITED (status)) | ||
| 336 | return WEXITSTATUS (status); | ||
| 337 | else if (WIFSIGNALED (status)) | ||
| 338 | raise (WTERMSIG (status)); | ||
| 339 | } | ||
| 340 | return 0; | ||
| 341 | } | ||
| 342 | else | ||
| 343 | { | ||
| 344 | /* Running in child process. */ | ||
| 345 | if (setitimer (ITIMER_VIRTUAL, &old_itimerval, NULL) < 0) | ||
| 346 | fail_io ("setitimer"); | ||
| 347 | if (dup2 (slave, STDOUT_FILENO) < 0) | ||
| 348 | fail_io ("dup2"); | ||
| 349 | if (close (pipe_fds[0]) < 0 || close (pipe_fds[1]) < 0 | ||
| 350 | || close (slave) < 0 || close (master) < 0) | ||
| 351 | fail_io ("close"); | ||
| 352 | execvp (argv[1], argv + 1); | ||
| 353 | fail_io ("exec"); | ||
| 354 | } | ||
| 355 | } | ||
diff --git a/utils/squish-unix.c b/utils/squish-unix.c new file mode 100644 index 0000000..805205b --- /dev/null +++ b/utils/squish-unix.c | |||
| @@ -0,0 +1,338 @@ | |||
| 1 | #define _GNU_SOURCE 1 | ||
| 2 | #include <errno.h> | ||
| 3 | #include <fcntl.h> | ||
| 4 | #include <signal.h> | ||
| 5 | #include <stdarg.h> | ||
| 6 | #include <stdbool.h> | ||
| 7 | #include <stddef.h> | ||
| 8 | #include <stdio.h> | ||
| 9 | #include <stdlib.h> | ||
| 10 | #include <string.h> | ||
| 11 | #include <stropts.h> | ||
| 12 | #include <sys/ioctl.h> | ||
| 13 | #include <sys/stat.h> | ||
| 14 | #include <sys/time.h> | ||
| 15 | #include <sys/types.h> | ||
| 16 | #include <sys/wait.h> | ||
| 17 | #include <sys/socket.h> | ||
| 18 | #include <sys/un.h> | ||
| 19 | #include <termios.h> | ||
| 20 | #include <unistd.h> | ||
| 21 | |||
| 22 | static void | ||
| 23 | fail_io (const char *msg, ...) | ||
| 24 | __attribute__ ((noreturn)) | ||
| 25 | __attribute__ ((format (printf, 1, 2))); | ||
| 26 | |||
| 27 | /* Prints MSG, formatting as with printf(), | ||
| 28 | plus an error message based on errno, | ||
| 29 | and exits. */ | ||
| 30 | static void | ||
| 31 | fail_io (const char *msg, ...) | ||
| 32 | { | ||
| 33 | va_list args; | ||
| 34 | |||
| 35 | va_start (args, msg); | ||
| 36 | vfprintf (stderr, msg, args); | ||
| 37 | va_end (args); | ||
| 38 | |||
| 39 | if (errno != 0) | ||
| 40 | fprintf (stderr, ": %s", strerror (errno)); | ||
| 41 | putc ('\n', stderr); | ||
| 42 | exit (EXIT_FAILURE); | ||
| 43 | } | ||
| 44 | |||
| 45 | /* If FD is a terminal, configures it for noncanonical input mode | ||
| 46 | with VMIN and VTIME set as indicated. | ||
| 47 | If FD is not a terminal, has no effect. */ | ||
| 48 | static void | ||
| 49 | make_noncanon (int fd, int vmin, int vtime) | ||
| 50 | { | ||
| 51 | if (isatty (fd)) | ||
| 52 | { | ||
| 53 | struct termios termios; | ||
| 54 | if (tcgetattr (fd, &termios) < 0) | ||
| 55 | fail_io ("tcgetattr"); | ||
| 56 | termios.c_lflag &= ~(ICANON | ECHO); | ||
| 57 | termios.c_cc[VMIN] = vmin; | ||
| 58 | termios.c_cc[VTIME] = vtime; | ||
| 59 | if (tcsetattr (fd, TCSANOW, &termios) < 0) | ||
| 60 | fail_io ("tcsetattr"); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | /* Make FD non-blocking if NONBLOCKING is true, | ||
| 65 | or blocking if NONBLOCKING is false. */ | ||
| 66 | static void | ||
| 67 | make_nonblocking (int fd, bool nonblocking) | ||
| 68 | { | ||
| 69 | int flags = fcntl (fd, F_GETFL); | ||
| 70 | if (flags < 0) | ||
| 71 | fail_io ("fcntl"); | ||
| 72 | if (nonblocking) | ||
| 73 | flags |= O_NONBLOCK; | ||
| 74 | else | ||
| 75 | flags &= ~O_NONBLOCK; | ||
| 76 | if (fcntl (fd, F_SETFL, flags) < 0) | ||
| 77 | fail_io ("fcntl"); | ||
| 78 | } | ||
| 79 | |||
| 80 | /* Handle a read or write on *FD, which is the socket if | ||
| 81 | FD_IS_SOCK is true, that returned end-of-file or error | ||
| 82 | indication RETVAL. The system call is named CALL, for use in | ||
| 83 | error messages. Returns true if processing may continue, | ||
| 84 | false if we're all done. */ | ||
| 85 | static bool | ||
| 86 | handle_error (ssize_t retval, int *fd, bool fd_is_sock, const char *call) | ||
| 87 | { | ||
| 88 | if (retval == 0) | ||
| 89 | { | ||
| 90 | if (fd_is_sock) | ||
| 91 | return false; | ||
| 92 | else | ||
| 93 | { | ||
| 94 | *fd = -1; | ||
| 95 | return true; | ||
| 96 | } | ||
| 97 | } | ||
| 98 | else | ||
| 99 | fail_io (call); | ||
| 100 | } | ||
| 101 | |||
| 102 | /* Copies data from stdin to SOCK and from SOCK to stdout until no | ||
| 103 | more data can be read or written. */ | ||
| 104 | static void | ||
| 105 | relay (int sock) | ||
| 106 | { | ||
| 107 | struct pipe | ||
| 108 | { | ||
| 109 | int in, out; | ||
| 110 | char buf[BUFSIZ]; | ||
| 111 | size_t size, ofs; | ||
| 112 | bool active; | ||
| 113 | }; | ||
| 114 | struct pipe pipes[2]; | ||
| 115 | |||
| 116 | /* In case stdin is a file, go back to the beginning. | ||
| 117 | This allows replaying the input on reset. */ | ||
| 118 | lseek (STDIN_FILENO, 0, SEEK_SET); | ||
| 119 | |||
| 120 | /* Make SOCK, stdin, and stdout non-blocking. */ | ||
| 121 | make_nonblocking (sock, true); | ||
| 122 | make_nonblocking (STDIN_FILENO, true); | ||
| 123 | make_nonblocking (STDOUT_FILENO, true); | ||
| 124 | |||
| 125 | /* Configure noncanonical mode on stdin to avoid waiting for | ||
| 126 | end-of-line. */ | ||
| 127 | make_noncanon (STDIN_FILENO, 1, 0); | ||
| 128 | |||
| 129 | memset (pipes, 0, sizeof pipes); | ||
| 130 | pipes[0].in = STDIN_FILENO; | ||
| 131 | pipes[0].out = sock; | ||
| 132 | pipes[1].in = sock; | ||
| 133 | pipes[1].out = STDOUT_FILENO; | ||
| 134 | |||
| 135 | while (pipes[0].in != -1 || pipes[1].in != -1 | ||
| 136 | || (pipes[1].size && pipes[1].out != -1)) | ||
| 137 | { | ||
| 138 | fd_set read_fds, write_fds; | ||
| 139 | sigset_t empty_set; | ||
| 140 | int retval; | ||
| 141 | int i; | ||
| 142 | |||
| 143 | FD_ZERO (&read_fds); | ||
| 144 | FD_ZERO (&write_fds); | ||
| 145 | for (i = 0; i < 2; i++) | ||
| 146 | { | ||
| 147 | struct pipe *p = &pipes[i]; | ||
| 148 | |||
| 149 | /* Don't do anything with the stdin->sock pipe until we | ||
| 150 | have some data for the sock->stdout pipe. If we get | ||
| 151 | too eager, vmplayer will throw away our input. */ | ||
| 152 | if (i == 0 && !pipes[1].active) | ||
| 153 | continue; | ||
| 154 | |||
| 155 | if (p->in != -1 && p->size + p->ofs < sizeof p->buf) | ||
| 156 | FD_SET (p->in, &read_fds); | ||
| 157 | if (p->out != -1 && p->size > 0) | ||
| 158 | FD_SET (p->out, &write_fds); | ||
| 159 | } | ||
| 160 | sigemptyset (&empty_set); | ||
| 161 | retval = pselect (FD_SETSIZE, &read_fds, &write_fds, NULL, NULL, | ||
| 162 | &empty_set); | ||
| 163 | if (retval < 0) | ||
| 164 | { | ||
| 165 | if (errno == EINTR) | ||
| 166 | { | ||
| 167 | /* Child died. Do final relaying. */ | ||
| 168 | struct pipe *p = &pipes[1]; | ||
| 169 | if (p->out == -1) | ||
| 170 | exit (0); | ||
| 171 | make_nonblocking (STDOUT_FILENO, false); | ||
| 172 | for (;;) | ||
| 173 | { | ||
| 174 | ssize_t n; | ||
| 175 | |||
| 176 | /* Write buffer. */ | ||
| 177 | while (p->size > 0) | ||
| 178 | { | ||
| 179 | n = write (p->out, p->buf + p->ofs, p->size); | ||
| 180 | if (n < 0) | ||
| 181 | fail_io ("write"); | ||
| 182 | else if (n == 0) | ||
| 183 | fail_io ("zero-length write"); | ||
| 184 | p->ofs += n; | ||
| 185 | p->size -= n; | ||
| 186 | } | ||
| 187 | p->ofs = 0; | ||
| 188 | |||
| 189 | p->size = n = read (p->in, p->buf, sizeof p->buf); | ||
| 190 | if (n <= 0) | ||
| 191 | exit (0); | ||
| 192 | } | ||
| 193 | } | ||
| 194 | fail_io ("select"); | ||
| 195 | } | ||
| 196 | |||
| 197 | for (i = 0; i < 2; i++) | ||
| 198 | { | ||
| 199 | struct pipe *p = &pipes[i]; | ||
| 200 | if (p->in != -1 && FD_ISSET (p->in, &read_fds)) | ||
| 201 | { | ||
| 202 | ssize_t n = read (p->in, p->buf + p->ofs + p->size, | ||
| 203 | sizeof p->buf - p->ofs - p->size); | ||
| 204 | if (n > 0) | ||
| 205 | { | ||
| 206 | p->active = true; | ||
| 207 | p->size += n; | ||
| 208 | if (p->size == BUFSIZ && p->ofs != 0) | ||
| 209 | { | ||
| 210 | memmove (p->buf, p->buf + p->ofs, p->size); | ||
| 211 | p->ofs = 0; | ||
| 212 | } | ||
| 213 | } | ||
| 214 | else if (!handle_error (n, &p->in, p->in == sock, "read")) | ||
| 215 | return; | ||
| 216 | } | ||
| 217 | if (p->out != -1 && FD_ISSET (p->out, &write_fds)) | ||
| 218 | { | ||
| 219 | ssize_t n = write (p->out, p->buf + p->ofs, p->size); | ||
| 220 | if (n > 0) | ||
| 221 | { | ||
| 222 | p->ofs += n; | ||
| 223 | p->size -= n; | ||
| 224 | if (p->size == 0) | ||
| 225 | p->ofs = 0; | ||
| 226 | } | ||
| 227 | else if (!handle_error (n, &p->out, p->out == sock, "write")) | ||
| 228 | return; | ||
| 229 | } | ||
| 230 | } | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | static void | ||
| 235 | sigchld_handler (int signo __attribute__ ((unused))) | ||
| 236 | { | ||
| 237 | /* Nothing to do. */ | ||
| 238 | } | ||
| 239 | |||
| 240 | int | ||
| 241 | main (int argc __attribute__ ((unused)), char *argv[]) | ||
| 242 | { | ||
| 243 | pid_t pid; | ||
| 244 | struct itimerval zero_itimerval; | ||
| 245 | struct sockaddr_un sun; | ||
| 246 | sigset_t sigchld_set; | ||
| 247 | int sock; | ||
| 248 | |||
| 249 | if (argc < 3) | ||
| 250 | { | ||
| 251 | fprintf (stderr, | ||
| 252 | "usage: squish-unix SOCKET COMMAND [ARG]...\n" | ||
| 253 | "Squishes both stdin and stdout into a single Unix domain\n" | ||
| 254 | "socket named SOCKET, and runs COMMAND as a subprocess.\n"); | ||
| 255 | return EXIT_FAILURE; | ||
| 256 | } | ||
| 257 | |||
| 258 | /* Create socket. */ | ||
| 259 | sock = socket (PF_LOCAL, SOCK_STREAM, 0); | ||
| 260 | if (sock < 0) | ||
| 261 | fail_io ("socket"); | ||
| 262 | |||
| 263 | /* Configure socket. */ | ||
| 264 | sun.sun_family = AF_LOCAL; | ||
| 265 | strncpy (sun.sun_path, argv[1], sizeof sun.sun_path); | ||
| 266 | sun.sun_path[sizeof sun.sun_path - 1] = '\0'; | ||
| 267 | if (unlink (sun.sun_path) < 0 && errno != ENOENT) | ||
| 268 | fail_io ("unlink"); | ||
| 269 | if (bind (sock, (struct sockaddr *) &sun, | ||
| 270 | (offsetof (struct sockaddr_un, sun_path) | ||
| 271 | + strlen (sun.sun_path) + 1)) < 0) | ||
| 272 | fail_io ("bind"); | ||
| 273 | |||
| 274 | /* Listen on socket. */ | ||
| 275 | if (listen (sock, 1) < 0) | ||
| 276 | fail_io ("listen"); | ||
| 277 | |||
| 278 | /* Block SIGCHLD and set up a handler for it. */ | ||
| 279 | sigemptyset (&sigchld_set); | ||
| 280 | sigaddset (&sigchld_set, SIGCHLD); | ||
| 281 | if (sigprocmask (SIG_BLOCK, &sigchld_set, NULL) < 0) | ||
| 282 | fail_io ("sigprocmask"); | ||
| 283 | if (signal (SIGCHLD, sigchld_handler) == SIG_ERR) | ||
| 284 | fail_io ("signal"); | ||
| 285 | |||
| 286 | /* Save the virtual interval timer, which might have been set | ||
| 287 | by the process that ran us. It really should be applied to | ||
| 288 | our child process. */ | ||
| 289 | memset (&zero_itimerval, 0, sizeof zero_itimerval); | ||
| 290 | if (setitimer (ITIMER_VIRTUAL, &zero_itimerval, NULL) < 0) | ||
| 291 | fail_io ("setitimer"); | ||
| 292 | |||
| 293 | pid = fork (); | ||
| 294 | if (pid < 0) | ||
| 295 | fail_io ("fork"); | ||
| 296 | else if (pid != 0) | ||
| 297 | { | ||
| 298 | /* Running in parent process. */ | ||
| 299 | make_nonblocking (sock, true); | ||
| 300 | for (;;) | ||
| 301 | { | ||
| 302 | fd_set read_fds; | ||
| 303 | sigset_t empty_set; | ||
| 304 | int retval; | ||
| 305 | int conn; | ||
| 306 | |||
| 307 | /* Wait for connection. */ | ||
| 308 | FD_ZERO (&read_fds); | ||
| 309 | FD_SET (sock, &read_fds); | ||
| 310 | sigemptyset (&empty_set); | ||
| 311 | retval = pselect (sock + 1, &read_fds, NULL, NULL, NULL, &empty_set); | ||
| 312 | if (retval < 0) | ||
| 313 | { | ||
| 314 | if (errno == EINTR) | ||
| 315 | break; | ||
| 316 | fail_io ("select"); | ||
| 317 | } | ||
| 318 | |||
| 319 | /* Accept connection. */ | ||
| 320 | conn = accept (sock, NULL, NULL); | ||
| 321 | if (conn < 0) | ||
| 322 | fail_io ("accept"); | ||
| 323 | |||
| 324 | /* Relay connection. */ | ||
| 325 | relay (conn); | ||
| 326 | close (conn); | ||
| 327 | } | ||
| 328 | return 0; | ||
| 329 | } | ||
| 330 | else | ||
| 331 | { | ||
| 332 | /* Running in child process. */ | ||
| 333 | if (close (sock) < 0) | ||
| 334 | fail_io ("close"); | ||
| 335 | execvp (argv[2], argv + 2); | ||
| 336 | fail_io ("exec"); | ||
| 337 | } | ||
| 338 | } | ||
