blob: 33078f2328327631928071505036834912228fc9 [file] [log] [blame]
You-Cheng Syud5692942018-01-04 14:40:59 +08001#!/usr/bin/env python
Hung-Te Linc772e1a2017-04-14 16:50:50 +08002# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""An utility to manipulate GPT on a disk image.
7
8Chromium OS factory software usually needs to access partitions from disk
9images. However, there is no good, lightweight, and portable GPT utility.
10Most Chromium OS systems use `cgpt`, but that's not by default installed on
11Ubuntu. Most systems have parted (GNU) or partx (util-linux-ng) but they have
12their own problems.
13
14For example, when a disk image is resized (usually enlarged for putting more
15resources on stateful partition), GPT table must be updated. However,
16 - `parted` can't repair partition without interactive console in exception
17 handler.
18 - `partx` cannot fix headers nor make changes to partition table.
19 - `cgpt repair` does not fix `LastUsableLBA` so we cannot enlarge partition.
20 - `gdisk` is not installed on most systems.
21
22As a result, we need a dedicated tool to help processing GPT.
23
24This pygpt.py provides a simple and customized implementation for processing
25GPT, as a replacement for `cgpt`.
26"""
27
28
29from __future__ import print_function
30
31import argparse
32import binascii
33import collections
34import logging
35import os
36import struct
37import uuid
38
39
40# The binascii.crc32 returns signed integer, so CRC32 in in struct must be
41# declared as 'signed' (l) instead of 'unsigned' (L).
42# http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_.28LBA_1.29
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080043HEADER_DESCRIPTION = """
Hung-Te Linc772e1a2017-04-14 16:50:50 +080044 8s Signature
45 4s Revision
46 L HeaderSize
47 l CRC32
48 4s Reserved
49 Q CurrentLBA
50 Q BackupLBA
51 Q FirstUsableLBA
52 Q LastUsableLBA
53 16s DiskGUID
54 Q PartitionEntriesStartingLBA
55 L PartitionEntriesNumber
56 L PartitionEntrySize
57 l PartitionArrayCRC32
58"""
59
60# http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080061PARTITION_DESCRIPTION = """
Hung-Te Linc772e1a2017-04-14 16:50:50 +080062 16s TypeGUID
63 16s UniqueGUID
64 Q FirstLBA
65 Q LastLBA
66 Q Attributes
67 72s Names
68"""
69
Hung-Te Linc6e009c2018-04-17 15:06:16 +080070# The PMBR has so many variants. The basic format is defined in
71# https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout, and our
72# implementation, as derived from `cgpt`, is following syslinux as:
73# https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/master/cgpt/cgpt.h#32
74PMBR_DESCRIPTION = """
75 424s BootCode
76 16s BootGUID
77 L DiskID
78 2s Magic
79 16s LegacyPart0
80 16s LegacyPart1
81 16s LegacyPart2
82 16s LegacyPart3
83 2s Signature
84"""
Hung-Te Linc772e1a2017-04-14 16:50:50 +080085
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080086def BitProperty(getter, setter, shift, mask):
87 """A generator for bit-field properties.
88
89 This is used inside a class to manipulate an integer-like variable using
90 properties. The getter and setter should be member functions to change the
91 underlying member data.
Hung-Te Linc772e1a2017-04-14 16:50:50 +080092
93 Args:
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080094 getter: a function to read integer type variable (for all the bits).
95 setter: a function to set the new changed integer type variable.
96 shift: integer for how many bits should be shifted (right).
97 mask: integer for the mask to filter out bit field.
Hung-Te Linc772e1a2017-04-14 16:50:50 +080098 """
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080099 def _getter(self):
100 return (getter(self) >> shift) & mask
101 def _setter(self, value):
102 assert value & mask == value, (
103 'Value %s out of range (mask=%s)' % (value, mask))
104 setter(self, getter(self) & ~(mask << shift) | value << shift)
105 return property(_getter, _setter)
106
107
108class GPTBlob(object):
109 """A decorator class to help accessing GPT blobs as named tuple.
110
111 To use this, specify the blob description (struct format and named tuple field
112 names) above the derived class, for example:
113
114 @GPTBlob(description):
115 class Header(GPTObject):
116 pass
117 """
118 def __init__(self, description):
119 spec = description.split()
120 self.struct_format = '<' + ''.join(spec[::2])
121 self.fields = spec[1::2]
122
123 def __call__(self, cls):
124 new_bases = ((
125 collections.namedtuple(cls.__name__, self.fields),) + cls.__bases__)
126 new_cls = type(cls.__name__, new_bases, dict(cls.__dict__))
127 setattr(new_cls, 'FORMAT', self.struct_format)
128 return new_cls
129
130
131class GPTObject(object):
132 """An object in GUID Partition Table.
133
134 This needs to be decorated by @GPTBlob(description) and inherited by a real
135 class. Properties (not member functions) in CamelCase should be reserved for
136 named tuple attributes.
137
138 To create a new object, use class method ReadFrom(), which takes a stream
139 as input or None to create with all elements set to zero. To make changes to
140 named tuple elements, use member function Clone(changes).
141
142 It is also possible to attach some additional properties to the object as meta
143 data (for example path of the underlying image file). To do that, specify the
144 data as keyword arguments when calling ReadFrom(). These properties will be
145 preserved when you call Clone().
146
147 A special case is "reset named tuple elements of an object but keeping all
148 properties", for example changing a partition object to unused (zeroed).
149 ReadFrom() is a class method so properties won't be copied. You need to
150 call as cls.ReadFrom(None, **p.__dict__), or a short cut - p.CloneAndZero().
151 """
152
153 FORMAT = None
154 """The struct.{pack,unpack} format string, and should be set by GPTBlob."""
155
156 CLONE_CONVERTERS = None
157 """A dict (name, cvt) to convert input arguments into named tuple data.
158
159 `name` is a string for the name of argument to convert.
160 `cvt` is a callable to convert value. The return value may be:
161 - a tuple in (new_name, value): save the value as new name.
162 - otherwise, save the value in original name.
163 Note tuple is an invalid input for struct.unpack so it's used for the
164 special value.
165 """
166
167 @classmethod
168 def ReadFrom(cls, f, **kargs):
169 """Reads and decode an object from stream.
170
171 Args:
172 f: a stream to read blob, or None to decode with all zero bytes.
173 kargs: a dict for additional attributes in object.
174 """
175 if f is None:
176 reader = lambda num: '\x00' * num
177 else:
178 reader = f.read
179 data = cls(*struct.unpack(cls.FORMAT, reader(struct.calcsize(cls.FORMAT))))
180 # Named tuples do not accept kargs in constructor.
181 data.__dict__.update(kargs)
182 return data
183
184 def Clone(self, **dargs):
185 """Clones a new instance with modifications.
186
187 GPT objects are usually named tuples that are immutable, so the only way
188 to make changes is to create a new instance with modifications.
189
190 Args:
191 dargs: a dict with all modifications.
192 """
193 for name, convert in (self.CLONE_CONVERTERS or {}).iteritems():
194 if name not in dargs:
195 continue
196 result = convert(dargs.pop(name))
197 if isinstance(result, tuple):
198 assert len(result) == 2, 'Converted tuple must be (name, value).'
199 dargs[result[0]] = result[1]
200 else:
201 dargs[name] = result
202
203 cloned = self._replace(**dargs)
204 cloned.__dict__.update(self.__dict__)
205 return cloned
206
207 def CloneAndZero(self, **dargs):
208 """Short cut to create a zeroed object while keeping all properties.
209
210 This is very similar to Clone except all named tuple elements will be zero.
211 Also different from class method ReadFrom(None) because this keeps all
212 properties from one object.
213 """
214 cloned = self.ReadFrom(None, **self.__dict__)
215 return cloned.Clone(**dargs) if dargs else cloned
216
217 @property
218 def blob(self):
219 """Returns the object in formatted bytes."""
220 return struct.pack(self.FORMAT, *self)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800221
222
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800223class GPTError(Exception):
224 """All exceptions by GPT."""
225 pass
226
227
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800228class GPT(object):
229 """A GPT helper class.
230
231 To load GPT from an existing disk image file, use `LoadFromFile`.
232 After modifications were made, use `WriteToFile` to commit changes.
233
234 Attributes:
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800235 header: a namedtuple of GPT header.
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800236 pmbr: a namedtuple of Protective MBR.
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800237 partitions: a list of GPT partition entry nametuple.
238 block_size: integer for size of bytes in one block (sector).
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800239 """
240
Hung-Te Linf148d322018-04-13 10:24:42 +0800241 DEFAULT_BLOCK_SIZE = 512
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800242 TYPE_GUID_UNUSED = '\x00' * 16
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800243 TYPE_NAME_CHROMEOS_KERNEL = 'ChromeOS kernel'
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800244 TYPE_GUID_MAP = {
245 '00000000-0000-0000-0000-000000000000': 'Unused',
246 'EBD0A0A2-B9E5-4433-87C0-68B6B72699C7': 'Linux data',
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800247 'FE3A2A5D-4F32-41A7-B725-ACCC3285A309': TYPE_NAME_CHROMEOS_KERNEL,
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800248 '3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC': 'ChromeOS rootfs',
249 '2E0A753D-9E48-43B0-8337-B15192CB1B5E': 'ChromeOS reserved',
250 'CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3': 'ChromeOS firmware',
251 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B': 'EFI System Partition',
252 }
253 TYPE_GUID_LIST_BOOTABLE = [
254 'FE3A2A5D-4F32-41A7-B725-ACCC3285A309', # ChromeOS kernel
255 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B', # EFI System Partition
256 ]
257
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800258 @GPTBlob(PMBR_DESCRIPTION)
259 class ProtectiveMBR(GPTObject):
260 """Protective MBR (PMBR) in GPT."""
261 SIGNATURE = '\x55\xAA'
262 MAGIC = '\x1d\x9a'
263
264 CLONE_CONVERTERS = {
265 'BootGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v
266 }
267
268 @property
269 def boot_guid(self):
270 """Returns the BootGUID in decoded (uuid.UUID) format."""
271 return uuid.UUID(bytes_le=self.BootGUID)
272
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800273 @GPTBlob(HEADER_DESCRIPTION)
274 class Header(GPTObject):
275 """Wrapper to Header in GPT."""
276 SIGNATURES = ['EFI PART', 'CHROMEOS']
277 SIGNATURE_IGNORE = 'IGNOREME'
278 DEFAULT_REVISION = '\x00\x00\x01\x00'
279
280 DEFAULT_PARTITION_ENTRIES = 128
281 DEFAULT_PARTITIONS_LBA = 2 # LBA 0 = MBR, LBA 1 = GPT Header.
282
283 def Clone(self, **dargs):
284 """Creates a new instance with modifications.
285
286 GPT objects are usually named tuples that are immutable, so the only way
287 to make changes is to create a new instance with modifications.
288
289 CRC32 is always updated but PartitionArrayCRC32 must be updated explicitly
290 since we can't track changes in GPT.partitions automatically.
291
292 Note since GPTHeader.Clone will always update CRC, we can only check and
293 compute CRC by super(GPT.Header, header).Clone, or header._replace.
294 """
295 dargs['CRC32'] = 0
296 header = super(GPT.Header, self).Clone(**dargs)
297 return super(GPT.Header, header).Clone(CRC32=binascii.crc32(header.blob))
298
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800299 @classmethod
300 def Create(cls, size, block_size, pad_blocks=0,
301 part_entries=DEFAULT_PARTITION_ENTRIES):
302 """Creates a header with default values.
303
304 Args:
305 size: integer of expected image size.
306 block_size: integer for size of each block (sector).
307 pad_blocks: number of preserved sectors between header and partitions.
308 part_entries: number of partitions to include in header.
309 """
310 part_entry_size = struct.calcsize(GPT.Partition.FORMAT)
311 parts_lba = cls.DEFAULT_PARTITIONS_LBA + pad_blocks
312 parts_bytes = part_entries * part_entry_size
313 parts_blocks = parts_bytes / block_size
314 if parts_bytes % block_size:
315 parts_blocks += 1
316 # PartitionsCRC32 must be updated later explicitly.
317 return cls.ReadFrom(None).Clone(
318 Signature=cls.SIGNATURES[0],
319 Revision=cls.DEFAULT_REVISION,
320 HeaderSize=struct.calcsize(cls.FORMAT),
321 CurrentLBA=1,
322 BackupLBA=size / block_size - 1,
323 FirstUsableLBA=parts_lba + parts_blocks,
324 LastUsableLBA=size / block_size - parts_blocks - parts_lba,
325 DiskGUID=uuid.uuid4().get_bytes(),
326 PartitionEntriesStartingLBA=parts_lba,
327 PartitionEntriesNumber=part_entries,
328 PartitionEntrySize=part_entry_size,
329 )
330
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800331 class PartitionAttributes(object):
332 """Wrapper for Partition.Attributes.
333
334 This can be created using Partition.attrs, but the changed properties won't
335 apply to underlying Partition until an explicit call with
336 Partition.Clone(Attributes=new_attrs).
337 """
338
339 def __init__(self, attrs):
340 self._attrs = attrs
341
342 @property
343 def raw(self):
344 """Returns the raw integer type attributes."""
345 return self._Get()
346
347 def _Get(self):
348 return self._attrs
349
350 def _Set(self, value):
351 self._attrs = value
352
353 successful = BitProperty(_Get, _Set, 56, 1)
354 tries = BitProperty(_Get, _Set, 52, 0xf)
355 priority = BitProperty(_Get, _Set, 48, 0xf)
356 legacy_boot = BitProperty(_Get, _Set, 2, 1)
357 required = BitProperty(_Get, _Set, 0, 1)
358
359 @GPTBlob(PARTITION_DESCRIPTION)
360 class Partition(GPTObject):
361 """The partition entry in GPT.
362
363 Please include following properties when creating a Partition object:
364 - image: a string for path to the image file the partition maps to.
365 - number: the 1-based partition number.
366 - block_size: an integer for size of each block (LBA, or sector).
367 """
368 NAMES_ENCODING = 'utf-16-le'
369 NAMES_LENGTH = 72
370
371 CLONE_CONVERTERS = {
372 # TODO(hungte) check if encoded name is too long.
373 'label': lambda l: (None if l is None else
374 ('Names', l.encode(GPT.Partition.NAMES_ENCODING))),
375 'TypeGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v,
376 'UniqueGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v,
377 'Attributes': (
378 lambda v: v.raw if isinstance(v, GPT.PartitionAttributes) else v),
379 }
380
381 def __str__(self):
382 return '%s#%s' % (self.image, self.number)
383
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800384 @classmethod
385 def Create(cls, block_size, image, number):
386 """Creates a new partition entry with given meta data."""
387 part = cls.ReadFrom(
388 None, image=image, number=number, block_size=block_size)
389 return part
390
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800391 def IsUnused(self):
392 """Returns if the partition is unused and can be allocated."""
393 return self.TypeGUID == GPT.TYPE_GUID_UNUSED
394
395 @property
396 def blocks(self):
397 """Return size of partition in blocks (see block_size)."""
398 return self.LastLBA - self.FirstLBA + 1
399
400 @property
401 def offset(self):
402 """Returns offset to partition in bytes."""
403 return self.FirstLBA * self.block_size
404
405 @property
406 def size(self):
407 """Returns size of partition in bytes."""
408 return self.blocks * self.block_size
409
410 @property
411 def type_guid(self):
412 return uuid.UUID(bytes_le=self.TypeGUID)
413
414 @property
415 def unique_guid(self):
416 return uuid.UUID(bytes_le=self.UniqueGUID)
417
418 @property
419 def label(self):
420 """Returns the Names in decoded string type."""
421 return self.Names.decode(self.NAMES_ENCODING).strip('\0')
422
423 @property
424 def attrs(self):
425 return GPT.PartitionAttributes(self.Attributes)
426
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800427 def __init__(self):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800428 """GPT constructor.
429
430 See LoadFromFile for how it's usually used.
431 """
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800432 self.pmbr = None
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800433 self.header = None
434 self.partitions = None
Hung-Te Linf148d322018-04-13 10:24:42 +0800435 self.block_size = self.DEFAULT_BLOCK_SIZE
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800436
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800437 @classmethod
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800438 def Create(cls, image_name, size, block_size, pad_blocks=0):
439 """Creates a new GPT instance from given size and block_size.
440
441 Args:
442 image_name: a string of underlying disk image file name.
443 size: expected size of disk image.
444 block_size: size of each block (sector) in bytes.
445 pad_blocks: number of blocks between header and partitions array.
446 """
447 gpt = cls()
448 gpt.block_size = block_size
449 gpt.header = cls.Header.Create(size, block_size, pad_blocks)
450 gpt.partitions = [
451 cls.Partition.Create(block_size, image_name, i + 1)
452 for i in xrange(gpt.header.PartitionEntriesNumber)]
453 return gpt
454
455 @classmethod
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800456 def LoadFromFile(cls, image):
457 """Loads a GPT table from give disk image file object.
458
459 Args:
460 image: a string as file path or a file-like object to read from.
461 """
462 if isinstance(image, basestring):
463 with open(image, 'rb') as f:
464 return cls.LoadFromFile(f)
465
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800466 gpt = cls()
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800467 image.seek(0)
468 pmbr = gpt.ProtectiveMBR.ReadFrom(image)
469 if pmbr.Signature == cls.ProtectiveMBR.SIGNATURE:
470 logging.debug('Found MBR signature in %s', image.name)
471 if pmbr.Magic == cls.ProtectiveMBR.MAGIC:
472 logging.debug('Found PMBR in %s', image.name)
473 gpt.pmbr = pmbr
474
Hung-Te Linf148d322018-04-13 10:24:42 +0800475 # Try DEFAULT_BLOCK_SIZE, then 4K.
476 for block_size in [cls.DEFAULT_BLOCK_SIZE, 4096]:
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800477 image.seek(block_size * 1)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800478 header = gpt.Header.ReadFrom(image)
479 if header.Signature in cls.Header.SIGNATURES:
Hung-Te Linf148d322018-04-13 10:24:42 +0800480 gpt.block_size = block_size
481 break
482 else:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800483 raise GPTError('Invalid signature in GPT header.')
Hung-Te Linf148d322018-04-13 10:24:42 +0800484
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800485 image.seek(gpt.block_size * header.PartitionEntriesStartingLBA)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800486 def ReadPartition(image, i):
487 p = gpt.Partition.ReadFrom(
488 image, image=image.name, number=i + 1, block_size=gpt.block_size)
489 return p
490
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800491 gpt.header = header
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800492 gpt.partitions = [
493 ReadPartition(image, i) for i in range(header.PartitionEntriesNumber)]
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800494 return gpt
495
496 def GetValidPartitions(self):
497 """Returns the list of partitions before entry with empty type GUID.
498
499 In partition table, the first entry with empty type GUID indicates end of
500 valid partitions. In most implementations all partitions after that should
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800501 be zeroed. However, few implementations for example cgpt, may create
502 partitions in arbitrary order so use this carefully.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800503 """
504 for i, p in enumerate(self.partitions):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800505 if p.IsUnused():
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800506 return self.partitions[:i]
507 return self.partitions
508
509 def GetMaxUsedLBA(self):
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800510 """Returns the max LastLBA from all used partitions."""
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800511 parts = [p for p in self.partitions if not p.IsUnused()]
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800512 return (max(p.LastLBA for p in parts)
513 if parts else self.header.FirstUsableLBA - 1)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800514
515 def GetPartitionTableBlocks(self, header=None):
516 """Returns the blocks (or LBA) of partition table from given header."""
517 if header is None:
518 header = self.header
519 size = header.PartitionEntrySize * header.PartitionEntriesNumber
Hung-Te Linf148d322018-04-13 10:24:42 +0800520 blocks = size / self.block_size
521 if size % self.block_size:
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800522 blocks += 1
523 return blocks
524
525 def Resize(self, new_size):
526 """Adjust GPT for a disk image in given size.
527
528 Args:
529 new_size: Integer for new size of disk image file.
530 """
Hung-Te Linf148d322018-04-13 10:24:42 +0800531 old_size = self.block_size * (self.header.BackupLBA + 1)
532 if new_size % self.block_size:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800533 raise GPTError(
534 'New file size %d is not valid for image files.' % new_size)
Hung-Te Linf148d322018-04-13 10:24:42 +0800535 new_blocks = new_size / self.block_size
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800536 if old_size != new_size:
537 logging.warn('Image size (%d, LBA=%d) changed from %d (LBA=%d).',
Hung-Te Linf148d322018-04-13 10:24:42 +0800538 new_size, new_blocks, old_size, old_size / self.block_size)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800539 else:
540 logging.info('Image size (%d, LBA=%d) not changed.',
541 new_size, new_blocks)
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800542 return
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800543
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800544 # Expected location
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800545 backup_lba = new_blocks - 1
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800546 last_usable_lba = backup_lba - self.header.FirstUsableLBA
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800547
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800548 if last_usable_lba < self.header.LastUsableLBA:
549 max_used_lba = self.GetMaxUsedLBA()
550 if last_usable_lba < max_used_lba:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800551 raise GPTError('Backup partition tables will overlap used partitions')
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800552
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800553 self.header = self.header.Clone(
554 BackupLBA=backup_lba, LastUsableLBA=last_usable_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800555
556 def GetFreeSpace(self):
557 """Returns the free (available) space left according to LastUsableLBA."""
558 max_lba = self.GetMaxUsedLBA()
559 assert max_lba <= self.header.LastUsableLBA, "Partitions too large."
Hung-Te Linf148d322018-04-13 10:24:42 +0800560 return self.block_size * (self.header.LastUsableLBA - max_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800561
562 def ExpandPartition(self, i):
563 """Expands a given partition to last usable LBA.
564
565 Args:
566 i: Index (0-based) of target partition.
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800567
568 Returns:
569 (old_blocks, new_blocks) for size in blocks.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800570 """
571 # Assume no partitions overlap, we need to make sure partition[i] has
572 # largest LBA.
573 if i < 0 or i >= len(self.GetValidPartitions()):
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800574 raise GPTError('Partition number %d is invalid.' % (i + 1))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800575 p = self.partitions[i]
576 max_used_lba = self.GetMaxUsedLBA()
577 if max_used_lba > p.LastLBA:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800578 raise GPTError(
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800579 'Cannot expand partition %d because it is not the last allocated '
580 'partition.' % (i + 1))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800581
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800582 old_blocks = p.blocks
583 p = p.Clone(LastLBA=self.header.LastUsableLBA)
584 new_blocks = p.blocks
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800585 self.partitions[i] = p
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800586 logging.warn(
587 '%s expanded, size in LBA: %d -> %d.', p, old_blocks, new_blocks)
588 return (old_blocks, new_blocks)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800589
590 def UpdateChecksum(self):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800591 """Updates all checksum fields in GPT objects.
592
593 The Header.CRC32 is automatically updated in Header.Clone().
594 """
595 parts = ''.join(p.blob for p in self.partitions)
596 self.header = self.header.Clone(
597 PartitionArrayCRC32=binascii.crc32(parts))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800598
599 def GetBackupHeader(self):
600 """Returns the backup header according to current header."""
601 partitions_starting_lba = (
602 self.header.BackupLBA - self.GetPartitionTableBlocks())
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800603 return self.header.Clone(
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800604 BackupLBA=self.header.CurrentLBA,
605 CurrentLBA=self.header.BackupLBA,
606 PartitionEntriesStartingLBA=partitions_starting_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800607
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800608 @classmethod
609 def WriteProtectiveMBR(cls, image, create, bootcode=None, boot_guid=None):
610 """Writes a protective MBR to given file.
611
612 Each MBR is 512 bytes: 424 bytes for bootstrap code, 16 bytes of boot GUID,
613 4 bytes of disk id, 2 bytes of bootcode magic, 4*16 for 4 partitions, and 2
614 byte as signature. cgpt has hard-coded the CHS and bootstrap magic values so
615 we can follow that.
616
617 Args:
618 create: True to re-create PMBR structure.
619 bootcode: a blob of new boot code.
620 boot_guid a blob for new boot GUID.
621
622 Returns:
623 The written PMBR structure.
624 """
625 if isinstance(image, basestring):
626 with open(image, 'rb+') as f:
627 return cls.WriteProtectiveMBR(f, create, bootcode, boot_guid)
628
629 image.seek(0)
630 assert struct.calcsize(cls.ProtectiveMBR.FORMAT) == cls.DEFAULT_BLOCK_SIZE
631 pmbr = cls.ProtectiveMBR.ReadFrom(image)
632
633 if create:
634 legacy_sectors = min(
635 0x100000000,
636 os.path.getsize(image.name) / cls.DEFAULT_BLOCK_SIZE) - 1
637 # Partition 0 must have have the fixed CHS with number of sectors
638 # (calculated as legacy_sectors later).
639 part0 = ('00000200eeffffff01000000'.decode('hex') +
640 struct.pack('<I', legacy_sectors))
641 # Partition 1~3 should be all zero.
642 part1 = '\x00' * 16
643 assert len(part0) == len(part1) == 16, 'MBR entry is wrong.'
644 pmbr = pmbr.Clone(
645 BootGUID=cls.TYPE_GUID_UNUSED,
646 DiskID=0,
647 Magic=cls.ProtectiveMBR.MAGIC,
648 LegacyPart0=part0,
649 LegacyPart1=part1,
650 LegacyPart2=part1,
651 LegacyPart3=part1,
652 Signature=cls.ProtectiveMBR.SIGNATURE)
653
654 if bootcode:
655 if len(bootcode) > len(pmbr.BootCode):
656 logging.info(
657 'Bootcode is larger (%d > %d)!', len(bootcode), len(pmbr.BootCode))
658 bootcode = bootcode[:len(pmbr.BootCode)]
659 pmbr = pmbr.Clone(BootCode=bootcode)
660 if boot_guid:
661 pmbr = pmbr.Clone(BootGUID=boot_guid)
662
663 blob = pmbr.blob
664 assert len(blob) == cls.DEFAULT_BLOCK_SIZE
665 image.seek(0)
666 image.write(blob)
667 return pmbr
668
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800669 def WriteToFile(self, image):
670 """Updates partition table in a disk image file.
671
672 Args:
673 image: a string as file path or a file-like object to write into.
674 """
675 if isinstance(image, basestring):
676 with open(image, 'rb+') as f:
677 return self.WriteToFile(f)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800678
679 def WriteData(name, blob, lba):
680 """Writes a blob into given location."""
681 logging.info('Writing %s in LBA %d (offset %d)',
Hung-Te Linf148d322018-04-13 10:24:42 +0800682 name, lba, lba * self.block_size)
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800683 image.seek(lba * self.block_size)
684 image.write(blob)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800685
686 self.UpdateChecksum()
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800687 parts_blob = ''.join(p.blob for p in self.partitions)
688 WriteData('GPT Header', self.header.blob, self.header.CurrentLBA)
689 WriteData(
690 'GPT Partitions', parts_blob, self.header.PartitionEntriesStartingLBA)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800691 logging.info('Usable LBA: First=%d, Last=%d',
692 self.header.FirstUsableLBA, self.header.LastUsableLBA)
693 backup_header = self.GetBackupHeader()
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800694 WriteData(
695 'Backup Partitions', parts_blob,
696 backup_header.PartitionEntriesStartingLBA)
697 WriteData('Backup Header', backup_header.blob, backup_header.CurrentLBA)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800698
699
700class GPTCommands(object):
701 """Collection of GPT sub commands for command line to use.
702
703 The commands are derived from `cgpt`, but not necessary to be 100% compatible
704 with cgpt.
705 """
706
707 FORMAT_ARGS = [
Peter Shihc7156ca2018-02-26 14:46:24 +0800708 ('begin', 'beginning sector'),
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800709 ('size', 'partition size (in sectors)'),
Peter Shihc7156ca2018-02-26 14:46:24 +0800710 ('type', 'type guid'),
711 ('unique', 'unique guid'),
712 ('label', 'label'),
713 ('Successful', 'Successful flag'),
714 ('Tries', 'Tries flag'),
715 ('Priority', 'Priority flag'),
716 ('Legacy', 'Legacy Boot flag'),
717 ('Attribute', 'raw 16-bit attribute value (bits 48-63)')]
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800718
719 def __init__(self):
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800720 commands = dict(
721 (command.lower(), getattr(self, command)())
722 for command in dir(self)
723 if (isinstance(getattr(self, command), type) and
724 issubclass(getattr(self, command), self.SubCommand) and
725 getattr(self, command) is not self.SubCommand)
726 )
727 self.commands = commands
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800728
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800729 def DefineArgs(self, parser):
730 """Defines all available commands to an argparser subparsers instance."""
731 subparsers = parser.add_subparsers(help='Sub-command help.', dest='command')
732 for name, instance in sorted(self.commands.iteritems()):
733 parser = subparsers.add_parser(
734 name, description=instance.__doc__,
735 formatter_class=argparse.RawDescriptionHelpFormatter,
736 help=instance.__doc__.splitlines()[0])
737 instance.DefineArgs(parser)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800738
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800739 def Execute(self, args):
740 """Execute the sub commands by given parsed arguments."""
741 self.commands[args.command].Execute(args)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800742
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800743 class SubCommand(object):
744 """A base class for sub commands to derive from."""
745
746 def DefineArgs(self, parser):
747 """Defines command line arguments to argparse parser.
748
749 Args:
750 parser: An argparse parser instance.
751 """
752 del parser # Unused.
753 raise NotImplementedError
754
755 def Execute(self, args):
756 """Execute the command.
757
758 Args:
759 args: An argparse parsed namespace.
760 """
761 del args # Unused.
762 raise NotImplementedError
763
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800764 class Create(SubCommand):
765 """Create or reset GPT headers and tables.
766
767 Create or reset an empty GPT.
768 """
769
770 def DefineArgs(self, parser):
771 parser.add_argument(
772 '-z', '--zero', action='store_true',
773 help='Zero the sectors of the GPT table and entries')
774 parser.add_argument(
775 '-p', '--pad_blocks', type=int, default=0,
776 help=('Size (in blocks) of the disk to pad between the '
777 'primary GPT header and its entries, default %(default)s'))
778 parser.add_argument(
779 '--block_size', type=int, default=GPT.DEFAULT_BLOCK_SIZE,
780 help='Size of each block (sector) in bytes.')
781 parser.add_argument(
782 'image_file', type=argparse.FileType('rb+'),
783 help='Disk image file to create.')
784
785 def Execute(self, args):
786 block_size = args.block_size
787 gpt = GPT.Create(
788 args.image_file.name, os.path.getsize(args.image_file.name),
789 block_size, args.pad_blocks)
790 if args.zero:
791 # In theory we only need to clear LBA 1, but to make sure images already
792 # initialized with different block size won't have GPT signature in
793 # different locations, we should zero until first usable LBA.
794 args.image_file.seek(0)
795 args.image_file.write('\0' * block_size * gpt.header.FirstUsableLBA)
796 gpt.WriteToFile(args.image_file)
797 print('OK: Created GPT for %s' % args.image_file.name)
798
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800799 class Boot(SubCommand):
800 """Edit the PMBR sector for legacy BIOSes.
801
802 With no options, it will just print the PMBR boot guid.
803 """
804
805 def DefineArgs(self, parser):
806 parser.add_argument(
807 '-i', '--number', type=int,
808 help='Set bootable partition')
809 parser.add_argument(
810 '-b', '--bootloader', type=argparse.FileType('r'),
811 help='Install bootloader code in the PMBR')
812 parser.add_argument(
813 '-p', '--pmbr', action='store_true',
814 help='Create legacy PMBR partition table')
815 parser.add_argument(
816 'image_file', type=argparse.FileType('rb+'),
817 help='Disk image file to change PMBR.')
818
819 def Execute(self, args):
820 """Rebuilds the protective MBR."""
821 bootcode = args.bootloader.read() if args.bootloader else None
822 boot_guid = None
823 if args.number is not None:
824 gpt = GPT.LoadFromFile(args.image_file)
825 boot_guid = gpt.partitions[args.number - 1].UniqueGUID
826 pmbr = GPT.WriteProtectiveMBR(
827 args.image_file, args.pmbr, bootcode=bootcode, boot_guid=boot_guid)
828
829 print(str(pmbr.boot_guid).upper())
830
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800831 class Repair(SubCommand):
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800832 """Repair damaged GPT headers and tables."""
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800833
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800834 def DefineArgs(self, parser):
835 parser.add_argument(
836 'image_file', type=argparse.FileType('rb+'),
837 help='Disk image file to repair.')
838
839 def Execute(self, args):
840 gpt = GPT.LoadFromFile(args.image_file)
841 gpt.Resize(os.path.getsize(args.image_file.name))
842 gpt.WriteToFile(args.image_file)
843 print('Disk image file %s repaired.' % args.image_file.name)
844
845 class Expand(SubCommand):
846 """Expands a GPT partition to all available free space."""
847
848 def DefineArgs(self, parser):
849 parser.add_argument(
850 '-i', '--number', type=int, required=True,
851 help='The partition to expand.')
852 parser.add_argument(
853 'image_file', type=argparse.FileType('rb+'),
854 help='Disk image file to modify.')
855
856 def Execute(self, args):
857 gpt = GPT.LoadFromFile(args.image_file)
858 old_blocks, new_blocks = gpt.ExpandPartition(args.number - 1)
859 gpt.WriteToFile(args.image_file)
860 if old_blocks < new_blocks:
861 print(
862 'Partition %s on disk image file %s has been extended '
863 'from %s to %s .' %
864 (args.number, args.image_file.name, old_blocks * gpt.block_size,
865 new_blocks * gpt.block_size))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800866 else:
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800867 print('Nothing to expand for disk image %s partition %s.' %
868 (args.image_file.name, args.number))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800869
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800870 class Show(SubCommand):
871 """Show partition table and entries.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800872
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800873 Display the GPT table.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800874 """
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800875
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800876 def DefineArgs(self, parser):
877 parser.add_argument(
878 '--numeric', '-n', action='store_true',
879 help='Numeric output only.')
880 parser.add_argument(
881 '--quick', '-q', action='store_true',
882 help='Quick output.')
883 parser.add_argument(
884 '-i', '--number', type=int,
885 help='Show specified partition only, with format args.')
886 for name, help_str in GPTCommands.FORMAT_ARGS:
887 # TODO(hungte) Alert if multiple args were specified.
888 parser.add_argument(
889 '--%s' % name, '-%c' % name[0], action='store_true',
890 help='[format] %s.' % help_str)
891 parser.add_argument(
892 'image_file', type=argparse.FileType('rb'),
893 help='Disk image file to show.')
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800894
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800895 def Execute(self, args):
896 """Show partition table and entries."""
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800897
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800898 def FormatGUID(bytes_le):
899 return str(uuid.UUID(bytes_le=bytes_le)).upper()
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800900
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800901 def FormatTypeGUID(p):
902 guid_str = FormatGUID(p.TypeGUID)
903 if not args.numeric:
904 names = gpt.TYPE_GUID_MAP.get(guid_str)
905 if names:
906 return names
907 return guid_str
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800908
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800909 def FormatNames(p):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800910 return p.label
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800911
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800912 def IsBootableType(type_guid):
913 return type_guid in gpt.TYPE_GUID_LIST_BOOTABLE
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800914
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800915 def FormatAttribute(attrs, chromeos_kernel=False):
916 if args.numeric:
917 return '[%x]' % (attrs.raw >> 48)
918 results = []
919 if chromeos_kernel:
920 results += [
921 'priority=%d' % attrs.priority,
922 'tries=%d' % attrs.tries,
923 'successful=%d' % attrs.successful]
924 if attrs.required:
925 results += ['required=1']
926 if attrs.legacy_boot:
927 results += ['legacy_boot=1']
928 return ' '.join(results)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800929
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800930 def ApplyFormatArgs(p):
931 if args.begin:
932 return p.FirstLBA
933 elif args.size:
934 return p.blocks
935 elif args.type:
936 return FormatTypeGUID(p)
937 elif args.unique:
938 return FormatGUID(p.UniqueGUID)
939 elif args.label:
940 return FormatNames(p)
941 elif args.Successful:
942 return p.attrs.successful
943 elif args.Priority:
944 return p.attrs.priority
945 elif args.Tries:
946 return p.attrs.tries
947 elif args.Legacy:
948 return p.attrs.legacy_boot
949 elif args.Attribute:
950 return '[%x]' % (p.Attributes >> 48)
951 else:
952 return None
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800953
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800954 def IsFormatArgsSpecified():
955 return any(getattr(args, arg[0]) for arg in GPTCommands.FORMAT_ARGS)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800956
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800957 gpt = GPT.LoadFromFile(args.image_file)
958 logging.debug('%r', gpt.header)
959 fmt = '%12s %11s %7s %s'
960 fmt2 = '%32s %s: %s'
961 header = ('start', 'size', 'part', 'contents')
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800962
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800963 if IsFormatArgsSpecified() and args.number is None:
964 raise GPTError('Format arguments must be used with -i.')
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800965
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800966 if not (args.number is None or
967 0 < args.number <= gpt.header.PartitionEntriesNumber):
968 raise GPTError('Invalid partition number: %d' % args.number)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800969
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800970 partitions = gpt.partitions
971 do_print_gpt_blocks = False
972 if not (args.quick or IsFormatArgsSpecified()):
973 print(fmt % header)
974 if args.number is None:
975 do_print_gpt_blocks = True
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800976
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800977 if do_print_gpt_blocks:
978 print(fmt % (gpt.header.CurrentLBA, 1, '', 'Pri GPT header'))
979 print(fmt % (gpt.header.PartitionEntriesStartingLBA,
980 gpt.GetPartitionTableBlocks(), '', 'Pri GPT table'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800981
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800982 for p in partitions:
983 if args.number is None:
984 # Skip unused partitions.
985 if p.IsUnused():
986 continue
987 elif p.number != args.number:
988 continue
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800989
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800990 if IsFormatArgsSpecified():
991 print(ApplyFormatArgs(p))
992 continue
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800993
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800994 type_guid = FormatGUID(p.TypeGUID)
995 print(fmt % (p.FirstLBA, p.blocks, p.number,
996 FormatTypeGUID(p) if args.quick else
997 'Label: "%s"' % FormatNames(p)))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800998
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800999 if not args.quick:
1000 print(fmt2 % ('', 'Type', FormatTypeGUID(p)))
1001 print(fmt2 % ('', 'UUID', FormatGUID(p.UniqueGUID)))
1002 if args.numeric or IsBootableType(type_guid):
1003 name = GPT.TYPE_GUID_MAP[type_guid]
1004 print(fmt2 % ('', 'Attr', FormatAttribute(
1005 p.attrs, name == GPT.TYPE_NAME_CHROMEOS_KERNEL)))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001006
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001007 if do_print_gpt_blocks:
1008 f = args.image_file
1009 f.seek(gpt.header.BackupLBA * gpt.block_size)
1010 header = gpt.Header.ReadFrom(f)
1011 print(fmt % (header.PartitionEntriesStartingLBA,
1012 gpt.GetPartitionTableBlocks(header), '',
1013 'Sec GPT table'))
1014 print(fmt % (header.CurrentLBA, 1, '', 'Sec GPT header'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001015
1016
1017def main():
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001018 commands = GPTCommands()
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001019 parser = argparse.ArgumentParser(description='GPT Utility.')
1020 parser.add_argument('--verbose', '-v', action='count', default=0,
1021 help='increase verbosity.')
1022 parser.add_argument('--debug', '-d', action='store_true',
1023 help='enable debug output.')
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001024 commands.DefineArgs(parser)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001025
1026 args = parser.parse_args()
1027 log_level = max(logging.WARNING - args.verbose * 10, logging.DEBUG)
1028 if args.debug:
1029 log_level = logging.DEBUG
1030 logging.basicConfig(format='%(module)s:%(funcName)s %(message)s',
1031 level=log_level)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001032 try:
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001033 commands.Execute(args)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001034 except Exception as e:
1035 if args.verbose or args.debug:
1036 logging.exception('Failure in command [%s]', args.command)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001037 exit('ERROR: %s: %s' % (args.command, str(e) or 'Unknown error.'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001038
1039
1040if __name__ == '__main__':
1041 main()