2 # Change the size of a GPT image's partition array.
4 # The vast majority of GPT partition tables have an 128-entry partition array.
5 # However, we get reports of ZFS-related arrays with a mere 9 entries, and
6 # some others with 140, and parted could not handle either of those because
7 # it was effectively hard-coding the 128.
8 # This script takes as input a GPT image that might be created by
9 # parted itself, and transforms it into one with a different number, N,
10 # of partition array entries. That involves the following steps:
11 # - poke that value of N into the 4-byte "Number of partition entries" slot,
12 # - compute the crc32 of the N partition table entries,
13 # - poke the resulting value into its slot,
14 # - recompute the GPT header's CRC32 checksum and poke that into its slot.
15 # Do the above for both the primary and the backup GPT headers.
19 use Digest::CRC qw(crc32);
20 use List::Util qw(max);
22 (my $ME = $0) =~ s|.*/||;
25 # Technically we shouldn't hard-code this, since it's specified
26 # as the little-endian number in bytes 12..15 of the GPT header.
27 my $gpt_header_len = 92;
29 # Size of a partition array entry, in bytes.
30 # This too is specified in the GPT header, but AFAIK, no one changes it.
36 # Sector number of the backup GPT header, to be read from the primary header.
39 # Given a GPT header $B, extract the my_LBA/backup_LBA sector number.
40 sub curr_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 24, 8)) }
41 sub backup_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 32, 8)) }
43 # Given a GPT header $B, return its "partition entries starting LBA".
44 sub pe_start_LBA($) { my ($b) = @_; unpack ('Q<', substr ($b, 72, 8)) }
46 sub round_up_to_ss ($)
49 return $n + $ss - $n % $ss;
52 # Return the byte offset of the start of the specified partition array.
53 sub partition_array_start_offset ($$)
55 my ($pri_or_backup, $n_pe) = @_;
56 $pri_or_backup eq 'primary'
60 return $backup_LBA * $ss - round_up_to_ss ($n_pe * $pe_size);
63 # Calculate and return the specified partition array crc32 checksum.
64 sub partition_array_crc ($$$)
66 my ($pri_or_backup, $n_pe, $in) = @_;
69 or die "$ME: failed to open $in: $!\n";
71 # Seek to start of partition array.
72 my $off = partition_array_start_offset $pri_or_backup, $n_pe;
74 or die "$ME: $in: failed to seek to $off: $!\n";
79 my $n = $n_pe * $pe_size;
80 ($p = sysread F, $pe_buf, $n) && $p == $n
81 or die "$ME: $in: failed to read $pri_or_backup partition array:($p:$n) $!\n";
86 # Verify the initial CRC of BUF.
87 sub check_GPT_header ($$$)
89 my ($pri_or_backup, $in, $buf) = @_;
91 my $curr = curr_LBA $buf;
92 my $backup = backup_LBA $buf;
93 ($pri_or_backup eq 'primary') == ($curr == 1)
94 or die "$ME: $in: invalid curr_LBA($curr) in $pri_or_backup header\n";
95 ($pri_or_backup eq 'primary') == (34 < $backup)
96 or die "$ME: $in: invalid backup_LBA($backup) in $pri_or_backup header\n";
98 $pri_or_backup eq 'backup' && $backup != 1
99 and die "$ME: $in: the backup_LBA in the backup header must be 1\n";
101 # A primary partition's "partition entries starting LBA" must be 2.
102 if ($pri_or_backup eq 'primary')
104 my $p = pe_start_LBA $buf;
106 or die "$ME: $in: primary header's PE start LBA is $p (should be 2)\n";
109 # Save a copy of the CRC, then zero that field, bytes 16..19:
110 my $orig_crc = unpack ('L', substr ($buf, 16, 4));
111 substr ($buf, 16, 4) = "\0" x 4;
113 # Compute CRC32 of header: it'd better match.
114 my $crc = crc32($buf);
116 or die "$ME: $in: cannot reproduce $pri_or_backup GPT header's CRC32\n";
119 # Poke the $N_PE value into $$BUF's number-of-partition-entries slot.
122 my ($buf, $n_pe) = @_;
124 # Poke the little-endian value into place.
125 substr ($$buf, 80, 4) = pack ('L<', $n_pe);
128 # Compute/set partition-array CRC (given $N_PE), then compute a new
129 # header-CRC and poke it into its position, too.
132 my ($pri_or_backup, $buf, $in, $n_pe) = @_;
134 # Compute CRC of primary partition array and put it in substr ($pri, 88, 4)
135 my $pa_crc = partition_array_crc $pri_or_backup, $n_pe, $in;
136 substr ($$buf, 88, 4) = pack ('L', $pa_crc);
138 # In the backup header, we must also set the 8-byte "Partition entries
139 # starting LBA number" field to reflect our new value of $n_pe.
140 if ($pri_or_backup eq 'backup')
142 my $off = partition_array_start_offset $pri_or_backup, $n_pe;
144 or die "$ME: internal error: starting LBA byte offset($off) is"
145 . " not a multiple of $ss\n";
146 my $lba = $off / $ss;
147 substr ($$buf, 72, 8) = pack ('Q<', $lba);
150 # Before we compute the checksum, we must zero-out the 4-byte
151 # slot into which we'll store the result.
152 substr ($$buf, 16, 4) = "\0" x 4;
153 my $crc = crc32($$buf);
154 substr ($$buf, 16, 4) = pack ('L', $crc);
159 my ($exit_code) = @_;
160 my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
163 print $STREAM "Try `$ME --help' for more information.\n";
168 Usage: $ME [OPTIONS] FILE
169 Change the number of GPT partition array entries in FILE.
171 This option must be specified:
173 --n-partition-array-entries=N FIXME
175 The following are optional:
177 --sector-size=N assume sector size of N bytes (default: 512)
178 --help display this help and exit
179 --version output version information and exit
183 dd if=/dev/null of=F bs=1 seek=6MB
184 parted -s F mklabel gpt
193 my $n_partition_entries;
198 'n-partition-array-entries=i' => \$n_partition_entries,
199 'sector-size=i' => \$ss,
201 help => sub { usage 0 },
202 version => sub { print "$ME version $VERSION\n"; exit },
205 defined $n_partition_entries
206 or (warn "$ME: --n-partition-array-entries=N not specified\n"), usage 1;
211 # Require sensible number:
212 # It must either be <= 128, or else a multiple of 4 so that at 128 bytes each,
213 # this array fully occupies a whole number of 512-byte sectors.
214 1 <= $n_partition_entries
215 && ($n_partition_entries <= 128
216 || $n_partition_entries % 4 == 0)
217 or die "$ME: invalid number of partition entries: $n_partition_entries\n";
220 or (warn "$ME: no file specified\n"), usage 1;
225 or die "$ME: failed to open $in: $!\n";
227 # Get length and perform some basic sanity checks.
228 my $len = sysseek (F, 0, 2);
230 or die "$ME: $in: failed to seek to EOF: $!\n";
231 my $min_n_sectors = 34 + 33 + ($n_partition_entries * $pe_size + $ss - 1) / $ss;
232 my $n_sectors = int (($len + $ss - 1) / $ss);
233 $n_sectors < $min_n_sectors
234 and die "$ME: $in: image file is too small to contain a GPT image\n";
236 or die "$ME: $in: size is not a multiple of $ss: $!\n";
240 or die "$ME: $in: failed to seek to byte $ss: $!\n";
242 # Read the primary GPT header.
245 ($p = sysread F, $pri, $gpt_header_len) && $p == $gpt_header_len
246 or die "$ME: $in: failed to read the primary GPT header: $!\n";
248 $backup_LBA = unpack ('Q<', substr ($pri, 32, 8));
250 # Seek-to and read the backup GPT header.
251 sysseek (F, $backup_LBA * $ss, 0)
252 or die "$ME: $in: failed to seek to backup LBA $backup_LBA: $!\n";
254 ($p = sysread F, $backup, $gpt_header_len) && $p == $gpt_header_len
255 or die "$ME: $in: read failed: $!\n";
259 check_GPT_header ('primary', $in, $pri);
260 check_GPT_header ('backup', $in, $backup);
262 poke_n_pe (\$pri, $n_partition_entries);
263 poke_n_pe (\$backup, $n_partition_entries);
265 # set both PE CRC and header CRCs:
266 set_CRCs 'primary', \$pri, $in, $n_partition_entries;
267 set_CRCs 'backup', \$backup, $in, $n_partition_entries;
269 # Write both headers back to the file:
271 or die "$ME: failed to open $in: $!\n";
274 or die "$ME: $in: failed to seek to byte $ss: $!\n";
276 or die "$ME: $in: failed to write primary header: $!\n";
278 sysseek (F, $backup_LBA * $ss, 0)
279 or die "$ME: $in: failed to seek to backup LBA $backup_LBA: $!\n";
281 or die "$ME: $in: failed to write backup header: $!\n";
284 or die "$ME: failed to close $in: $!\n";