phoenix_firmware_dumper/utils/kdztools/libexec/gpt.py

341 lines
8.7 KiB
Python

#!/usr/bin/env python
"""
Copyright (C) 2016 Elliott Mitchell <ehem+android@m5p.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from __future__ import absolute_import
from __future__ import print_function
import os
import sys
import io
from collections import OrderedDict
from struct import Struct
from uuid import UUID
from binascii import crc32
verbose = lambda msg: None
class NoGPT(Exception):
def __init__(self, errmsg):
self.errmsg = errmsg
def __str__(self):
return self.errmsg
class GPTSlice(object):
"""
Class for handling invidual slices^Wpartitions of a GUID table
"""
_gpt_slice_length = 128 # this is *default*!
# Format string dict
# itemName is the new dict key for the data to be stored under
# formatString is the Python formatstring for struct.unpack()
# Example:
# ('itemName', ('formatString'))
_gpt_slice_fmt = OrderedDict([
('type', ('16s')),
('uuid', ('16s')),
('startLBA', ('Q')),
('endLBA', ('Q')),
('flags', ('Q')),
('name', ('72s'))
])
# Generate the formatstring for struct.unpack()
_gpt_struct = Struct("<" + "".join([x for x in _gpt_slice_fmt.values()]))
def display(self, idx):
"""
Display the data for this slice of a GPT
"""
if self.type == UUID(int=0):
verbose("Name: <empty entry>")
return None
verbose("Name({:d}): \"{:s}\" start={:d} end={:d} count={:d}".format(idx, self.name, self.startLBA, self.endLBA, self.endLBA-self.startLBA+1))
verbose("typ={:s} id={:s}".format(str(self.type), str(self.uuid)))
def __init__(self, buf):
"""
Initialize the GPTSlice class
"""
data = dict(zip(
self._gpt_slice_fmt.keys(),
self._gpt_struct.unpack(buf)
))
self.type = UUID(bytes=data['type'])
self.uuid = UUID(bytes=data['uuid'])
self.startLBA = data['startLBA']
self.endLBA = data['endLBA']
self.flags = data['flags']
self.name = data['name'].decode("utf16").rstrip('\x00')
class GPT(object):
"""
Class for handling of GUID Partition Tables
(https://en.wikipedia.org/wiki/GUID_Partition_Table)
"""
_gpt_header = b"EFI PART"
_gpt_size = 0x5C # default, can be overridden by headerSize
# Format string dict
# itemName is the new dict key for the data to be stored under
# formatString is the Python formatstring for struct.unpack()
# Example:
# ('itemName', ('formatString'))
_gpt_head_fmt = OrderedDict([
('header', ('8s')), # magic number
('revision', ('I')), # actually 2 shorts, Struct...
('headerSize', ('I')),
('crc32', ('I')),
('reserved', ('I')),
('myLBA', ('Q')),
('altLBA', ('Q')),
('dataStartLBA',('Q')),
('dataEndLBA', ('Q')),
('uuid', ('16s')),
('entryStart', ('Q')),
('entryCount', ('I')),
('entrySize', ('I')),
('entryCrc32', ('I')),
])
# Generate the formatstring for struct.unpack()
_gpt_struct = Struct("<" + "".join([x for x in _gpt_head_fmt.values()]))
def display(self):
"""
Display the data in the particular GPT
"""
verbose("")
verbose("block size is {:d} bytes (shift {:d})".format(1<<self.shiftLBA, self.shiftLBA))
verbose("device={:s}".format(str(self.uuid)))
verbose("myLBA={:d} altLBA={:d} dataStart={:d} dataEnd={:d}".format(self.myLBA, self.altLBA, self.dataStartLBA, self.dataEndLBA))
verbose("")
if self.myLBA == 1:
if self.entryStart != 2:
verbose("Note: {:d} unused blocks between GPT header and entry table".format(self.entryStart-2))
endEntry = self.entryStart + ((self.entrySize * self.entryCount + (1<<self.shiftLBA)-1)>>self.shiftLBA)
if endEntry < self.dataStartLBA:
verbose("Note: {:d} unused slice entry blocks before first usable block".format(self.dataStartLBA - endEntry))
else:
if self.entryStart != self.dataEndLBA+1:
verbose("Note: {:d} unused slice entry blocks after last usable block".format(self.entryStart-self.dataEndLBA-1))
endEntry = self.entryStart + ((self.entrySize * self.entryCount + (1<<self.shiftLBA)-1)>>self.shiftLBA)
if endEntry < self.myLBA-1:
verbose("Note: {:d} unused blocks between GPT header and entry table".format(self.myLBA-endEntry+1))
current = self.dataStartLBA
idx = 1
for slice in self.slices:
if slice.type != UUID(int=0):
if slice.startLBA != current:
verbose("Note: non-contiguous ({:d} unused)".format(slice.startLBA-current))
current = slice.endLBA + 1
slice.display(idx)
idx += 1
current-=1
if self.dataEndLBA != current:
verbose("Note: empty LBAs at end ({:d} unused)".format(self.dataEndLBA-current))
def tryParseHeader(self, buf):
"""
Try to parse a buffer as a GPT header, return None on failure
"""
if len(buf) < self._gpt_size:
raise NoGPT("Failed to locate GPT")
data = dict(zip(
self._gpt_head_fmt.keys(),
self._gpt_struct.unpack(buf[0:self._gpt_size])
))
if data['header'] != self._gpt_header:
return None
tmp = data['crc32']
data['crc32'] = 0
crc = crc32(self._gpt_struct.pack(*[data[k] for k in self._gpt_head_fmt.keys()]))
data['crc32'] = tmp
# just in case future ones are larger
crc = crc32(buf[self._gpt_size:data['headerSize']], crc)
crc &= 0xFFFFFFFF
if crc != data['crc32']:
verbose("Warning: Found GPT candidate with bad CRC")
return None
return data
def __init__(self, buf, type=None, lbaMinShift=9, lbaMaxShift=16):
"""
Initialize the GPT class
"""
# sanity checking
if self._gpt_struct.size != self._gpt_size:
raise NoGPT("GPT format string wrong!")
# we assume we're searching, start with the bottom end
shiftLBA = lbaMinShift
lbaSize = 1<<shiftLBA
shiftLBA-=1
# search for the GPT, since block size is unknown
while shiftLBA < lbaMaxShift:
# non power of 2 sizes are illegal
shiftLBA+=1
lbaSize = 1<<shiftLBA
# try for a primary GPT
hbuf = buf[lbaSize:lbaSize<<1]
data = self.tryParseHeader(hbuf)
if data:
verbose("Found Primary GPT")
break
# try for a backup GPT
hbuf = buf[-lbaSize:]
data = self.tryParseHeader(hbuf)
if data:
verbose("Found Backup GPT")
break
else:
raise NoGPT("Failed to locate GPT")
self.shiftLBA = shiftLBA
if data['reserved'] != 0:
verbose("Warning: Reserved area non-zero")
self.revision = data['revision']
self.headerSize = data['headerSize']
self.reserved = data['reserved']
self.myLBA = data['myLBA']
self.altLBA = data['altLBA']
self.dataStartLBA = data['dataStartLBA']
self.dataEndLBA = data['dataEndLBA']
self.uuid = UUID(bytes=data['uuid'])
self.entryStart = data['entryStart']
self.entryCount = data['entryCount']
self.entrySize = data['entrySize']
self.entryCrc32 = data['entryCrc32']
if self.revision>>16 != 1:
raise NoGPT("Error: GPT major version isn't 1")
elif self.revision&0xFFFF != 0:
verbose("Warning: Newer GPT revision")
# these tests were against our version and may well fail
elif self.reserved != 0:
verbose("Warning: Reserved area non-zero")
# this is an error according to the specs
if (self.myLBA != 1) and (self.altLBA != 1):
raise NoGPT("Error: No GPT at LBA 1 ?!")
# this is an error according to the specs
if self.entrySize & (self.entrySize-1):
raise NoGPT("Error: entry size is not a power of 2")
if self.myLBA == 1:
sliceAddr = self.entryStart<<self.shiftLBA
else:
sliceAddr = (self.entryStart-self.myLBA-1)<<self.shiftLBA
self.slices = []
crc = 0
for i in range(self.entryCount):
sbuf = buf[sliceAddr:sliceAddr+self.entrySize]
crc = crc32(sbuf, crc)
slice = GPTSlice(sbuf)
self.slices.append(slice)
sliceAddr+=self.entrySize
crc &= 0xFFFFFFFF
if crc != self.entryCrc32:
raise NoGPT("Error: bad slice entry CRC")
last = 0
for slice in self.slices:
if slice.type == UUID(int=0):
continue
if slice.startLBA <= last:
verbose("Note: slices are out of order in GPT")
self.ordered = False
break
last = slice.endLBA
else:
self.ordered = True
if __name__ == "__main__":
verbose = lambda msg: print(msg)
progname = sys.argv[0]
del sys.argv[0]
for arg in sys.argv:
if arg == "-":
file = sys.stdin
else:
file = io.FileIO(arg, "rb")
# header is always in second LBA, slice entries in third
# if you run out of slice entries with a 64KB LBA, oy vey!
buf = file.read(1<<17+1<<16)
try:
gpt = GPT(buf)
gpt.display()
except NoGPT:
print(NoGPT, file=sys.stderr)