OSDN Git Service

maint: remove now-unused fat-related functions
[android-x86/external-parted.git] / tests / gpt-header-munge
1 #!/usr/bin/perl -w
2 # Change the size of a GPT image's partition array.
3
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.
16
17 use strict;
18 use warnings;
19 use Digest::CRC qw(crc32);
20 use List::Util qw(max);
21
22 (my $ME = $0) =~ s|.*/||;
23 my $VERSION = '1.0';
24
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;
28
29 # Size of a partition array entry, in bytes.
30 # This too is specified in the GPT header, but AFAIK, no one changes it.
31 my $pe_size = 128;
32
33 # Sector size.
34 my $ss;
35
36 # Sector number of the backup GPT header, to be read from the primary header.
37 my $backup_LBA;
38
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)) }
42
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)) }
45
46 sub round_up_to_ss ($)
47 {
48   my ($n) = @_;
49   return $n + $ss - $n % $ss;
50 }
51
52 # Return the byte offset of the start of the specified partition array.
53 sub partition_array_start_offset ($$)
54 {
55   my ($pri_or_backup, $n_pe) = @_;
56   $pri_or_backup eq 'primary'
57     and return 2 * $ss;
58
59   # Backup
60   return $backup_LBA * $ss - round_up_to_ss ($n_pe * $pe_size);
61 }
62
63 # Calculate and return the specified partition array crc32 checksum.
64 sub partition_array_crc ($$$)
65 {
66   my ($pri_or_backup, $n_pe, $in) = @_;
67   local *F;
68   open F, '<', $in
69     or die "$ME: failed to open $in: $!\n";
70
71   # Seek to start of partition array.
72   my $off = partition_array_start_offset $pri_or_backup, $n_pe;
73   sysseek (F, $off, 0)
74     or die "$ME: $in: failed to seek to $off: $!\n";
75
76   # Read the array.
77   my $p;
78   my $pe_buf;
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";
82
83   return crc32 $pe_buf;
84 }
85
86 # Verify the initial CRC of BUF.
87 sub check_GPT_header ($$$)
88 {
89   my ($pri_or_backup, $in, $buf) = @_;
90
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";
97
98   $pri_or_backup eq 'backup' && $backup != 1
99     and die "$ME: $in: the backup_LBA in the backup header must be 1\n";
100
101   # A primary partition's "partition entries starting LBA" must be 2.
102   if ($pri_or_backup eq 'primary')
103     {
104       my $p = pe_start_LBA $buf;
105       $p == 2
106         or die "$ME: $in: primary header's PE start LBA is $p (should be 2)\n";
107     }
108
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;
112
113   # Compute CRC32 of header: it'd better match.
114   my $crc = crc32($buf);
115   $orig_crc == $crc
116     or die "$ME: $in: cannot reproduce $pri_or_backup GPT header's CRC32\n";
117 }
118
119 # Poke the $N_PE value into $$BUF's number-of-partition-entries slot.
120 sub poke_n_pe ($$)
121 {
122   my ($buf, $n_pe) = @_;
123
124   # Poke the little-endian value into place.
125   substr ($$buf, 80, 4) = pack ('L<', $n_pe);
126 }
127
128 # Compute/set partition-array CRC (given $N_PE), then compute a new
129 # header-CRC and poke it into its position, too.
130 sub set_CRCs ($$$$)
131 {
132   my ($pri_or_backup, $buf, $in, $n_pe) = @_;
133
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);
137
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')
141     {
142       my $off = partition_array_start_offset $pri_or_backup, $n_pe;
143       $off % $ss == 0
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);
148     }
149
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);
155 }
156
157 sub usage ($)
158 {
159   my ($exit_code) = @_;
160   my $STREAM = ($exit_code == 0 ? *STDOUT : *STDERR);
161   if ($exit_code != 0)
162     {
163       print $STREAM "Try `$ME --help' for more information.\n";
164     }
165   else
166     {
167       print $STREAM <<EOF;
168 Usage: $ME [OPTIONS] FILE
169 Change the number of GPT partition array entries in FILE.
170
171 This option must be specified:
172
173    --n-partition-array-entries=N  FIXME
174
175 The following are optional:
176
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
180
181 EXAMPLE:
182
183   dd if=/dev/null of=F bs=1 seek=6MB
184   parted -s F mklabel gpt
185   $ME --n=9 F
186
187 EOF
188     }
189   exit $exit_code;
190 }
191
192 {
193   my $n_partition_entries;
194
195   use Getopt::Long;
196   GetOptions
197     (
198      'n-partition-array-entries=i' => \$n_partition_entries,
199      'sector-size=i' => \$ss,
200
201      help => sub { usage 0 },
202      version => sub { print "$ME version $VERSION\n"; exit },
203     ) or usage 1;
204
205   defined $n_partition_entries
206     or (warn "$ME: --n-partition-array-entries=N not specified\n"), usage 1;
207
208   defined $ss
209     or $ss = 512;
210
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";
218
219   @ARGV == 1
220     or (warn "$ME: no file specified\n"), usage 1;
221
222   my $in = $ARGV[0];
223   local *F;
224   open F, '<', $in
225     or die "$ME: failed to open $in: $!\n";
226
227   # Get length and perform some basic sanity checks.
228   my $len = sysseek (F, 0, 2);
229   defined $len
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";
235   $len % $ss == 0
236     or die "$ME: $in: size is not a multiple of $ss: $!\n";
237
238   # Skip 1st sector.
239   sysseek (F, $ss, 0)
240     or die "$ME: $in: failed to seek to byte $ss: $!\n";
241
242   # Read the primary GPT header.
243   my $p;
244   my $pri;
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";
247
248   $backup_LBA = unpack ('Q<', substr ($pri, 32, 8));
249
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";
253   my $backup;
254   ($p = sysread F, $backup, $gpt_header_len) && $p == $gpt_header_len
255     or die "$ME: $in: read failed: $!\n";
256
257   close F;
258
259   check_GPT_header ('primary', $in, $pri);
260   check_GPT_header ('backup',  $in, $backup);
261
262   poke_n_pe (\$pri,    $n_partition_entries);
263   poke_n_pe (\$backup, $n_partition_entries);
264
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;
268
269   # Write both headers back to the file:
270   open F, '+<', $in
271     or die "$ME: failed to open $in: $!\n";
272
273   sysseek (F, $ss, 0)
274     or die "$ME: $in: failed to seek to byte $ss: $!\n";
275   syswrite F, $pri
276     or die "$ME: $in: failed to write primary header: $!\n";
277
278   sysseek (F, $backup_LBA * $ss, 0)
279     or die "$ME: $in: failed to seek to backup LBA $backup_LBA: $!\n";
280   syswrite F, $backup
281     or die "$ME: $in: failed to write backup header: $!\n";
282
283   close F
284     or die "$ME: failed to close $in: $!\n";
285   }