bug-parted
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 3/4] tests: rewrite/complete GPT-munging perl script


From: Jim Meyering
Subject: [PATCH 3/4] tests: rewrite/complete GPT-munging perl script
Date: Thu, 3 Nov 2011 22:18:57 +0100

From: Jim Meyering <address@hidden>

Rewrite and complete this script.
It was incomplete and buggy.  Now it works.
* tests/gpt-header-munge: Rename from ...
* tests/perl-munge-9-PTE-table: ...this.
---
 tests/gpt-header-munge       |  279 ++++++++++++++++++++++++++++++++++++++++++
 tests/perl-munge-9-PTE-table |   85 -------------
 2 files changed, 279 insertions(+), 85 deletions(-)
 create mode 100755 tests/gpt-header-munge
 delete mode 100755 tests/perl-munge-9-PTE-table

diff --git a/tests/gpt-header-munge b/tests/gpt-header-munge
new file mode 100755
index 0000000..297e8ae
--- /dev/null
+++ b/tests/gpt-header-munge
@@ -0,0 +1,279 @@
+#!/usr/bin/perl -w
+# Change the size of a GPT image's partition array.
+
+# The vast majority of GPT partition tables have an 128-entry partition array.
+# However, we get reports of ZFS-related arrays with a mere 9 entries, and
+# some others with 140, and parted could not handle either of those because
+# it was effectively hard-coding the 128.
+# This script takes as input a GPT image that might be created by
+# parted itself, and transforms it into one with a different number, N,
+# of partition array entries.  That involves the following steps:
+# - poke that value of N into the 4-byte "Number of partition entries" slot,
+# - compute the crc32 of the N partition table entries,
+# - poke the resulting value into its slot,
+# - recompute the GPT header's CRC32 checksum and poke that into its slot.
+# Do the above for both the primary and the backup GPT headers.
+
+use strict;
+use warnings;
+use Digest::CRC qw(crc32);
+use List::Util qw(max);
+
+(my $ME = $0) =~ s|.*/||;
+my $VERSION = '1.0';
+
+# Technically we shouldn't hard-code this, since it's specified
+# as the little-endian number in bytes 12..15 of the GPT header.
+my $gpt_header_len = 92;
+
+# Size of a partition array entry, in bytes.
+# This too is specified in the GPT header, but AFAIK, no one changes it.
+my $pe_size = 128;
+
+# Sector size; FIXME: make this an option; default to 512.
+my $ss = 512;
+
+# Sector number of the backup GPT header, to be read from the primary header.
+my $backup_LBA;
+
+# Given a GPT header $B, extract the my_LBA/backup_LBA sector number.
+sub curr_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 24, 8)) }
+sub backup_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 32, 8)) }
+
+# Given a GPT header $B, return its "partition entries starting LBA".
+sub pe_start_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 72, 8)) }
+
+sub round_up_to_ss ($)
+{
+  my ($n) = @_;
+  return $n + $ss - $n % $ss;
+}
+
+# Return the byte offset of the start of the specified partition array.
+sub partition_array_start_offset ($$)
+{
+  my ($pri_or_backup, $n_pe) = @_;
+  $pri_or_backup eq 'primary'
+    and return 2 * $ss;
+
+  # Backup
+  return $backup_LBA * $ss - round_up_to_ss ($n_pe * $pe_size);
+}
+
+# Calculate and return the specified partition array crc32 checksum.
+sub partition_array_crc ($$$)
+{
+  my ($pri_or_backup, $n_pe, $in) = @_;
+  open F, '<', $in
+    or die "$ME: failed to open $in: $!\n";
+
+  # Seek to start of partition array.
+  my $off = partition_array_start_offset $pri_or_backup, $n_pe;
+  sysseek (F, $off, 0)
+    or die "$ME: $in: failed to seek to $off: $!\n";
+
+  # Read the array.
+  my $p;
+  my $pe_buf;
+  my $n = $n_pe * $pe_size;
+  ($p = sysread F, $pe_buf, $n) && $p == $n
+    or die "$ME: $in: failed to read $pri_or_backup partition array:($p:$n) 
$!\n";
+  close F;
+
+  return crc32 $pe_buf;
+}
+
+# Verify the initial CRC of BUF.
+sub check_GPT_header ($$$)
+{
+  my ($pri_or_backup, $in, $buf) = @_;
+
+  my $curr = curr_LBA $buf;
+  my $backup = backup_LBA $buf;
+  ($pri_or_backup eq 'primary') == ($curr == 1)
+    or die "$ME: $in: invalid curr_LBA($curr) in $pri_or_backup header\n";
+  ($pri_or_backup eq 'primary') == (34 < $backup)
+    or die "$ME: $in: invalid backup_LBA($backup) in $pri_or_backup header\n";
+
+  $pri_or_backup eq 'backup' && $backup != 1
+    and die "$ME: $in: the backup_LBA in the backup header must be 1\n";
+
+  # A primary partition's "partition entries starting LBA" must be 2.
+  if ($pri_or_backup eq 'primary')
+    {
+      my $p = pe_start_LBA $buf;
+      $p == 2
+       or die "$ME: $in: primary header's PE start LBA is $p (should be 2)\n";
+    }
+
+  # Save a copy of the CRC, then zero that field, bytes 16..19:
+  my $orig_crc = unpack ('L', substr ($buf, 16, 4));
+  substr ($buf, 16, 4) = "\0" x 4;
+
+  # Compute CRC32 of header: it'd better match.
+  my $crc = crc32($buf);
+  $orig_crc == $crc
+    or die "$ME: $in: cannot reproduce $pri_or_backup GPT header's CRC32\n";
+}
+
+# Poke the $N_PE value into $$BUF's number-of-partition-entries slot.
+sub poke_n_pe ($$)
+{
+  my ($buf, $n_pe) = @_;
+
+  # Poke the little-endian value into place.
+  substr ($$buf, 80, 4) = pack ('L<', $n_pe);
+}
+
+# Compute/set partition-array CRC (given $N_PE), then compute a new
+# header-CRC and poke it into its position, too.
+sub set_CRCs ($$$$)
+{
+  my ($pri_or_backup, $buf, $in, $n_pe) = @_;
+
+  # Compute CRC of primary partition array and put it in substr ($pri, 88, 4)
+  my $pa_crc = partition_array_crc $pri_or_backup, $n_pe, $in;
+  substr ($$buf, 88, 4) = pack ('L', $pa_crc);
+
+  # In the backup header, we must also set the 8-byte "Partition entries
+  # starting LBA number" field to reflect our new value of $n_pe.
+  if ($pri_or_backup eq 'backup')
+    {
+      my $off = partition_array_start_offset $pri_or_backup, $n_pe;
+      $off % $ss == 0
+       or die "$ME: internal error: starting LBA byte offset($off) is"
+         . " not a multiple of $ss\n";
+      my $lba = $off / 512;
+      substr ($$buf, 72, 8) = pack ('Q<', $lba);
+    }
+
+  # Before we compute the checksum, we must zero-out the 4-byte
+  # slot into which we'll store the result.
+  substr ($$buf, 16, 4) = "\0" x 4;
+  my $crc = crc32($$buf);
+  substr ($$buf, 16, 4) = pack ('L', $crc);
+}
+
+sub usage ($)
+{
+  my ($exit_code) = @_;
+  my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
+  if ($exit_code != 0)
+    {
+      print $STREAM "Try `$ME --help' for more information.\n";
+    }
+  else
+    {
+      print $STREAM <<EOF;
+Usage: $ME [OPTIONS] FILE
+Change the number of GPT partition array entries in FILE.
+
+This option must be specified:
+
+   --n-partition-array-entries=N  FIXME
+
+The following are optional:
+
+   --help             display this help and exit
+   --version          output version information and exit
+
+EXAMPLE:
+
+  dd if=/dev/null of=F bs=1 seek=6MB
+  parted -s F mklabel gpt
+  $ME --n=9 F
+
+EOF
+    }
+  exit $exit_code;
+}
+
+{
+  my $n_partition_entries;
+
+  use Getopt::Long;
+  GetOptions
+    (
+     'n-partition-array-entries=i' => \$n_partition_entries,
+
+     help => sub { usage 0 },
+     version => sub { print "$ME version $VERSION\n"; exit },
+    ) or usage 1;
+
+  defined $n_partition_entries
+    or (warn "$ME: --n-partition-array-entries=N not specified\n"), usage 1;
+
+  # Require sensible number:
+  # It must either be <= 128, or else a multiple of 4 so that at 128 bytes 
each,
+  # this array fully occupies a whole number of 512-byte sectors.
+  1 <= $n_partition_entries
+    && ($n_partition_entries <= 128
+       || $n_partition_entries % 4 == 0)
+    or die "$ME: invalid number of partition entries: $n_partition_entries\n";
+
+  @ARGV == 1
+    or (warn "$ME: no file specified\n"), usage 1;
+
+  my $in = $ARGV[0];
+  open F, '<', $in
+    or die "$ME: failed to open $in: $!\n";
+
+  # Get length and perform some basic sanity checks.
+  my $len = sysseek (F, 0, 2);
+  defined $len
+    or die "$ME: $in: failed to seek to EOF: $!\n";
+  my $min_n_sectors = 34 + 33 + ($n_partition_entries * $pe_size + $ss - 1) / 
$ss;
+  my $n_sectors = int (($len + $ss - 1) / $ss);
+  $n_sectors < $min_n_sectors
+    and die "$ME: $in: image file is too small to contain a GPT image\n";
+  $len % $ss == 0
+    or die "$ME: $in: size is not a multiple of $ss: $!\n";
+
+  # Skip 1st sector.
+  sysseek (F, $ss, 0)
+    or die "$ME: $in: failed to seek to byte $ss: $!\n";
+
+  # Read the primary GPT header.
+  my $p;
+  my $pri;
+  ($p = sysread F, $pri, $gpt_header_len) && $p == $gpt_header_len
+    or die "$ME: $in: failed to read the primary GPT header: $!\n";
+
+  $backup_LBA = unpack ('Q<', substr ($pri, 32, 8));
+
+  # Seek-to and read the backup GPT header.
+  sysseek (F, $backup_LBA * $ss, 0)
+    or die "$ME: $in: failed to seek to backup LBA $backup_LBA: $!\n";
+  my $backup;
+  ($p = sysread F, $backup, $gpt_header_len) && $p == $gpt_header_len
+    or die "$ME: $in: read failed: $!\n";
+
+  close F;
+
+  check_GPT_header ('primary', $in, $pri);
+  check_GPT_header ('backup',  $in, $backup);
+
+  poke_n_pe (\$pri,    $n_partition_entries);
+  poke_n_pe (\$backup, $n_partition_entries);
+
+  # set both PE CRC and header CRCs:
+  set_CRCs 'primary', \$pri,    $in, $n_partition_entries;
+  set_CRCs 'backup',  \$backup, $in, $n_partition_entries;
+
+  # Write both headers back to the file:
+  open F, '+<', $in
+    or die "$ME: failed to open $in: $!\n";
+
+  sysseek (F, $ss, 0)
+    or die "$ME: $in: failed to seek to byte $ss: $!\n";
+  syswrite F, $pri
+    or die "$ME: $in: failed to write primary header: $!\n";
+
+  sysseek (F, $backup_LBA * $ss, 0)
+    or die "$ME: $in: failed to seek to backup LBA $backup_LBA: $!\n";
+  syswrite F, $backup
+    or die "$ME: $in: failed to write backup header: $!\n";
+
+  close F
+    or die "$ME: failed to close $in: $!\n";
+  }
diff --git a/tests/perl-munge-9-PTE-table b/tests/perl-munge-9-PTE-table
deleted file mode 100755
index 9d65f94..0000000
--- a/tests/perl-munge-9-PTE-table
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/usr/bin/perl -w
-# Read a GPT header, poke "9" into the 4-byte "Number of partition entries" 
slot
-# compute crc32 of 9 partition table entries and poke that into place,
-# recompute the primary header's CRC32 checksum and poke that into its slot.
-
-# Set up:
-# dev=dev-file
-# dd if=/dev/null of=$dev bs=1 seek=1026MB
-# parted -s -- $dev mklabel gpt
-
-use strict;
-use warnings;
-use Digest::CRC qw(crc32);
-
-(my $ME = $0) =~ s|.*/||;
-
-my $in = 'dev-file';
-my $n_partition_entries = 9;
-
-sub partition_array_crc ($$)
-{
-  my ($n_entries, $in) = @_;
-  open F, '<', $in
-    or die "$ME: failed to open $in: $!\n";
-
-  # Skip 1st 512-byte sector.
-  sysseek (F, 2*512, 0)
-    or die "$ME: $in: failed to seek: $!\n";
-
-  # Read the array.
-  my $p;
-  my $buf;
-  my $n = $n_partition_entries * 128;
-  ($p = sysread F, $buf, $n) && $p == $n
-    or die "$ME: $in: failed to read partition array: $!\n";
-  close F;
-
-  return crc32 $buf;
-}
-
-
-open F, '<', $in
-  or die "$ME: failed to open $in: $!\n";
-
-# Skip 1st 512-byte sector.
-sysseek (F, 512, 0)
-  or die "$ME: $in: failed to seek to byte 512: $!\n";
-
-# Read the primary GPT header.
-my $p;
-my $buf;
-($p = sysread F, $buf, 92) && $p == 92
-  or die "$ME: $in: read failed: $!\n";
-close F;
-
-print substr ($buf, 0, 8), "\n";
-
-# Zero the CRC32 field, bytes 16..19
-my $orig_crc = unpack ('L', substr ($buf, 16, 4));
-substr ($buf, 16, 4) = "\0" x 4;
-
-# Before proceeding, compute CRC32 of header as a sanity check.
-my $crc = crc32($buf);
-$orig_crc == $crc
-  or die "$ME: $in: cannot reproduce GPT header's CRC32\n";
-
-# poke the $n_partition_entries value into place
-substr ($buf, 80, 4) = pack ('L', $n_partition_entries);
-
-# Compute CRC of partition array and put it in substr ($buf, 88, 4)
-my $pa_crc = partition_array_crc $n_partition_entries, $in;
-substr ($buf, 88, 4) = pack ('L', $pa_crc);
-
-$crc = crc32($buf);
-substr ($buf, 16, 4) = pack ('L', $crc);
-
-# Write it back to the file:
-open F, '+<', $in
-  or die "$ME: failed to open $in: $!\n";
-sysseek (F, 512, 0)
-  or die "$ME: $in: failed to seek to byte 512: $!\n";
-print F $buf
-  or die "$ME: $in: write failed: $!\n";
-close F
-  or die "$ME: failed to close $in: $!\n";
-- 
1.7.8.rc0.32.g87bf9




reply via email to

[Prev in Thread] Current Thread [Next in Thread]