2 # -*- coding: utf-8 -*-
4 # Copyright 2012 GRNET S.A. All rights reserved.
6 # Redistribution and use in source and binary forms, with or
7 # without modification, are permitted provided that the following
10 # 1. Redistributions of source code must retain the above
11 # copyright notice, this list of conditions and the following
14 # 2. Redistributions in binary form must reproduce the above
15 # copyright notice, this list of conditions and the following
16 # disclaimer in the documentation and/or other materials
17 # provided with the distribution.
19 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
20 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
23 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
27 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 # POSSIBILITY OF SUCH DAMAGE.
32 # The views and conclusions contained in the software and
33 # documentation are those of the authors and should not be
34 # interpreted as representing official policies, either expressed
35 # or implied, of GRNET S.A.
37 """This module provides the code for handling GUID partition tables"""
48 """Represents a Master Boot Record."""
49 class Partition(object):
50 """Represents a partition entry in MBR"""
53 def __init__(self, raw_part):
54 """Create a Partition instance"""
62 ) = struct.unpack(self.format, raw_part)
65 """Pack the partition values into a binary string"""
66 return struct.pack(self.format,
76 """Returns the size of an MBR partition entry"""
77 return struct.calcsize(MBR.Partition.format)
80 start = self.unpack_chs(self.start)
81 end = self.unpack_chs(self.end)
82 return "%d %s %d %s %d %d" % (self.status, start, self.type, end,
83 self.first_sector, self.sector_count)
85 def unpack_chs(self, chs):
86 """Unpacks a CHS address string to a tuple."""
90 head = struct.unpack('<B', chs[0])[0]
91 sector = struct.unpack('<B', chs[1])[0] & 0x3f
92 cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
93 struct.unpack('<B', chs[2])[0]
95 return (cylinder, head, sector)
97 def pack_chs(self, cylinder, head, sector):
98 """Packs a CHS tuple to an address string."""
100 assert 1 <= sector <= 63
101 assert 0 <= cylinder <= 1023
102 assert 0 <= head <= 255
105 byte1 = (cylinder >> 2) & 0xC0 | sector
106 byte2 = cylinder & 0xff
108 return struct.pack('<BBB', byte0, byte1, byte2)
110 format = "<444s2x16s16s16s16s2s"
112 Offset Length Contents
113 0 440(max. 446) code area
114 440 2(optional) disk signature
122 def __init__(self, block):
123 """Create an MBR instance"""
130 self.signature) = struct.unpack(self.format, block)
134 self.part[i] = self.Partition(raw_part[i])
138 """Return the size of a Master Boot Record."""
139 return struct.calcsize(MBR.format)
142 """Pack an MBR to a binary string."""
143 return struct.pack(self.format,
154 ret += "Partition %d: %s\n" % (i, self.part[i])
155 ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
156 hex(ord(self.signature[1])))
160 class GPTPartitionTable(object):
161 """Represents a GUID Partition Table."""
162 class GPTHeader(object):
163 """Represents a GPT Header of a GUID Partition Table."""
164 format = "<8s4sII4xQQQQ16sQIII"
166 Offset Length Contents
169 12 4 bytes Header size in little endian
170 16 4 bytes CRC32 of header
171 20 4 bytes Reserved; must be zero
172 24 8 bytes Current LBA
173 32 8 bytes Backup LBA
174 40 8 bytes First usable LBA for partitions
175 48 8 bytes Last usable LBA
176 56 16 bytes Disk GUID
177 72 8 bytes Partition entries starting LBA
178 80 4 bytes Number of partition entries
179 84 4 bytes Size of a partition entry
180 88 4 bytes CRC32 of partition array
181 92 * Reserved; must be zeroes
185 def __init__(self, block):
186 """Create a GPTHeader instance"""
193 self.first_usable_lba,
194 self.last_usable_lba,
196 self.part_entry_start,
198 self.part_entry_size,
199 self.part_crc32) = struct.unpack(self.format, block)
202 """Packs a GPT Header to a binary string."""
203 return struct.pack(self.format,
210 self.first_usable_lba,
211 self.last_usable_lba,
213 self.part_entry_start,
215 self.part_entry_size,
220 """Return the size of a GPT Header."""
221 return struct.calcsize(GPTPartitionTable.GPTHeader.format)
224 """Print a GPTHeader"""
225 return "Signature: %s\n" % self.signature + \
226 "Revision: %r\n" % self.revision + \
227 "Header Size: %d\n" % self.hdr_size + \
228 "CRC32: %d\n" % self.header_crc32 + \
229 "Current LBA: %d\n" % self.current_lba + \
230 "Backup LBA: %d\n" % self.backup_lba + \
231 "First Usable LBA: %d\n" % self.first_usable_lba + \
232 "Last Usable LBA: %d\n" % self.last_usable_lba + \
233 "Disk GUID: %s\n" % uuid.UUID(bytes=self.uuid) + \
234 "Partition entries starting LBA: %d\n" % \
235 self.part_entry_start + \
236 "Number of Partition entries: %d\n" % self.part_count + \
237 "Size of a partition entry: %d\n" % self.part_entry_size + \
238 "CRC32 of partition array: %s\n" % self.part_crc32
240 def __init__(self, disk):
241 """Create a GPTPartitionTable instance"""
243 with open(disk, "rb") as d:
244 # MBR (Logical block address 0)
245 lba0 = d.read(BLOCKSIZE)
248 # Primary GPT Header (LBA 1)
249 raw_header = d.read(self.GPTHeader.size())
250 self.primary = self.GPTHeader(raw_header)
252 # Partition entries (LBA 2...34)
253 d.seek(self.primary.part_entry_start * BLOCKSIZE)
254 entries_size = self.primary.part_count * \
255 self.primary.part_entry_size
256 self.part_entries = d.read(entries_size)
258 # Secondary GPT Header (LBA -1)
259 d.seek(self.primary.backup_lba * BLOCKSIZE)
260 raw_header = d.read(self.GPTHeader.size())
261 self.secondary = self.GPTHeader(raw_header)
264 """Return the payload size of GPT partitioned device."""
265 return (self.primary.backup_lba + 1) * BLOCKSIZE
267 def shrink(self, size, old_size):
268 """Move the secondary GPT Header entries to the address specified by
272 # Most partition manipulation programs leave 2048 sector after the last
274 aligned = size + 2048 * BLOCKSIZE
276 # new_size is at least: size + Partition Entries + Secondary GPT Header
277 new_size = aligned if aligned <= old_size else \
278 size + len(self.part_entries) + BLOCKSIZE
280 assert new_size <= old_size, "The secodary GPT fits in the device"
282 if new_size == self.size():
285 lba_count = new_size // BLOCKSIZE
288 #TODO: Check if the partition tables is hybrid
289 self.mbr.part[0].sector_count = (new_size // BLOCKSIZE) - 1
292 self.primary.header_crc32 = 0
293 self.primary.backup_lba = lba_count - 1 # LBA-1
294 self.primary.last_usable_lba = lba_count - 34 # LBA-34
295 self.primary.header_crc32 = \
296 binascii.crc32(self.primary.pack()) & 0xffffffff
298 # Fix Secondary header
299 self.secondary.header_crc32 = 0
300 self.secondary.current_lba = self.primary.backup_lba
301 self.secondary.last_usable_lba = lba_count - 34 # LBA-34
302 self.secondary.part_entry_start = lba_count - 33 # LBA-33
303 self.secondary.header_crc32 = \
304 binascii.crc32(self.secondary.pack()) & 0xffffffff
306 # Copy the new partition table back to the device
307 with open(self.disk, "wb") as d:
308 d.write(self.mbr.pack())
309 d.write(self.primary.pack())
310 d.write('\x00' * (BLOCKSIZE - self.primary.size()))
311 d.write(self.part_entries)
312 d.seek(self.secondary.part_entry_start * BLOCKSIZE)
313 d.write(self.part_entries)
314 d.seek(self.primary.backup_lba * BLOCKSIZE)
315 d.write(self.secondary.pack())
316 d.write('\x00' * (BLOCKSIZE - self.secondary.size()))
320 if __name__ == '__main__':
321 ptable = GPTPartitionTable(sys.argv[1])
323 print "MBR:\n%s" % ptable.mbr
324 print "Primary partition table:\n%s" % ptable.primary
325 print "Secondary partition table:\n%s" % ptable.secondary
327 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :