summaryrefslogtreecommitdiffstats
path: root/utils/Pintos.pm
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2012-03-27 11:51:08 +0200
committermanuel <manuel@mausz.at>2012-03-27 11:51:08 +0200
commit4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b (patch)
tree868c52e06f207b5ec8a3cc141f4b8b2bdfcc165c /utils/Pintos.pm
parenteae0bd57f0a26314a94785061888d193d186944a (diff)
downloadprogos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.gz
progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.tar.bz2
progos-4f670845ff9ab6c48bcb5f7bf4d4ef6dc3c3064b.zip
reorganize file structure to match the upstream requirements
Diffstat (limited to 'utils/Pintos.pm')
-rw-r--r--utils/Pintos.pm491
1 files changed, 491 insertions, 0 deletions
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.
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;