summaryrefslogtreecommitdiffstats
path: root/pintos-progos/utils
diff options
context:
space:
mode:
Diffstat (limited to 'pintos-progos/utils')
-rw-r--r--pintos-progos/utils/.gitignore3
-rw-r--r--pintos-progos/utils/Makefile11
-rw-r--r--pintos-progos/utils/Pintos.pm491
-rwxr-xr-xpintos-progos/utils/backtrace106
-rwxr-xr-xpintos-progos/utils/pintos955
-rwxr-xr-xpintos-progos/utils/pintos-gdb21
-rwxr-xr-xpintos-progos/utils/pintos-mkdisk134
-rw-r--r--pintos-progos/utils/pintos-set-cmdline42
-rw-r--r--pintos-progos/utils/setitimer-helper.c49
-rw-r--r--pintos-progos/utils/squish-pty.c355
-rw-r--r--pintos-progos/utils/squish-unix.c338
11 files changed, 2505 insertions, 0 deletions
diff --git a/pintos-progos/utils/.gitignore b/pintos-progos/utils/.gitignore
new file mode 100644
index 0000000..b96f278
--- /dev/null
+++ b/pintos-progos/utils/.gitignore
@@ -0,0 +1,3 @@
1setitimer-helper
2squish-pty
3squish-unix
diff --git a/pintos-progos/utils/Makefile b/pintos-progos/utils/Makefile
new file mode 100644
index 0000000..46a9124
--- /dev/null
+++ b/pintos-progos/utils/Makefile
@@ -0,0 +1,11 @@
1all: setitimer-helper squish-pty squish-unix
2
3CC = gcc
4CFLAGS = -Wall -W
5LDFLAGS = -lm
6setitimer-helper: setitimer-helper.o
7squish-pty: squish-pty.o
8squish-unix: squish-unix.o
9
10clean:
11 rm -f *.o setitimer-helper squish-pty squish-unix
diff --git a/pintos-progos/utils/Pintos.pm b/pintos-progos/utils/Pintos.pm
new file mode 100644
index 0000000..70df40d
--- /dev/null
+++ b/pintos-progos/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.
5our $LOADER_SIZE = 314;
6
7# Partition types.
8my (%role2type) = (KERNEL => 0x20,
9 FILESYS => 0x21,
10 SCRATCH => 0x22,
11 SWAP => 0x23);
12my (%type2role) = reverse %role2type;
13
14# Order of roles within a given disk.
15our (@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.
39our (%parts);
40
41# set_part($opt, $arg)
42#
43# For use as a helper function for Getopt::Long::GetOptions to set
44# disk sources.
45sub 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'.
63sub 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.
100sub 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.
116sub 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', ...]
148sub 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.
244sub 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.
268sub 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.
279sub 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.
296sub 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.
309sub 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
316sub 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.
332sub 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.
341sub 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.
350sub 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.
360sub 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.
379sub 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.
389sub 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.
415sub 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.
434sub 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.
463sub 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.
473sub 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.
483sub max {
484 my ($max) = $_[0];
485 foreach (@_[1..$#_]) {
486 $max = $_ if $_ > $max;
487 }
488 return $max;
489}
490
4911;
diff --git a/pintos-progos/utils/backtrace b/pintos-progos/utils/backtrace
new file mode 100755
index 0000000..95e422f
--- /dev/null
+++ b/pintos-progos/utils/backtrace
@@ -0,0 +1,106 @@
1#! /usr/bin/perl -w
2
3use strict;
4
5# Check command line.
6if (grep ($_ eq '-h' || $_ eq '--help', @ARGV)) {
7 print <<'EOF';
8backtrace, for converting raw addresses into symbolic backtraces
9usage: backtrace [BINARY]... ADDRESS...
10where 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
13If no BINARY is unspecified, the default is the first of kernel.o or
14build/kernel.o that exists. If multiple binaries are specified, each
15symbol printed is from the first binary that contains a match.
16
17The ADDRESS list should be taken from the "Call stack:" printed by the
18kernel. Read "Backtraces" in the "Debugging Tools" chapter of the
19Pintos documentation for more information.
20EOF
21 exit 0;
22}
23die "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);
28s/\.$// foreach @ARGV;
29
30# Find binaries.
31my (@binaries);
32while ($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}
37if (!@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.
50my ($a2l) = search_path ("i386-elf-addr2line") || search_path ("addr2line");
51if (!$a2l) {
52 die "backtrace: neither `i386-elf-addr2line' nor `addr2line' in PATH\n";
53}
54sub 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.
64my (@locs) = map ({ADDR => $_}, @ARGV);
65for 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.
83my ($cur_binary);
84for 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/pintos-progos/utils/pintos b/pintos-progos/utils/pintos
new file mode 100755
index 0000000..91f73ad
--- /dev/null
+++ b/pintos-progos/utils/pintos
@@ -0,0 +1,955 @@
1#! /usr/bin/perl -w
2
3use strict;
4use POSIX;
5use Fcntl;
6use File::Temp 'tempfile';
7use Getopt::Long qw(:config bundling);
8use Fcntl qw(SEEK_SET SEEK_CUR);
9
10# Read Pintos.pm from the same directory as this program.
11BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
12
13# Command-line options.
14our ($start_time) = time ();
15our ($sim); # Simulator: bochs, qemu, or player.
16our ($debug) = "none"; # Debugger: none, monitor, or gdb.
17our ($mem) = 4; # Physical RAM in MB.
18our ($serial) = 1; # Use serial port for input and output?
19our ($vga); # VGA output: window, terminal, or none.
20our ($jitter); # Seed for random timer interrupts, if set.
21our ($realtime); # Synchronize timer interrupts with real time?
22our ($timeout); # Maximum runtime in seconds, if set.
23our ($kill_on_failure); # Abort quickly on test failure?
24our ($kernel_test); # Run kernel test instead of user program
25our (@puts); # Files to copy into the VM.
26our (@gets); # Files to copy out of the VM.
27our ($as_ref); # Reference to last addition to @gets or @puts.
28our (@kernel_args); # Arguments to pass to kernel.
29our (%parts); # Partitions.
30our ($make_disk); # Name of disk to create.
31our ($tmp_disk) = 1; # Delete $make_disk after run?
32our (@disks); # Extra disk images to pass to simulator.
33our ($loader_fn); # Bootstrap loader.
34our (%geometry); # IDE disk geometry.
35our ($align); # Partition alignment.
36
37parse_command_line ();
38prepare_scratch_disk ();
39find_disks ();
40run_vm ();
41finish_scratch_disk ();
42
43exit 0;
44
45# Parses the command line.
46sub 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.
122sub usage {
123 my ($exitcode) = @_;
124 $exitcode = 1 unless defined $exitcode;
125 print <<'EOF';
126pintos, a utility for running Pintos in a simulator
127Usage: pintos [OPTION...] -- [ARGUMENT...]
128where each OPTION is one of the following options
129 and each ARGUMENT is passed to Pintos kernel verbatim.
130Simulator selection:
131 --bochs (default) Use Bochs as simulator
132 --qemu Use QEMU as simulator
133 --player Use VMware Player as simulator
134Debugger selection:
135 --no-debug (default) No debugger
136 --monitor Debug with simulator's monitor
137 --gdb Debug with gdb
138Display 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)
142Timing options: (Bochs only)
143 -j SEED Randomize timer interrupts
144 -r, --realtime Use realistic, not reproducible, timings
145Testing 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.
152Configuration options:
153 -m, --mem=N Give Pintos N MB physical RAM (default: 4)
154File 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
158Partition 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.)
163Disk 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)
166Advanced 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
175Other options:
176 -h, --help Display this help message.
177EOF
178 exit $exitcode;
179}
180
181# Sets the simulator.
182sub 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.
190sub 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.
198sub 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.
207sub 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.
216sub 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.
223sub 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.
231sub 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.
238sub 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.
247sub 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.
264sub 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.
339sub 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.
378sub 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.
417sub 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.
430sub 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.
442sub 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.
496sub 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.
541sub 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.
554sub 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;
568romimage: file=\$BXSHARE/BIOS-bochs-latest
569vgaromimage: file=\$BXSHARE/VGABIOS-lgpl-latest
570boot: disk
571cpu: ips=1000000
572megs: $mem
573log: bochsout.txt
574panic: action=fatal
575user_shortcut: keys=ctrlaltdel
576EOF
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
616sub 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.
627sub 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.
651sub player_unsup {
652 my ($flag) = @_;
653 print "warning: no support for $flag with VMware Player\n";
654}
655
656# Runs VMware Player.
657sub 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
672config.version = 8
673guestOS = "linux"
674memsize = $mem
675floppy0.present = FALSE
676usb.present = FALSE
677sound.present = FALSE
678gui.exitAtPowerOff = TRUE
679gui.exitOnCLIHLT = TRUE
680gui.powerOnAtStartUp = TRUE
681EOF
682
683 print VMX <<EOF if $serial;
684serial0.present = TRUE
685serial0.fileType = "pipe"
686serial0.fileName = "pintos.socket"
687serial0.pipe.endPoint = "client"
688serial0.tryNoRxLoss = "TRUE"
689EOF
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"
702EOF
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;
713version=1
714CID=$cid
715parentCID=ffffffff
716createType="monolithicFlat"
717
718RW $geom{CAPACITY} FLAT "$dsk" 0
719
720# The Disk Data Base
721#DDB
722
723ddb.adapterType = "ide"
724ddb.virtualHWVersion = "4"
725ddb.toolsVersion = "2"
726ddb.geometry.cylinders = "$geom{C}"
727ddb.geometry.heads = "$geom{H}"
728ddb.geometry.sectors = "$geom{S}"
729EOF
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
750sub 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.
764sub 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.
784sub 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.
794sub 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.
880sub 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.
893sub 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.
913sub 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.
919sub 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
937sub 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.
951sub find_in_path {
952 my ($program) = @_;
953 -x "$_/$program" and return $program foreach split (':', $ENV{PATH});
954 return;
955}
diff --git a/pintos-progos/utils/pintos-gdb b/pintos-progos/utils/pintos-gdb
new file mode 100755
index 0000000..9c9555b
--- /dev/null
+++ b/pintos-progos/utils/pintos-gdb
@@ -0,0 +1,21 @@
1#! /bin/sh
2
3# Path to GDB macros file. Customize for your site.
4PINTOS_SRC="$(dirname $(dirname $(which pintos-gdb)))"
5GDBMACROS="${PINTOS_SRC}/misc/gdb-macros"
6
7# Choose correct GDB.
8if command -v i386-elf-gdb >/dev/null 2>&1; then
9 GDB=i386-elf-gdb
10else
11 GDB=gdb
12fi
13
14# Run GDB.
15if test -f "$GDBMACROS"; then
16 exec $GDB -x "$GDBMACROS" "$@"
17else
18 echo "*** $GDBMACROS does not exist ***"
19 echo "*** Pintos GDB macros will not be available ***"
20 exec $GDB "$@"
21fi
diff --git a/pintos-progos/utils/pintos-mkdisk b/pintos-progos/utils/pintos-mkdisk
new file mode 100755
index 0000000..87b1563
--- /dev/null
+++ b/pintos-progos/utils/pintos-mkdisk
@@ -0,0 +1,134 @@
1#! /usr/bin/perl
2
3use strict;
4use warnings;
5use POSIX;
6use Getopt::Long qw(:config bundling);
7use Fcntl 'SEEK_SET';
8
9# Read Pintos.pm from the same directory as this program.
10BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
11
12our ($disk_fn); # Output disk file name.
13our (%parts); # Partitions.
14our ($format); # "partitioned" (default) or "raw"
15our (%geometry); # IDE disk geometry.
16our ($align); # Align partitions on cylinders?
17our ($loader_fn); # File name of loader.
18our ($include_loader); # Include loader?
19our (@kernel_args); # Kernel arguments.
20
21if (grep ($_ eq '--', @ARGV)) {
22 @kernel_args = @ARGV;
23 @ARGV = ();
24 while ((my $arg = shift (@kernel_args)) ne '--') {
25 push (@ARGV, $arg);
26 }
27}
28
29GetOptions ("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;
51usage (1) if @ARGV != 1;
52
53$disk_fn = $ARGV[0];
54die "$disk_fn: already exists\n" if -e $disk_fn;
55
56# Sets the loader to copy to the MBR.
57sub 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.
65sub 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);
74die "can't write loader to raw disk\n" if $include_loader && $format eq 'raw';
75die "can't write command-line arguments without --loader or --kernel\n"
76 if @kernel_args && !$include_loader;
77print 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.
82my ($disk_handle);
83open ($disk_handle, '>', $disk_fn) or die "$disk_fn: create: $!\n";
84
85# Read loader.
86my ($loader);
87$loader = read_loader ($loader_fn) if $include_loader;
88
89# Write disk.
90my (%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;
98assemble_disk (%disk);
99
100# Done.
101exit 0;
102
103sub usage {
104 print <<'EOF';
105pintos-mkdisk, a utility for creating Pintos virtual disks
106Usage: pintos-mkdisk [OPTIONS] DISK [-- ARGUMENT...]
107where 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.
110Partition 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.)
115Output 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.)
119Partitioned 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
130Other options:
131 -h, --help Display this help message.
132EOF
133 exit ($_[0]);
134}
diff --git a/pintos-progos/utils/pintos-set-cmdline b/pintos-progos/utils/pintos-set-cmdline
new file mode 100644
index 0000000..8c8f702
--- /dev/null
+++ b/pintos-progos/utils/pintos-set-cmdline
@@ -0,0 +1,42 @@
1#! /usr/bin/perl -w
2
3use strict;
4use Fcntl 'SEEK_SET';
5
6# Read Pintos.pm from the same directory as this program.
7BEGIN { my $self = $0; $self =~ s%/+[^/]*$%%; require "$self/Pintos.pm"; }
8
9# Get command-line arguments.
10usage (0) if @ARGV == 1 && $ARGV[0] eq '--help';
11usage (1) if @ARGV < 2 || $ARGV[1] ne '--';
12my ($disk, undef, @kernel_args) = @ARGV;
13
14# Open disk.
15my ($handle);
16open ($handle, '+<', $disk) or die "$disk: open: $!\n";
17
18# Check that it's a partitioned disk with a Pintos loader.
19my ($buffer) = read_fully ($handle, $disk, 512);
20unpack ("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.
24our ($LOADER_SIZE);
25sysseek ($handle, $LOADER_SIZE, SEEK_SET) == $LOADER_SIZE
26 or die "$disk: seek: $!\n";
27write_fully ($handle, $disk, make_kernel_command_line (@kernel_args));
28
29# Close disk.
30close ($handle) or die "$disk: close: $!\n";
31
32exit 0;
33
34sub usage {
35 print <<'EOF';
36pintos-set-cmdline, a utility for changing the command line in Pintos disks
37Usage: pintos-set-cmdline DISK -- [ARGUMENT...]
38where DISK is a bootable disk containing a Pintos loader
39 and each ARGUMENT is inserted into the command line written to DISK.
40EOF
41 exit ($_[0]);
42}
diff --git a/pintos-progos/utils/setitimer-helper.c b/pintos-progos/utils/setitimer-helper.c
new file mode 100644
index 0000000..772d736
--- /dev/null
+++ b/pintos-progos/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
10int
11main (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/pintos-progos/utils/squish-pty.c b/pintos-progos/utils/squish-pty.c
new file mode 100644
index 0000000..c8375a5
--- /dev/null
+++ b/pintos-progos/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
19static void
20fail_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. */
27static void
28fail_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. */
45static void
46make_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. */
63static void
64make_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. */
82static bool
83handle_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. */
115static void
116relay (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
245static int dead_child_fd;
246
247static void
248sigchld_handler (int signo __attribute__ ((unused)))
249{
250 if (write (dead_child_fd, "", 1) < 0)
251 _exit (1);
252}
253
254int
255main (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/pintos-progos/utils/squish-unix.c b/pintos-progos/utils/squish-unix.c
new file mode 100644
index 0000000..805205b
--- /dev/null
+++ b/pintos-progos/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
22static void
23fail_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. */
30static void
31fail_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. */
48static void
49make_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. */
66static void
67make_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. */
85static bool
86handle_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. */
104static void
105relay (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
234static void
235sigchld_handler (int signo __attribute__ ((unused)))
236{
237 /* Nothing to do. */
238}
239
240int
241main (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}