Add a simple GPT partitions parser
authorArtem Bityutskiy <artem.bityutskiy@intel.com>
Tue, 29 Jan 2013 12:19:11 +0000 (14:19 +0200)
committerGui Chen <gui.chen@intel.com>
Tue, 29 Jan 2013 05:48:13 +0000 (13:48 +0800)
The 'PARTUUID' blkid feature exists only in relatively new versions of blkid,
which causes troubles in some distributions. Therefore, lets implement our own
simple GPT parser instead.

Change-Id: Ie07efc5040770c002ef4be60431c8756fa690d63
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@intel.com>
mic/utils/gpt_parser.py [new file with mode: 0644]

diff --git a/mic/utils/gpt_parser.py b/mic/utils/gpt_parser.py
new file mode 100644 (file)
index 0000000..cbf1097
--- /dev/null
@@ -0,0 +1,132 @@
+#!/usr/bin/python -tt
+#
+# Copyright (c) 2013 Intel, Inc.
+#
+# 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; version 2 of the License
+#
+# 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, write to the Free Software Foundation, Inc., 59
+# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+""" This module implements a simple GPT partitions parser which can read the
+GPT header and the GPT partition table. """
+
+import struct
+import uuid
+from mic.utils.errors import MountError
+
+GPT_HEADER_FORMAT = "<8sIIIIQQQQ16sQIII420x"
+GPT_ENTRY_FORMAT = "<16s16sQQQ72s"
+
+def _stringify_uuid(binary_uuid):
+    """ A small helper function to transform a binary UUID into a string
+    format. """
+
+    uuid_str = str(uuid.UUID(bytes_le = binary_uuid))
+
+    return uuid_str.upper()
+
+class GptParser:
+    """ GPT partition table parser. The current implementation is simplified
+    and it assumes that the partition table is correct, so it does not check
+    the CRC-32 checksums and does not handle the backup GPT partition table.
+    But this implementation can be extended in the future, if needed. """
+
+    def __init__(self, disk_path, sector_size = 512):
+        """ The class construcor which accepts the following parameters:
+            * disk_path - full path to the disk image or device node
+            * sector_size - size of a disk sector in bytes """
+
+        self.sector_size = sector_size
+        self.disk_path = disk_path
+
+        try:
+            self.disk_obj = open(disk_path, 'rb')
+        except IOError as err:
+            raise MountError("Cannot open file '%s' for reading GPT " \
+                             "partitions: %s" % (disk_path, err))
+
+    def __del__(self):
+        """ The class destructor. """
+
+        self.disk_obj.close()
+
+    def read_header(self):
+        """ Read and verify the GPT header and return a tuple containing the
+        following elements:
+
+        (Signature, Revision, Header size in bytes, header CRC32, Current LBA,
+        Backup LBA, First usable LBA for partitions, Last usable LBA, Disk GUID,
+        Starting LBA of array of partition entries, Number of partition entries,
+        Size of a single partition entry, CRC32 of partition array)
+
+        This tuple corresponds to the GPT header format. Please, see the UEFI
+        standard for the description of these fields. """
+
+        # The header sits at LBA 1 - read it
+        self.disk_obj.seek(self.sector_size)
+        try:
+            header = self.disk_obj.read(struct.calcsize(GPT_HEADER_FORMAT))
+        except IOError as err:
+            raise MountError("cannot read from file '%s': %s" % \
+                             (self.disk_path, err))
+
+        header = struct.unpack(GPT_HEADER_FORMAT, header)
+
+        # Perform a simple validation
+        if header[0] != 'EFI PART':
+            raise MountError("GPT paritition table on disk '%s' not found" % \
+                             self.disk_path)
+
+        return (header[0], # 0. Signature
+                header[1], # 1. Revision
+                header[2], # 2. Header size in bytes
+                header[3], # 3. Header CRC32
+                header[5], # 4. Current LBA
+                header[6], # 5. Backup LBA
+                header[7], # 6. First usable LBA for partitions
+                header[8], # 7. Last usable LBA
+                _stringify_uuid(header[9]), # 8. Disk GUID
+                header[10], # 9. Starting LBA of array of partition entries
+                header[11], # 10. Number of partition entries
+                header[12], # 11. Size of a single partition entry
+                header[13]) # 12. CRC32 of partition array
+
+    def get_partitions(self):
+        """ This is a generator which parses teh GPT partition table and
+        generates the following tupes for each partition:
+
+        (Partition type GUID, Partition GUID, First LBA, Last LBA,
+        Attribute flags, Partition name)
+
+        This tuple corresponds to the GPT partition record format. Please, see the
+        UEFI standard for the description of these fields. """
+
+        gpt_header = self.read_header()
+        entries_start = gpt_header[9] * self.sector_size
+        entries_count = gpt_header[10]
+
+        self.disk_obj.seek(entries_start)
+
+        for _ in xrange(0, entries_count):
+            entry = self.disk_obj.read(struct.calcsize(GPT_ENTRY_FORMAT))
+            entry = struct.unpack(GPT_ENTRY_FORMAT, entry)
+
+            if entry[2] == 0 or entry[3] == 0:
+                continue
+
+            part_name = str(entry[5].decode('UTF-16').split('\0', 1)[0])
+
+            yield (_stringify_uuid(entry[0]), # 0. Partition type GUID
+                   _stringify_uuid(entry[1]), # 1. Partition GUID
+                   entry[2],                  # 2. First LBA
+                   entry[3],                  # 3. Last LBA
+                   entry[4],                  # 4. Attribute flags
+                   part_name)                 # 5. Partition name