blob: 8b07553682ab0521ea94be50cd6822dcbc502ade [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
Hung-Te Linf641d302018-04-18 15:09:35 +080037import subprocess
38import sys
Hung-Te Linc772e1a2017-04-14 16:50:50 +080039import uuid
40
41
42# The binascii.crc32 returns signed integer, so CRC32 in in struct must be
43# declared as 'signed' (l) instead of 'unsigned' (L).
44# http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_table_header_.28LBA_1.29
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080045HEADER_DESCRIPTION = """
Hung-Te Linc772e1a2017-04-14 16:50:50 +080046 8s Signature
47 4s Revision
48 L HeaderSize
49 l CRC32
50 4s Reserved
51 Q CurrentLBA
52 Q BackupLBA
53 Q FirstUsableLBA
54 Q LastUsableLBA
55 16s DiskGUID
56 Q PartitionEntriesStartingLBA
57 L PartitionEntriesNumber
58 L PartitionEntrySize
59 l PartitionArrayCRC32
60"""
61
62# http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries
Hung-Te Lin49ac3c22018-04-17 14:37:54 +080063PARTITION_DESCRIPTION = """
Hung-Te Linc772e1a2017-04-14 16:50:50 +080064 16s TypeGUID
65 16s UniqueGUID
66 Q FirstLBA
67 Q LastLBA
68 Q Attributes
69 72s Names
70"""
71
Hung-Te Linc6e009c2018-04-17 15:06:16 +080072# The PMBR has so many variants. The basic format is defined in
73# https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout, and our
74# implementation, as derived from `cgpt`, is following syslinux as:
75# https://chromium.googlesource.com/chromiumos/platform/vboot_reference/+/master/cgpt/cgpt.h#32
76PMBR_DESCRIPTION = """
77 424s BootCode
78 16s BootGUID
79 L DiskID
80 2s Magic
81 16s LegacyPart0
82 16s LegacyPart1
83 16s LegacyPart2
84 16s LegacyPart3
85 2s Signature
86"""
Hung-Te Linc772e1a2017-04-14 16:50:50 +080087
Hung-Te Linbf8aa272018-04-19 03:02:29 +080088
89class GUID(uuid.UUID):
90 """A special UUID that defaults to upper case in str()."""
91
92 def __str__(self):
93 """Returns GUID in upper case."""
94 return super(GUID, self).__str__().upper()
95
96 @staticmethod
97 def Random():
98 return uuid.uuid4()
99
100
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800101def BitProperty(getter, setter, shift, mask):
102 """A generator for bit-field properties.
103
104 This is used inside a class to manipulate an integer-like variable using
105 properties. The getter and setter should be member functions to change the
106 underlying member data.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800107
108 Args:
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800109 getter: a function to read integer type variable (for all the bits).
110 setter: a function to set the new changed integer type variable.
111 shift: integer for how many bits should be shifted (right).
112 mask: integer for the mask to filter out bit field.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800113 """
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800114 def _getter(self):
115 return (getter(self) >> shift) & mask
116 def _setter(self, value):
117 assert value & mask == value, (
118 'Value %s out of range (mask=%s)' % (value, mask))
119 setter(self, getter(self) & ~(mask << shift) | value << shift)
120 return property(_getter, _setter)
121
122
123class GPTBlob(object):
124 """A decorator class to help accessing GPT blobs as named tuple.
125
126 To use this, specify the blob description (struct format and named tuple field
127 names) above the derived class, for example:
128
129 @GPTBlob(description):
130 class Header(GPTObject):
131 pass
132 """
133 def __init__(self, description):
134 spec = description.split()
135 self.struct_format = '<' + ''.join(spec[::2])
136 self.fields = spec[1::2]
137
138 def __call__(self, cls):
139 new_bases = ((
140 collections.namedtuple(cls.__name__, self.fields),) + cls.__bases__)
141 new_cls = type(cls.__name__, new_bases, dict(cls.__dict__))
142 setattr(new_cls, 'FORMAT', self.struct_format)
143 return new_cls
144
145
146class GPTObject(object):
147 """An object in GUID Partition Table.
148
149 This needs to be decorated by @GPTBlob(description) and inherited by a real
150 class. Properties (not member functions) in CamelCase should be reserved for
151 named tuple attributes.
152
153 To create a new object, use class method ReadFrom(), which takes a stream
154 as input or None to create with all elements set to zero. To make changes to
155 named tuple elements, use member function Clone(changes).
156
157 It is also possible to attach some additional properties to the object as meta
158 data (for example path of the underlying image file). To do that, specify the
159 data as keyword arguments when calling ReadFrom(). These properties will be
160 preserved when you call Clone().
161
162 A special case is "reset named tuple elements of an object but keeping all
163 properties", for example changing a partition object to unused (zeroed).
164 ReadFrom() is a class method so properties won't be copied. You need to
165 call as cls.ReadFrom(None, **p.__dict__), or a short cut - p.CloneAndZero().
166 """
167
168 FORMAT = None
169 """The struct.{pack,unpack} format string, and should be set by GPTBlob."""
170
171 CLONE_CONVERTERS = None
172 """A dict (name, cvt) to convert input arguments into named tuple data.
173
174 `name` is a string for the name of argument to convert.
175 `cvt` is a callable to convert value. The return value may be:
176 - a tuple in (new_name, value): save the value as new name.
177 - otherwise, save the value in original name.
178 Note tuple is an invalid input for struct.unpack so it's used for the
179 special value.
180 """
181
182 @classmethod
183 def ReadFrom(cls, f, **kargs):
184 """Reads and decode an object from stream.
185
186 Args:
187 f: a stream to read blob, or None to decode with all zero bytes.
188 kargs: a dict for additional attributes in object.
189 """
190 if f is None:
191 reader = lambda num: '\x00' * num
192 else:
193 reader = f.read
194 data = cls(*struct.unpack(cls.FORMAT, reader(struct.calcsize(cls.FORMAT))))
195 # Named tuples do not accept kargs in constructor.
196 data.__dict__.update(kargs)
197 return data
198
199 def Clone(self, **dargs):
200 """Clones a new instance with modifications.
201
202 GPT objects are usually named tuples that are immutable, so the only way
203 to make changes is to create a new instance with modifications.
204
205 Args:
206 dargs: a dict with all modifications.
207 """
208 for name, convert in (self.CLONE_CONVERTERS or {}).iteritems():
209 if name not in dargs:
210 continue
211 result = convert(dargs.pop(name))
212 if isinstance(result, tuple):
213 assert len(result) == 2, 'Converted tuple must be (name, value).'
214 dargs[result[0]] = result[1]
215 else:
216 dargs[name] = result
217
218 cloned = self._replace(**dargs)
219 cloned.__dict__.update(self.__dict__)
220 return cloned
221
222 def CloneAndZero(self, **dargs):
223 """Short cut to create a zeroed object while keeping all properties.
224
225 This is very similar to Clone except all named tuple elements will be zero.
226 Also different from class method ReadFrom(None) because this keeps all
227 properties from one object.
228 """
229 cloned = self.ReadFrom(None, **self.__dict__)
230 return cloned.Clone(**dargs) if dargs else cloned
231
232 @property
233 def blob(self):
234 """Returns the object in formatted bytes."""
235 return struct.pack(self.FORMAT, *self)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800236
237
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800238class GPTError(Exception):
239 """All exceptions by GPT."""
240 pass
241
242
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800243class GPT(object):
244 """A GPT helper class.
245
246 To load GPT from an existing disk image file, use `LoadFromFile`.
247 After modifications were made, use `WriteToFile` to commit changes.
248
249 Attributes:
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800250 header: a namedtuple of GPT header.
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800251 pmbr: a namedtuple of Protective MBR.
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800252 partitions: a list of GPT partition entry nametuple.
253 block_size: integer for size of bytes in one block (sector).
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800254 is_secondary: boolean to indicate if the header is from primary or backup.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800255 """
Hung-Te Linf148d322018-04-13 10:24:42 +0800256 DEFAULT_BLOCK_SIZE = 512
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800257 TYPE_GUID_MAP = {
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800258 GUID('00000000-0000-0000-0000-000000000000'): 'Unused',
259 GUID('EBD0A0A2-B9E5-4433-87C0-68B6B72699C7'): 'Linux data',
260 GUID('FE3A2A5D-4F32-41A7-B725-ACCC3285A309'): 'ChromeOS kernel',
261 GUID('3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC'): 'ChromeOS rootfs',
262 GUID('2E0A753D-9E48-43B0-8337-B15192CB1B5E'): 'ChromeOS reserved',
263 GUID('CAB6E88E-ABF3-4102-A07A-D4BB9BE3C1D3'): 'ChromeOS firmware',
264 GUID('C12A7328-F81F-11D2-BA4B-00A0C93EC93B'): 'EFI System Partition',
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800265 }
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800266 TYPE_GUID_FROM_NAME = dict(
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +0800267 ('efi' if v.startswith('EFI') else v.lower().split()[-1], k)
268 for k, v in TYPE_GUID_MAP.iteritems())
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800269 TYPE_GUID_UNUSED = TYPE_GUID_FROM_NAME['unused']
270 TYPE_GUID_CHROMEOS_KERNEL = TYPE_GUID_FROM_NAME['kernel']
271 TYPE_GUID_LIST_BOOTABLE = [
272 TYPE_GUID_CHROMEOS_KERNEL,
273 TYPE_GUID_FROM_NAME['efi'],
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800274 ]
275
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800276 @GPTBlob(PMBR_DESCRIPTION)
277 class ProtectiveMBR(GPTObject):
278 """Protective MBR (PMBR) in GPT."""
279 SIGNATURE = '\x55\xAA'
280 MAGIC = '\x1d\x9a'
281
282 CLONE_CONVERTERS = {
283 'BootGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v
284 }
285
286 @property
287 def boot_guid(self):
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800288 """Returns the BootGUID in decoded (GUID) format."""
289 return GUID(bytes_le=self.BootGUID)
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800290
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800291 @GPTBlob(HEADER_DESCRIPTION)
292 class Header(GPTObject):
293 """Wrapper to Header in GPT."""
294 SIGNATURES = ['EFI PART', 'CHROMEOS']
295 SIGNATURE_IGNORE = 'IGNOREME'
296 DEFAULT_REVISION = '\x00\x00\x01\x00'
297
298 DEFAULT_PARTITION_ENTRIES = 128
299 DEFAULT_PARTITIONS_LBA = 2 # LBA 0 = MBR, LBA 1 = GPT Header.
300
301 def Clone(self, **dargs):
302 """Creates a new instance with modifications.
303
304 GPT objects are usually named tuples that are immutable, so the only way
305 to make changes is to create a new instance with modifications.
306
307 CRC32 is always updated but PartitionArrayCRC32 must be updated explicitly
308 since we can't track changes in GPT.partitions automatically.
309
310 Note since GPTHeader.Clone will always update CRC, we can only check and
311 compute CRC by super(GPT.Header, header).Clone, or header._replace.
312 """
313 dargs['CRC32'] = 0
314 header = super(GPT.Header, self).Clone(**dargs)
315 return super(GPT.Header, header).Clone(CRC32=binascii.crc32(header.blob))
316
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800317 @classmethod
318 def Create(cls, size, block_size, pad_blocks=0,
319 part_entries=DEFAULT_PARTITION_ENTRIES):
320 """Creates a header with default values.
321
322 Args:
323 size: integer of expected image size.
324 block_size: integer for size of each block (sector).
325 pad_blocks: number of preserved sectors between header and partitions.
326 part_entries: number of partitions to include in header.
327 """
328 part_entry_size = struct.calcsize(GPT.Partition.FORMAT)
329 parts_lba = cls.DEFAULT_PARTITIONS_LBA + pad_blocks
330 parts_bytes = part_entries * part_entry_size
331 parts_blocks = parts_bytes / block_size
332 if parts_bytes % block_size:
333 parts_blocks += 1
334 # PartitionsCRC32 must be updated later explicitly.
335 return cls.ReadFrom(None).Clone(
336 Signature=cls.SIGNATURES[0],
337 Revision=cls.DEFAULT_REVISION,
338 HeaderSize=struct.calcsize(cls.FORMAT),
339 CurrentLBA=1,
340 BackupLBA=size / block_size - 1,
341 FirstUsableLBA=parts_lba + parts_blocks,
342 LastUsableLBA=size / block_size - parts_blocks - parts_lba,
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800343 DiskGUID=GUID.Random().get_bytes(),
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800344 PartitionEntriesStartingLBA=parts_lba,
345 PartitionEntriesNumber=part_entries,
346 PartitionEntrySize=part_entry_size,
347 )
348
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800349 class PartitionAttributes(object):
350 """Wrapper for Partition.Attributes.
351
352 This can be created using Partition.attrs, but the changed properties won't
353 apply to underlying Partition until an explicit call with
354 Partition.Clone(Attributes=new_attrs).
355 """
356
357 def __init__(self, attrs):
358 self._attrs = attrs
359
360 @property
361 def raw(self):
362 """Returns the raw integer type attributes."""
363 return self._Get()
364
365 def _Get(self):
366 return self._attrs
367
368 def _Set(self, value):
369 self._attrs = value
370
371 successful = BitProperty(_Get, _Set, 56, 1)
372 tries = BitProperty(_Get, _Set, 52, 0xf)
373 priority = BitProperty(_Get, _Set, 48, 0xf)
374 legacy_boot = BitProperty(_Get, _Set, 2, 1)
375 required = BitProperty(_Get, _Set, 0, 1)
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +0800376 raw_16 = BitProperty(_Get, _Set, 48, 0xffff)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800377
378 @GPTBlob(PARTITION_DESCRIPTION)
379 class Partition(GPTObject):
380 """The partition entry in GPT.
381
382 Please include following properties when creating a Partition object:
383 - image: a string for path to the image file the partition maps to.
384 - number: the 1-based partition number.
385 - block_size: an integer for size of each block (LBA, or sector).
386 """
387 NAMES_ENCODING = 'utf-16-le'
388 NAMES_LENGTH = 72
389
390 CLONE_CONVERTERS = {
391 # TODO(hungte) check if encoded name is too long.
392 'label': lambda l: (None if l is None else
393 ('Names', l.encode(GPT.Partition.NAMES_ENCODING))),
394 'TypeGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v,
395 'UniqueGUID': lambda v: v.bytes_le if isinstance(v, uuid.UUID) else v,
396 'Attributes': (
397 lambda v: v.raw if isinstance(v, GPT.PartitionAttributes) else v),
398 }
399
400 def __str__(self):
401 return '%s#%s' % (self.image, self.number)
402
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800403 @classmethod
404 def Create(cls, block_size, image, number):
405 """Creates a new partition entry with given meta data."""
406 part = cls.ReadFrom(
407 None, image=image, number=number, block_size=block_size)
408 return part
409
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800410 def IsUnused(self):
411 """Returns if the partition is unused and can be allocated."""
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800412 return self.type_guid == GPT.TYPE_GUID_UNUSED
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800413
Hung-Te Linfe724f82018-04-18 15:03:58 +0800414 def IsChromeOSKernel(self):
415 """Returns if the partition is a Chrome OS kernel partition."""
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800416 return self.type_guid == GPT.TYPE_GUID_CHROMEOS_KERNEL
Hung-Te Linfe724f82018-04-18 15:03:58 +0800417
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800418 @property
419 def blocks(self):
420 """Return size of partition in blocks (see block_size)."""
421 return self.LastLBA - self.FirstLBA + 1
422
423 @property
424 def offset(self):
425 """Returns offset to partition in bytes."""
426 return self.FirstLBA * self.block_size
427
428 @property
429 def size(self):
430 """Returns size of partition in bytes."""
431 return self.blocks * self.block_size
432
433 @property
434 def type_guid(self):
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800435 return GUID(bytes_le=self.TypeGUID)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800436
437 @property
438 def unique_guid(self):
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800439 return GUID(bytes_le=self.UniqueGUID)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800440
441 @property
442 def label(self):
443 """Returns the Names in decoded string type."""
444 return self.Names.decode(self.NAMES_ENCODING).strip('\0')
445
446 @property
447 def attrs(self):
448 return GPT.PartitionAttributes(self.Attributes)
449
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800450 def __init__(self):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800451 """GPT constructor.
452
453 See LoadFromFile for how it's usually used.
454 """
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800455 self.pmbr = None
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800456 self.header = None
457 self.partitions = None
Hung-Te Linf148d322018-04-13 10:24:42 +0800458 self.block_size = self.DEFAULT_BLOCK_SIZE
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800459 self.is_secondary = False
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800460
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800461 @classmethod
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800462 def GetTypeGUID(cls, value):
463 """The value may be a GUID in string or a short type string."""
464 guid = cls.TYPE_GUID_FROM_NAME.get(value.lower())
465 return GUID(value) if guid is None else guid
Hung-Te Linf641d302018-04-18 15:09:35 +0800466
467 @classmethod
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800468 def Create(cls, image_name, size, block_size, pad_blocks=0):
469 """Creates a new GPT instance from given size and block_size.
470
471 Args:
472 image_name: a string of underlying disk image file name.
473 size: expected size of disk image.
474 block_size: size of each block (sector) in bytes.
475 pad_blocks: number of blocks between header and partitions array.
476 """
477 gpt = cls()
478 gpt.block_size = block_size
479 gpt.header = cls.Header.Create(size, block_size, pad_blocks)
480 gpt.partitions = [
481 cls.Partition.Create(block_size, image_name, i + 1)
482 for i in xrange(gpt.header.PartitionEntriesNumber)]
483 return gpt
484
485 @classmethod
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800486 def LoadFromFile(cls, image):
487 """Loads a GPT table from give disk image file object.
488
489 Args:
490 image: a string as file path or a file-like object to read from.
491 """
492 if isinstance(image, basestring):
493 with open(image, 'rb') as f:
494 return cls.LoadFromFile(f)
495
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800496 gpt = cls()
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800497 image.seek(0)
498 pmbr = gpt.ProtectiveMBR.ReadFrom(image)
499 if pmbr.Signature == cls.ProtectiveMBR.SIGNATURE:
500 logging.debug('Found MBR signature in %s', image.name)
501 if pmbr.Magic == cls.ProtectiveMBR.MAGIC:
502 logging.debug('Found PMBR in %s', image.name)
503 gpt.pmbr = pmbr
504
Hung-Te Linf148d322018-04-13 10:24:42 +0800505 # Try DEFAULT_BLOCK_SIZE, then 4K.
506 for block_size in [cls.DEFAULT_BLOCK_SIZE, 4096]:
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800507 # Note because there are devices setting Primary as ignored and the
508 # partition table signature accepts 'CHROMEOS' which is also used by
509 # Chrome OS kernel partition, we have to look for Secondary (backup) GPT
510 # first before trying other block sizes, otherwise we may incorrectly
511 # identify a kernel partition as LBA 1 of larger block size system.
512 for i, seek in enumerate([(block_size * 1, os.SEEK_SET),
513 (-block_size, os.SEEK_END)]):
514 image.seek(*seek)
515 header = gpt.Header.ReadFrom(image)
516 if header.Signature in cls.Header.SIGNATURES:
517 gpt.block_size = block_size
518 if i != 0:
519 gpt.is_secondary = True
520 break
521 else:
522 # Nothing found, try next block size.
523 continue
524 # Found a valid signature.
525 break
Hung-Te Linf148d322018-04-13 10:24:42 +0800526 else:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800527 raise GPTError('Invalid signature in GPT header.')
Hung-Te Linf148d322018-04-13 10:24:42 +0800528
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800529 image.seek(gpt.block_size * header.PartitionEntriesStartingLBA)
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800530 def ReadPartition(image, i):
531 p = gpt.Partition.ReadFrom(
532 image, image=image.name, number=i + 1, block_size=gpt.block_size)
533 return p
534
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800535 gpt.header = header
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800536 gpt.partitions = [
537 ReadPartition(image, i) for i in range(header.PartitionEntriesNumber)]
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800538 return gpt
539
Hung-Te Linc5196682018-04-18 22:59:59 +0800540 def GetUsedPartitions(self):
541 """Returns a list of partitions with type GUID not set to unused.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800542
Hung-Te Linc5196682018-04-18 22:59:59 +0800543 Use 'number' property to find the real location of partition in
544 self.partitions.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800545 """
Hung-Te Linc5196682018-04-18 22:59:59 +0800546 return [p for p in self.partitions if not p.IsUnused()]
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800547
548 def GetMaxUsedLBA(self):
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800549 """Returns the max LastLBA from all used partitions."""
Hung-Te Linc5196682018-04-18 22:59:59 +0800550 parts = self.GetUsedPartitions()
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800551 return (max(p.LastLBA for p in parts)
552 if parts else self.header.FirstUsableLBA - 1)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800553
554 def GetPartitionTableBlocks(self, header=None):
555 """Returns the blocks (or LBA) of partition table from given header."""
556 if header is None:
557 header = self.header
558 size = header.PartitionEntrySize * header.PartitionEntriesNumber
Hung-Te Linf148d322018-04-13 10:24:42 +0800559 blocks = size / self.block_size
560 if size % self.block_size:
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800561 blocks += 1
562 return blocks
563
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800564 def GetPartition(self, number):
565 """Gets the Partition by given (1-based) partition number.
566
567 Args:
568 number: an integer as 1-based partition number.
569 """
570 if not 0 < number <= len(self.partitions):
571 raise GPTError('Invalid partition number %s.' % number)
572 return self.partitions[number - 1]
573
574 def UpdatePartition(self, part, number=None):
575 """Updates the entry in partition table by given Partition object.
576
577 Args:
578 part: a Partition GPT object.
579 number: an integer as 1-based partition number. None to use part.number.
580 """
581 number = part.number if number is None else number
582 self.partitions[number - 1] = part
583
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800584 def Resize(self, new_size):
585 """Adjust GPT for a disk image in given size.
586
587 Args:
588 new_size: Integer for new size of disk image file.
589 """
Hung-Te Linf148d322018-04-13 10:24:42 +0800590 old_size = self.block_size * (self.header.BackupLBA + 1)
591 if new_size % self.block_size:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800592 raise GPTError(
593 'New file size %d is not valid for image files.' % new_size)
Hung-Te Linf148d322018-04-13 10:24:42 +0800594 new_blocks = new_size / self.block_size
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800595 if old_size != new_size:
596 logging.warn('Image size (%d, LBA=%d) changed from %d (LBA=%d).',
Hung-Te Linf148d322018-04-13 10:24:42 +0800597 new_size, new_blocks, old_size, old_size / self.block_size)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800598 else:
599 logging.info('Image size (%d, LBA=%d) not changed.',
600 new_size, new_blocks)
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800601 return
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800602
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800603 # Expected location
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800604 backup_lba = new_blocks - 1
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800605 last_usable_lba = backup_lba - self.header.FirstUsableLBA
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800606
Hung-Te Lind3a2e9a2018-04-19 13:07:26 +0800607 if last_usable_lba < self.header.LastUsableLBA:
608 max_used_lba = self.GetMaxUsedLBA()
609 if last_usable_lba < max_used_lba:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800610 raise GPTError('Backup partition tables will overlap used partitions')
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800611
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800612 self.header = self.header.Clone(
613 BackupLBA=backup_lba, LastUsableLBA=last_usable_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800614
615 def GetFreeSpace(self):
616 """Returns the free (available) space left according to LastUsableLBA."""
617 max_lba = self.GetMaxUsedLBA()
618 assert max_lba <= self.header.LastUsableLBA, "Partitions too large."
Hung-Te Linf148d322018-04-13 10:24:42 +0800619 return self.block_size * (self.header.LastUsableLBA - max_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800620
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800621 def ExpandPartition(self, number):
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800622 """Expands a given partition to last usable LBA.
623
624 Args:
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800625 number: an integer to specify partition in 1-based number.
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800626
627 Returns:
628 (old_blocks, new_blocks) for size in blocks.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800629 """
630 # Assume no partitions overlap, we need to make sure partition[i] has
631 # largest LBA.
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800632 p = self.GetPartition(number)
633 if p.IsUnused():
634 raise GPTError('Partition %s is unused.' % p)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800635 max_used_lba = self.GetMaxUsedLBA()
Hung-Te Linc5196682018-04-18 22:59:59 +0800636 # TODO(hungte) We can do more by finding free space after i.
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800637 if max_used_lba > p.LastLBA:
Hung-Te Lin4dfd3302018-04-17 14:47:52 +0800638 raise GPTError(
Hung-Te Linc5196682018-04-18 22:59:59 +0800639 'Cannot expand %s because it is not allocated at last.' % p)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800640
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800641 old_blocks = p.blocks
642 p = p.Clone(LastLBA=self.header.LastUsableLBA)
643 new_blocks = p.blocks
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800644 self.UpdatePartition(p)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800645 logging.warn(
646 '%s expanded, size in LBA: %d -> %d.', p, old_blocks, new_blocks)
647 return (old_blocks, new_blocks)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800648
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800649 def GetIgnoredHeader(self):
650 """Returns a primary header with signature set to 'IGNOREME'.
651
652 This is a special trick to enforce using backup header, when there is
653 some security exploit in LBA1.
654 """
655 return self.header.Clone(Signature=self.header.SIGNATURE_IGNORE)
656
Hung-Te Lin3b491672018-04-19 01:41:20 +0800657 def CheckIntegrity(self):
658 """Checks if the GPT objects all look good."""
659 # Check if the header allocation looks good. CurrentLBA and
660 # PartitionEntriesStartingLBA should be all outside [FirstUsableLBA,
661 # LastUsableLBA].
662 header = self.header
663 entries_first_lba = header.PartitionEntriesStartingLBA
664 entries_last_lba = entries_first_lba + self.GetPartitionTableBlocks() - 1
665
666 def CheckOutsideUsable(name, lba, outside_entries=False):
667 if lba < 1:
668 raise GPTError('%s should not live in LBA %s.' % (name, lba))
669 if lba > max(header.BackupLBA, header.CurrentLBA):
670 # Note this is "in theory" possible, but we want to report this as
671 # error as well, since it usually leads to error.
672 raise GPTError('%s (%s) should not be larger than BackupLBA (%s).' %
673 (name, lba, header.BackupLBA))
674 if header.FirstUsableLBA <= lba <= header.LastUsableLBA:
675 raise GPTError('%s (%s) should not be included in usable LBAs [%s,%s]' %
676 (name, lba, header.FirstUsableLBA, header.LastUsableLBA))
677 if outside_entries and entries_first_lba <= lba <= entries_last_lba:
678 raise GPTError('%s (%s) should be outside partition entries [%s,%s]' %
679 (name, lba, entries_first_lba, entries_last_lba))
680 CheckOutsideUsable('Header', header.CurrentLBA, True)
681 CheckOutsideUsable('Backup header', header.BackupLBA, True)
682 CheckOutsideUsable('Partition entries', entries_first_lba)
683 CheckOutsideUsable('Partition entries end', entries_last_lba)
684
685 parts = self.GetUsedPartitions()
686 # Check if partition entries overlap with each other.
687 lba_list = [(p.FirstLBA, p.LastLBA, p) for p in parts]
688 lba_list.sort(key=lambda t: t[0])
689 for i in xrange(len(lba_list) - 1):
690 if lba_list[i][1] >= lba_list[i + 1][0]:
691 raise GPTError('Overlap in partition entries: [%s,%s]%s, [%s,%s]%s.' %
692 (lba_list[i] + lba_list[i + 1]))
693 # Now, check the first and last partition.
694 if lba_list:
695 p = lba_list[0][2]
696 if p.FirstLBA < header.FirstUsableLBA:
697 raise GPTError(
698 'Partition %s must not go earlier (%s) than FirstUsableLBA=%s' %
699 (p, p.FirstLBA, header.FirstLBA))
700 p = lba_list[-1][2]
701 if p.LastLBA > header.LastUsableLBA:
702 raise GPTError(
703 'Partition %s must not go further (%s) than LastUsableLBA=%s' %
704 (p, p.LastLBA, header.LastLBA))
705 # Check if UniqueGUIDs are not unique.
706 if len(set(p.UniqueGUID for p in parts)) != len(parts):
707 raise GPTError('Partition UniqueGUIDs are duplicated.')
708 # Check if CRCs match.
709 if (binascii.crc32(''.join(p.blob for p in self.partitions)) !=
710 header.PartitionArrayCRC32):
711 raise GPTError('GPT Header PartitionArrayCRC32 does not match.')
712
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800713 def UpdateChecksum(self):
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800714 """Updates all checksum fields in GPT objects.
715
716 The Header.CRC32 is automatically updated in Header.Clone().
717 """
718 parts = ''.join(p.blob for p in self.partitions)
719 self.header = self.header.Clone(
720 PartitionArrayCRC32=binascii.crc32(parts))
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800721
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800722 def GetBackupHeader(self, header):
723 """Returns the backup header according to given header."""
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800724 partitions_starting_lba = (
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800725 header.BackupLBA - self.GetPartitionTableBlocks())
726 return header.Clone(
727 BackupLBA=header.CurrentLBA,
728 CurrentLBA=header.BackupLBA,
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800729 PartitionEntriesStartingLBA=partitions_starting_lba)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800730
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800731 @classmethod
732 def WriteProtectiveMBR(cls, image, create, bootcode=None, boot_guid=None):
733 """Writes a protective MBR to given file.
734
735 Each MBR is 512 bytes: 424 bytes for bootstrap code, 16 bytes of boot GUID,
736 4 bytes of disk id, 2 bytes of bootcode magic, 4*16 for 4 partitions, and 2
737 byte as signature. cgpt has hard-coded the CHS and bootstrap magic values so
738 we can follow that.
739
740 Args:
741 create: True to re-create PMBR structure.
742 bootcode: a blob of new boot code.
743 boot_guid a blob for new boot GUID.
744
745 Returns:
746 The written PMBR structure.
747 """
748 if isinstance(image, basestring):
749 with open(image, 'rb+') as f:
750 return cls.WriteProtectiveMBR(f, create, bootcode, boot_guid)
751
752 image.seek(0)
753 assert struct.calcsize(cls.ProtectiveMBR.FORMAT) == cls.DEFAULT_BLOCK_SIZE
754 pmbr = cls.ProtectiveMBR.ReadFrom(image)
755
756 if create:
757 legacy_sectors = min(
758 0x100000000,
759 os.path.getsize(image.name) / cls.DEFAULT_BLOCK_SIZE) - 1
760 # Partition 0 must have have the fixed CHS with number of sectors
761 # (calculated as legacy_sectors later).
762 part0 = ('00000200eeffffff01000000'.decode('hex') +
763 struct.pack('<I', legacy_sectors))
764 # Partition 1~3 should be all zero.
765 part1 = '\x00' * 16
766 assert len(part0) == len(part1) == 16, 'MBR entry is wrong.'
767 pmbr = pmbr.Clone(
768 BootGUID=cls.TYPE_GUID_UNUSED,
769 DiskID=0,
770 Magic=cls.ProtectiveMBR.MAGIC,
771 LegacyPart0=part0,
772 LegacyPart1=part1,
773 LegacyPart2=part1,
774 LegacyPart3=part1,
775 Signature=cls.ProtectiveMBR.SIGNATURE)
776
777 if bootcode:
778 if len(bootcode) > len(pmbr.BootCode):
779 logging.info(
780 'Bootcode is larger (%d > %d)!', len(bootcode), len(pmbr.BootCode))
781 bootcode = bootcode[:len(pmbr.BootCode)]
782 pmbr = pmbr.Clone(BootCode=bootcode)
783 if boot_guid:
784 pmbr = pmbr.Clone(BootGUID=boot_guid)
785
786 blob = pmbr.blob
787 assert len(blob) == cls.DEFAULT_BLOCK_SIZE
788 image.seek(0)
789 image.write(blob)
790 return pmbr
791
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800792 def WriteToFile(self, image):
793 """Updates partition table in a disk image file.
794
795 Args:
796 image: a string as file path or a file-like object to write into.
797 """
798 if isinstance(image, basestring):
799 with open(image, 'rb+') as f:
800 return self.WriteToFile(f)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800801
802 def WriteData(name, blob, lba):
803 """Writes a blob into given location."""
804 logging.info('Writing %s in LBA %d (offset %d)',
Hung-Te Linf148d322018-04-13 10:24:42 +0800805 name, lba, lba * self.block_size)
Hung-Te Lin6977ae12018-04-17 12:20:32 +0800806 image.seek(lba * self.block_size)
807 image.write(blob)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800808
809 self.UpdateChecksum()
Hung-Te Lin3b491672018-04-19 01:41:20 +0800810 self.CheckIntegrity()
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800811 parts_blob = ''.join(p.blob for p in self.partitions)
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800812
813 header = self.header
814 WriteData('GPT Header', header.blob, header.CurrentLBA)
815 WriteData('GPT Partitions', parts_blob, header.PartitionEntriesStartingLBA)
816 logging.info(
817 'Usable LBA: First=%d, Last=%d', header.FirstUsableLBA,
818 header.LastUsableLBA)
819
820 if not self.is_secondary:
821 # When is_secondary is True, the header we have is actually backup header.
822 backup_header = self.GetBackupHeader(self.header)
823 WriteData(
824 'Backup Partitions', parts_blob,
825 backup_header.PartitionEntriesStartingLBA)
826 WriteData(
827 'Backup Header', backup_header.blob, backup_header.CurrentLBA)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800828
829
830class GPTCommands(object):
831 """Collection of GPT sub commands for command line to use.
832
833 The commands are derived from `cgpt`, but not necessary to be 100% compatible
834 with cgpt.
835 """
836
837 FORMAT_ARGS = [
Peter Shihc7156ca2018-02-26 14:46:24 +0800838 ('begin', 'beginning sector'),
Hung-Te Lin49ac3c22018-04-17 14:37:54 +0800839 ('size', 'partition size (in sectors)'),
Peter Shihc7156ca2018-02-26 14:46:24 +0800840 ('type', 'type guid'),
841 ('unique', 'unique guid'),
842 ('label', 'label'),
843 ('Successful', 'Successful flag'),
844 ('Tries', 'Tries flag'),
845 ('Priority', 'Priority flag'),
846 ('Legacy', 'Legacy Boot flag'),
847 ('Attribute', 'raw 16-bit attribute value (bits 48-63)')]
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800848
849 def __init__(self):
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800850 commands = dict(
851 (command.lower(), getattr(self, command)())
852 for command in dir(self)
853 if (isinstance(getattr(self, command), type) and
854 issubclass(getattr(self, command), self.SubCommand) and
855 getattr(self, command) is not self.SubCommand)
856 )
857 self.commands = commands
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800858
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800859 def DefineArgs(self, parser):
860 """Defines all available commands to an argparser subparsers instance."""
861 subparsers = parser.add_subparsers(help='Sub-command help.', dest='command')
862 for name, instance in sorted(self.commands.iteritems()):
863 parser = subparsers.add_parser(
864 name, description=instance.__doc__,
865 formatter_class=argparse.RawDescriptionHelpFormatter,
866 help=instance.__doc__.splitlines()[0])
867 instance.DefineArgs(parser)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800868
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800869 def Execute(self, args):
870 """Execute the sub commands by given parsed arguments."""
Hung-Te Linf641d302018-04-18 15:09:35 +0800871 return self.commands[args.command].Execute(args)
Hung-Te Linc772e1a2017-04-14 16:50:50 +0800872
Hung-Te Lin5cb0c312018-04-17 14:56:43 +0800873 class SubCommand(object):
874 """A base class for sub commands to derive from."""
875
876 def DefineArgs(self, parser):
877 """Defines command line arguments to argparse parser.
878
879 Args:
880 parser: An argparse parser instance.
881 """
882 del parser # Unused.
883 raise NotImplementedError
884
885 def Execute(self, args):
886 """Execute the command.
887
888 Args:
889 args: An argparse parsed namespace.
890 """
891 del args # Unused.
892 raise NotImplementedError
893
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800894 class Create(SubCommand):
895 """Create or reset GPT headers and tables.
896
897 Create or reset an empty GPT.
898 """
899
900 def DefineArgs(self, parser):
901 parser.add_argument(
902 '-z', '--zero', action='store_true',
903 help='Zero the sectors of the GPT table and entries')
904 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800905 '-p', '--pad-blocks', type=int, default=0,
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800906 help=('Size (in blocks) of the disk to pad between the '
907 'primary GPT header and its entries, default %(default)s'))
908 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800909 '--block-size', type=int, default=GPT.DEFAULT_BLOCK_SIZE,
Hung-Te Lin6c3575a2018-04-17 15:00:49 +0800910 help='Size of each block (sector) in bytes.')
911 parser.add_argument(
912 'image_file', type=argparse.FileType('rb+'),
913 help='Disk image file to create.')
914
915 def Execute(self, args):
916 block_size = args.block_size
917 gpt = GPT.Create(
918 args.image_file.name, os.path.getsize(args.image_file.name),
919 block_size, args.pad_blocks)
920 if args.zero:
921 # In theory we only need to clear LBA 1, but to make sure images already
922 # initialized with different block size won't have GPT signature in
923 # different locations, we should zero until first usable LBA.
924 args.image_file.seek(0)
925 args.image_file.write('\0' * block_size * gpt.header.FirstUsableLBA)
926 gpt.WriteToFile(args.image_file)
927 print('OK: Created GPT for %s' % args.image_file.name)
928
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800929 class Boot(SubCommand):
930 """Edit the PMBR sector for legacy BIOSes.
931
932 With no options, it will just print the PMBR boot guid.
933 """
934
935 def DefineArgs(self, parser):
936 parser.add_argument(
937 '-i', '--number', type=int,
938 help='Set bootable partition')
939 parser.add_argument(
940 '-b', '--bootloader', type=argparse.FileType('r'),
941 help='Install bootloader code in the PMBR')
942 parser.add_argument(
943 '-p', '--pmbr', action='store_true',
944 help='Create legacy PMBR partition table')
945 parser.add_argument(
946 'image_file', type=argparse.FileType('rb+'),
947 help='Disk image file to change PMBR.')
948
949 def Execute(self, args):
950 """Rebuilds the protective MBR."""
951 bootcode = args.bootloader.read() if args.bootloader else None
952 boot_guid = None
953 if args.number is not None:
954 gpt = GPT.LoadFromFile(args.image_file)
Hung-Te Lin5f0dea42018-04-18 23:20:11 +0800955 boot_guid = gpt.GetPartition(args.number).UniqueGUID
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800956 pmbr = GPT.WriteProtectiveMBR(
957 args.image_file, args.pmbr, bootcode=bootcode, boot_guid=boot_guid)
958
Hung-Te Linbf8aa272018-04-19 03:02:29 +0800959 print(pmbr.boot_guid)
960
Hung-Te Linc6e009c2018-04-17 15:06:16 +0800961
Hung-Te Linc34d89c2018-04-17 15:11:34 +0800962 class Legacy(SubCommand):
963 """Switch between GPT and Legacy GPT.
964
965 Switch GPT header signature to "CHROMEOS".
966 """
967
968 def DefineArgs(self, parser):
969 parser.add_argument(
970 '-e', '--efi', action='store_true',
971 help='Switch GPT header signature back to "EFI PART"')
972 parser.add_argument(
973 '-p', '--primary-ignore', action='store_true',
974 help='Switch primary GPT header signature to "IGNOREME"')
975 parser.add_argument(
976 'image_file', type=argparse.FileType('rb+'),
977 help='Disk image file to change.')
978
979 def Execute(self, args):
980 gpt = GPT.LoadFromFile(args.image_file)
981 # cgpt behavior: if -p is specified, -e is ignored.
982 if args.primary_ignore:
983 if gpt.is_secondary:
984 raise GPTError('Sorry, the disk already has primary GPT ignored.')
985 args.image_file.seek(gpt.header.CurrentLBA * gpt.block_size)
986 args.image_file.write(gpt.header.SIGNATURE_IGNORE)
987 gpt.header = gpt.GetBackupHeader(self.header)
988 gpt.is_secondary = True
989 else:
990 new_signature = gpt.Header.SIGNATURES[0 if args.efi else 1]
991 gpt.header = gpt.header.Clone(Signature=new_signature)
992 gpt.WriteToFile(args.image_file)
993 if args.primary_ignore:
994 print('OK: Set %s primary GPT header to %s.' %
995 (args.image_file.name, gpt.header.SIGNATURE_IGNORE))
996 else:
997 print('OK: Changed GPT signature for %s to %s.' %
998 (args.image_file.name, new_signature))
999
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001000 class Repair(SubCommand):
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001001 """Repair damaged GPT headers and tables."""
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001002
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001003 def DefineArgs(self, parser):
1004 parser.add_argument(
1005 'image_file', type=argparse.FileType('rb+'),
1006 help='Disk image file to repair.')
1007
1008 def Execute(self, args):
1009 gpt = GPT.LoadFromFile(args.image_file)
1010 gpt.Resize(os.path.getsize(args.image_file.name))
1011 gpt.WriteToFile(args.image_file)
1012 print('Disk image file %s repaired.' % args.image_file.name)
1013
1014 class Expand(SubCommand):
1015 """Expands a GPT partition to all available free space."""
1016
1017 def DefineArgs(self, parser):
1018 parser.add_argument(
1019 '-i', '--number', type=int, required=True,
1020 help='The partition to expand.')
1021 parser.add_argument(
1022 'image_file', type=argparse.FileType('rb+'),
1023 help='Disk image file to modify.')
1024
1025 def Execute(self, args):
1026 gpt = GPT.LoadFromFile(args.image_file)
Hung-Te Lin5f0dea42018-04-18 23:20:11 +08001027 old_blocks, new_blocks = gpt.ExpandPartition(args.number)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001028 gpt.WriteToFile(args.image_file)
1029 if old_blocks < new_blocks:
1030 print(
1031 'Partition %s on disk image file %s has been extended '
1032 'from %s to %s .' %
1033 (args.number, args.image_file.name, old_blocks * gpt.block_size,
1034 new_blocks * gpt.block_size))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001035 else:
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001036 print('Nothing to expand for disk image %s partition %s.' %
1037 (args.image_file.name, args.number))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001038
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001039 class Add(SubCommand):
1040 """Add, edit, or remove a partition entry.
1041
1042 Use the -i option to modify an existing partition.
1043 The -b, -s, and -t options must be given for new partitions.
1044
1045 The partition type may also be given as one of these aliases:
1046
1047 firmware ChromeOS firmware
1048 kernel ChromeOS kernel
1049 rootfs ChromeOS rootfs
1050 data Linux data
1051 reserved ChromeOS reserved
1052 efi EFI System Partition
1053 unused Unused (nonexistent) partition
1054 """
1055 def DefineArgs(self, parser):
1056 parser.add_argument(
1057 '-i', '--number', type=int,
1058 help='Specify partition (default is next available)')
1059 parser.add_argument(
1060 '-b', '--begin', type=int,
1061 help='Beginning sector')
1062 parser.add_argument(
1063 '-s', '--sectors', type=int,
1064 help='Size in sectors (logical blocks).')
1065 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001066 '-t', '--type-guid', type=GPT.GetTypeGUID,
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001067 help='Partition Type GUID')
1068 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001069 '-u', '--unique-guid', type=GUID,
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001070 help='Partition Unique ID')
1071 parser.add_argument(
1072 '-l', '--label',
1073 help='Label')
1074 parser.add_argument(
1075 '-S', '--successful', type=int, choices=xrange(2),
1076 help='set Successful flag')
1077 parser.add_argument(
1078 '-T', '--tries', type=int,
1079 help='set Tries flag (0-15)')
1080 parser.add_argument(
1081 '-P', '--priority', type=int,
1082 help='set Priority flag (0-15)')
1083 parser.add_argument(
1084 '-R', '--required', type=int, choices=xrange(2),
1085 help='set Required flag')
1086 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001087 '-B', '--boot-legacy', dest='legacy_boot', type=int,
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001088 choices=xrange(2),
1089 help='set Legacy Boot flag')
1090 parser.add_argument(
1091 '-A', '--attribute', dest='raw_16', type=int,
1092 help='set raw 16-bit attribute value (bits 48-63)')
1093 parser.add_argument(
1094 'image_file', type=argparse.FileType('rb+'),
1095 help='Disk image file to modify.')
1096
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001097 def Execute(self, args):
1098 gpt = GPT.LoadFromFile(args.image_file)
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001099 number = args.number
1100 if number is None:
Hung-Te Linc5196682018-04-18 22:59:59 +08001101 number = next(p for p in gpt.partitions if p.IsUnused()).number
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001102
1103 # First and last LBA must be calculated explicitly because the given
1104 # argument is size.
Hung-Te Lin5f0dea42018-04-18 23:20:11 +08001105 part = gpt.GetPartition(number)
Hung-Te Linc5196682018-04-18 22:59:59 +08001106 is_new_part = part.IsUnused()
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001107
1108 if is_new_part:
1109 part = part.ReadFrom(None, **part.__dict__).Clone(
Hung-Te Linc5196682018-04-18 22:59:59 +08001110 FirstLBA=gpt.GetMaxUsedLBA() + 1,
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001111 LastLBA=gpt.header.LastUsableLBA,
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001112 UniqueGUID=GUID.Random(),
Hung-Te Linf641d302018-04-18 15:09:35 +08001113 TypeGUID=gpt.GetTypeGUID('data'))
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001114
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001115 def UpdateAttr(name):
1116 value = getattr(args, name)
1117 if value is None:
1118 return
1119 setattr(attrs, name, value)
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001120
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001121 def GetArg(arg_value, default_value):
1122 return default_value if arg_value is None else arg_value
1123
1124 attrs = part.attrs
1125 for name in ['legacy_boot', 'required', 'priority', 'tries',
1126 'successful', 'raw_16']:
1127 UpdateAttr(name)
1128 first_lba = GetArg(args.begin, part.FirstLBA)
1129 part = part.Clone(
1130 label=GetArg(args.label, part.label),
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001131 FirstLBA=first_lba,
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001132 LastLBA=first_lba - 1 + GetArg(args.sectors, part.blocks),
1133 TypeGUID=GetArg(args.type_guid, part.TypeGUID),
1134 UniqueGUID=GetArg(args.unique_guid, part.UniqueGUID),
1135 Attributes=attrs)
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001136
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001137 # Wipe partition again if it should be empty.
1138 if part.IsUnused():
1139 part = part.ReadFrom(None, **part.__dict__)
1140
Hung-Te Lin5f0dea42018-04-18 23:20:11 +08001141 gpt.UpdatePartition(part)
Hung-Te Linfcd1a8d2018-04-17 15:15:01 +08001142 gpt.WriteToFile(args.image_file)
1143 if part.IsUnused():
1144 # If we do ('%s' % part) there will be TypeError.
1145 print('OK: Deleted (zeroed) %s.' % (part,))
1146 else:
1147 print('OK: %s %s (%s+%s).' %
1148 ('Added' if is_new_part else 'Modified',
1149 part, part.FirstLBA, part.blocks))
1150
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001151 class Show(SubCommand):
1152 """Show partition table and entries.
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001153
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001154 Display the GPT table.
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001155 """
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001156
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001157 def DefineArgs(self, parser):
1158 parser.add_argument(
1159 '--numeric', '-n', action='store_true',
1160 help='Numeric output only.')
1161 parser.add_argument(
1162 '--quick', '-q', action='store_true',
1163 help='Quick output.')
1164 parser.add_argument(
1165 '-i', '--number', type=int,
1166 help='Show specified partition only, with format args.')
1167 for name, help_str in GPTCommands.FORMAT_ARGS:
1168 # TODO(hungte) Alert if multiple args were specified.
1169 parser.add_argument(
1170 '--%s' % name, '-%c' % name[0], action='store_true',
1171 help='[format] %s.' % help_str)
1172 parser.add_argument(
1173 'image_file', type=argparse.FileType('rb'),
1174 help='Disk image file to show.')
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001175
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001176 def Execute(self, args):
1177 """Show partition table and entries."""
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001178
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001179 def FormatTypeGUID(p):
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001180 guid = p.type_guid
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001181 if not args.numeric:
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001182 names = gpt.TYPE_GUID_MAP.get(guid)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001183 if names:
1184 return names
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001185 return str(guid)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001186
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001187 def IsBootableType(guid):
1188 if not guid:
1189 return False
1190 return guid in gpt.TYPE_GUID_LIST_BOOTABLE
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001191
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001192 def FormatAttribute(attrs, chromeos_kernel=False):
1193 if args.numeric:
1194 return '[%x]' % (attrs.raw >> 48)
1195 results = []
1196 if chromeos_kernel:
1197 results += [
1198 'priority=%d' % attrs.priority,
1199 'tries=%d' % attrs.tries,
1200 'successful=%d' % attrs.successful]
1201 if attrs.required:
1202 results += ['required=1']
1203 if attrs.legacy_boot:
1204 results += ['legacy_boot=1']
1205 return ' '.join(results)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001206
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001207 def ApplyFormatArgs(p):
1208 if args.begin:
1209 return p.FirstLBA
1210 elif args.size:
1211 return p.blocks
1212 elif args.type:
1213 return FormatTypeGUID(p)
1214 elif args.unique:
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001215 return p.unique_guid
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001216 elif args.label:
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001217 return p.label
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001218 elif args.Successful:
1219 return p.attrs.successful
1220 elif args.Priority:
1221 return p.attrs.priority
1222 elif args.Tries:
1223 return p.attrs.tries
1224 elif args.Legacy:
1225 return p.attrs.legacy_boot
1226 elif args.Attribute:
1227 return '[%x]' % (p.Attributes >> 48)
1228 else:
1229 return None
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001230
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001231 def IsFormatArgsSpecified():
1232 return any(getattr(args, arg[0]) for arg in GPTCommands.FORMAT_ARGS)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001233
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001234 gpt = GPT.LoadFromFile(args.image_file)
1235 logging.debug('%r', gpt.header)
1236 fmt = '%12s %11s %7s %s'
1237 fmt2 = '%32s %s: %s'
1238 header = ('start', 'size', 'part', 'contents')
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001239
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001240 if IsFormatArgsSpecified() and args.number is None:
1241 raise GPTError('Format arguments must be used with -i.')
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001242
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001243 if not (args.number is None or
1244 0 < args.number <= gpt.header.PartitionEntriesNumber):
1245 raise GPTError('Invalid partition number: %d' % args.number)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001246
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001247 partitions = gpt.partitions
1248 do_print_gpt_blocks = False
1249 if not (args.quick or IsFormatArgsSpecified()):
1250 print(fmt % header)
1251 if args.number is None:
1252 do_print_gpt_blocks = True
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001253
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001254 if do_print_gpt_blocks:
Hung-Te Linc34d89c2018-04-17 15:11:34 +08001255 if gpt.pmbr:
1256 print(fmt % (0, 1, '', 'PMBR'))
1257 if gpt.is_secondary:
1258 print(fmt % (gpt.header.BackupLBA, 1, 'IGNORED', 'Pri GPT header'))
1259 else:
1260 print(fmt % (gpt.header.CurrentLBA, 1, '', 'Pri GPT header'))
1261 print(fmt % (gpt.header.PartitionEntriesStartingLBA,
1262 gpt.GetPartitionTableBlocks(), '', 'Pri GPT table'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001263
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001264 for p in partitions:
1265 if args.number is None:
1266 # Skip unused partitions.
1267 if p.IsUnused():
1268 continue
1269 elif p.number != args.number:
1270 continue
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001271
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001272 if IsFormatArgsSpecified():
1273 print(ApplyFormatArgs(p))
1274 continue
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001275
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001276 print(fmt % (p.FirstLBA, p.blocks, p.number,
1277 FormatTypeGUID(p) if args.quick else
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001278 'Label: "%s"' % p.label))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001279
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001280 if not args.quick:
1281 print(fmt2 % ('', 'Type', FormatTypeGUID(p)))
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001282 print(fmt2 % ('', 'UUID', p.unique_guid))
1283 if args.numeric or IsBootableType(p.type_guid):
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001284 print(fmt2 % ('', 'Attr', FormatAttribute(
Hung-Te Linfe724f82018-04-18 15:03:58 +08001285 p.attrs, p.IsChromeOSKernel())))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001286
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001287 if do_print_gpt_blocks:
Hung-Te Linc34d89c2018-04-17 15:11:34 +08001288 if gpt.is_secondary:
1289 header = gpt.header
1290 else:
1291 f = args.image_file
1292 f.seek(gpt.header.BackupLBA * gpt.block_size)
1293 header = gpt.Header.ReadFrom(f)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001294 print(fmt % (header.PartitionEntriesStartingLBA,
1295 gpt.GetPartitionTableBlocks(header), '',
1296 'Sec GPT table'))
1297 print(fmt % (header.CurrentLBA, 1, '', 'Sec GPT header'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001298
Hung-Te Lin3b491672018-04-19 01:41:20 +08001299 # Check integrity after showing all fields.
1300 gpt.CheckIntegrity()
1301
Hung-Te Linfe724f82018-04-18 15:03:58 +08001302 class Prioritize(SubCommand):
1303 """Reorder the priority of all kernel partitions.
1304
1305 Reorder the priority of all active ChromeOS Kernel partitions.
1306
1307 With no options this will set the lowest active kernel to priority 1 while
1308 maintaining the original order.
1309 """
1310
1311 def DefineArgs(self, parser):
1312 parser.add_argument(
1313 '-P', '--priority', type=int,
1314 help=('Highest priority to use in the new ordering. '
1315 'The other partitions will be ranked in decreasing '
1316 'priority while preserving their original order. '
1317 'If necessary the lowest ranks will be coalesced. '
1318 'No active kernels will be lowered to priority 0.'))
1319 parser.add_argument(
1320 '-i', '--number', type=int,
1321 help='Specify the partition to make the highest in the new order.')
1322 parser.add_argument(
1323 '-f', '--friends', action='store_true',
1324 help=('Friends of the given partition (those with the same '
1325 'starting priority) are also updated to the new '
1326 'highest priority. '))
1327 parser.add_argument(
1328 'image_file', type=argparse.FileType('rb+'),
1329 help='Disk image file to prioritize.')
1330
1331 def Execute(self, args):
1332 gpt = GPT.LoadFromFile(args.image_file)
1333 parts = [p for p in gpt.partitions if p.IsChromeOSKernel()]
1334 prios = list(set(p.attrs.priority for p in parts if p.attrs.priority))
1335 prios.sort(reverse=True)
1336 groups = [[p for p in parts if p.attrs.priority == priority]
1337 for priority in prios]
1338 if args.number:
Hung-Te Lin5f0dea42018-04-18 23:20:11 +08001339 p = gpt.GetPartition(args.number)
Hung-Te Linfe724f82018-04-18 15:03:58 +08001340 if p not in parts:
1341 raise GPTError('%s is not a ChromeOS kernel.' % p)
1342 if args.friends:
1343 group0 = [f for f in parts if f.attrs.priority == p.attrs.priority]
1344 else:
1345 group0 = [p]
1346 groups.insert(0, group0)
1347
1348 # Max priority is 0xf.
1349 highest = min(args.priority or len(prios), 0xf)
1350 logging.info('New highest priority: %s', highest)
1351 done = []
1352
1353 new_priority = highest
1354 for g in groups:
1355 has_new_part = False
1356 for p in g:
1357 if p.number in done:
1358 continue
1359 done.append(p.number)
1360 attrs = p.attrs
1361 old_priority = attrs.priority
1362 assert new_priority > 0, 'Priority must be > 0.'
1363 attrs.priority = new_priority
1364 p = p.Clone(Attributes=attrs)
Hung-Te Lin5f0dea42018-04-18 23:20:11 +08001365 gpt.UpdatePartition(p)
Hung-Te Linfe724f82018-04-18 15:03:58 +08001366 has_new_part = True
1367 logging.info('%s priority changed from %s to %s.', p, old_priority,
1368 new_priority)
1369 if has_new_part:
1370 new_priority -= 1
1371
1372 gpt.WriteToFile(args.image_file)
1373
Hung-Te Linf641d302018-04-18 15:09:35 +08001374 class Find(SubCommand):
1375 """Locate a partition by its GUID.
1376
1377 Find a partition by its UUID or label. With no specified DRIVE it scans all
1378 physical drives.
1379
1380 The partition type may also be given as one of these aliases:
1381
1382 firmware ChromeOS firmware
1383 kernel ChromeOS kernel
1384 rootfs ChromeOS rootfs
1385 data Linux data
1386 reserved ChromeOS reserved
1387 efi EFI System Partition
1388 unused Unused (nonexistent) partition
1389 """
1390 def DefineArgs(self, parser):
1391 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001392 '-t', '--type-guid', type=GPT.GetTypeGUID,
Hung-Te Linf641d302018-04-18 15:09:35 +08001393 help='Search for Partition Type GUID')
1394 parser.add_argument(
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001395 '-u', '--unique-guid', type=GUID,
Hung-Te Linf641d302018-04-18 15:09:35 +08001396 help='Search for Partition Unique GUID')
1397 parser.add_argument(
1398 '-l', '--label',
1399 help='Search for Label')
1400 parser.add_argument(
1401 '-n', '--numeric', action='store_true',
1402 help='Numeric output only.')
1403 parser.add_argument(
1404 '-1', '--single-match', action='store_true',
1405 help='Fail if more than one match is found.')
1406 parser.add_argument(
1407 '-M', '--match-file', type=str,
1408 help='Matching partition data must also contain MATCH_FILE content.')
1409 parser.add_argument(
1410 '-O', '--offset', type=int, default=0,
1411 help='Byte offset into partition to match content (default 0).')
1412 parser.add_argument(
1413 'drive', type=argparse.FileType('rb+'), nargs='?',
1414 help='Drive or disk image file to find.')
1415
1416 def Execute(self, args):
1417 if not any((args.type_guid, args.unique_guid, args.label)):
1418 raise GPTError('You must specify at least one of -t, -u, or -l')
1419
1420 drives = [args.drive.name] if args.drive else (
1421 '/dev/%s' % name for name in subprocess.check_output(
1422 'lsblk -d -n -r -o name', shell=True).split())
1423
1424 match_pattern = None
1425 if args.match_file:
1426 with open(args.match_file) as f:
1427 match_pattern = f.read()
1428
1429 found = 0
1430 for drive in drives:
1431 try:
1432 gpt = GPT.LoadFromFile(drive)
1433 except GPTError:
1434 if args.drive:
1435 raise
1436 # When scanning all block devices on system, ignore failure.
1437
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001438 def Unmatch(a, b):
1439 return a is not None and a != b
1440
Hung-Te Linf641d302018-04-18 15:09:35 +08001441 for p in gpt.partitions:
Hung-Te Linbf8aa272018-04-19 03:02:29 +08001442 if (p.IsUnused() or
1443 Unmatch(args.label, p.label) or
1444 Unmatch(args.unique_guid, p.unique_guid) or
1445 Unmatch(args.type_guid, p.type_guid)):
Hung-Te Linf641d302018-04-18 15:09:35 +08001446 continue
1447 if match_pattern:
1448 with open(drive, 'rb') as f:
1449 f.seek(p.offset + args.offset)
1450 if f.read(len(match_pattern)) != match_pattern:
1451 continue
1452 # Found the partition, now print.
1453 found += 1
1454 if args.numeric:
1455 print(p.number)
1456 else:
1457 # This is actually more for block devices.
1458 print('%s%s%s' % (p.image, 'p' if p.image[-1].isdigit() else '',
1459 p.number))
1460
1461 if found < 1 or (args.single_match and found > 1):
1462 return 1
1463 return 0
1464
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001465
1466def main():
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001467 commands = GPTCommands()
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001468 parser = argparse.ArgumentParser(description='GPT Utility.')
1469 parser.add_argument('--verbose', '-v', action='count', default=0,
1470 help='increase verbosity.')
1471 parser.add_argument('--debug', '-d', action='store_true',
1472 help='enable debug output.')
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001473 commands.DefineArgs(parser)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001474
1475 args = parser.parse_args()
1476 log_level = max(logging.WARNING - args.verbose * 10, logging.DEBUG)
1477 if args.debug:
1478 log_level = logging.DEBUG
1479 logging.basicConfig(format='%(module)s:%(funcName)s %(message)s',
1480 level=log_level)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001481 try:
Hung-Te Linf641d302018-04-18 15:09:35 +08001482 code = commands.Execute(args)
1483 if type(code) is int:
1484 sys.exit(code)
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001485 except Exception as e:
1486 if args.verbose or args.debug:
1487 logging.exception('Failure in command [%s]', args.command)
Hung-Te Lin5cb0c312018-04-17 14:56:43 +08001488 exit('ERROR: %s: %s' % (args.command, str(e) or 'Unknown error.'))
Hung-Te Linc772e1a2017-04-14 16:50:50 +08001489
1490
1491if __name__ == '__main__':
1492 main()