Bump version to 0.2.8
[snf-image-creator] / image_creator / gpt.py
1 # Copyright 2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import struct
35 import sys
36 import uuid
37 import binascii
38
39 BLOCKSIZE = 512
40
41
42 class MBR(object):
43     """Represents a Master Boot Record."""
44     class Partition(object):
45         """Represents a partition entry in MBR"""
46         format = "<B3sB3sLL"
47
48         def __init__(self, raw_part):
49             """Create a Partition instance"""
50             (
51                 self.status,
52                 self.start,
53                 self.type,
54                 self.end,
55                 self.first_sector,
56                 self.sector_count
57             ) = struct.unpack(self.format, raw_part)
58
59         def pack(self):
60             """Pack the partition values into a binary string"""
61             return struct.pack(self.format,
62                                self.status,
63                                self.start,
64                                self.type,
65                                self.end,
66                                self.first_sector,
67                                self.sector_count)
68
69         @staticmethod
70         def size():
71             """Returns the size of an MBR partition entry"""
72             return struct.calcsize(MBR.Partition.format)
73
74         def __str__(self):
75             start = self.unpack_chs(self.start)
76             end = self.unpack_chs(self.end)
77             return "%d %s %d %s %d %d" % (self.status, start, self.type, end,
78                                           self.first_sector, self.sector_count)
79
80         def unpack_chs(self, chs):
81             """Unpacks a CHS address string to a tuple."""
82
83             assert len(chs) == 3
84
85             head = struct.unpack('<B', chs[0])[0]
86             sector = struct.unpack('<B', chs[1])[0] & 0x3f
87             cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
88                 struct.unpack('<B', chs[2])[0]
89
90             return (cylinder, head, sector)
91
92         def pack_chs(self, cylinder, head, sector):
93             """Packs a CHS tuple to an address string."""
94
95             assert 1 <= sector <= 63
96             assert 0 <= cylinder <= 1023
97             assert 0 <= head <= 255
98
99             byte0 = head
100             byte1 = (cylinder >> 2) & 0xC0 | sector
101             byte2 = cylinder & 0xff
102
103             return struct.pack('<BBB', byte0, byte1, byte2)
104
105     format = "<444s2x16s16s16s16s2s"
106     """
107     Offset  Length          Contents
108     0       440(max. 446)   code area
109     440     2(optional)     disk signature
110     444     2               Usually nulls
111     446     16              Partition 0
112     462     16              Partition 1
113     478     16              Partition 2
114     494     16              Partition 3
115     510     2               MBR signature
116     """
117     def __init__(self, block):
118         """Create an MBR instance"""
119         raw_part = {}
120         (self.code_area,
121          raw_part[0],
122          raw_part[1],
123          raw_part[2],
124          raw_part[3],
125          self.signature) = struct.unpack(self.format, block)
126
127         self.part = {}
128         for i in range(4):
129             self.part[i] = self.Partition(raw_part[i])
130
131     @staticmethod
132     def size():
133         """Return the size of a Master Boot Record."""
134         return struct.calcsize(MBR.format)
135
136     def pack(self):
137         """Pack an MBR to a binary string."""
138         return struct.pack(self.format,
139                            self.code_area,
140                            self.part[0].pack(),
141                            self.part[1].pack(),
142                            self.part[2].pack(),
143                            self.part[3].pack(),
144                            self.signature)
145
146     def __str__(self):
147         ret = ""
148         for i in range(4):
149             ret += "Partition %d: %s\n" % (i, self.part[i])
150         ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
151                                        hex(ord(self.signature[1])))
152         return ret
153
154
155 class GPTPartitionTable(object):
156     """Represents a GUID Partition Table."""
157     class GPTHeader(object):
158         """Represents a GPT Header of a GUID Partition Table."""
159         format = "<8s4sII4xQQQQ16sQIII"
160         """
161         Offset  Length          Contents
162         0       8 bytes         Signature
163         8       4 bytes         Revision
164         12      4 bytes         Header size in little endian
165         16      4 bytes         CRC32 of header
166         20      4 bytes         Reserved; must be zero
167         24      8 bytes         Current LBA
168         32      8 bytes         Backup LBA
169         40      8 bytes         First usable LBA for partitions
170         48      8 bytes         Last usable LBA
171         56      16 bytes        Disk GUID
172         72      8 bytes         Partition entries starting LBA
173         80      4 bytes         Number of partition entries
174         84      4 bytes         Size of a partition entry
175         88      4 bytes         CRC32 of partition array
176         92      *               Reserved; must be zeroes
177         LBA    size            Total
178         """
179
180         def __init__(self, block):
181             """Create a GPTHeader instance"""
182             (self.signature,
183              self.revision,
184              self.hdr_size,
185              self.header_crc32,
186              self.current_lba,
187              self.backup_lba,
188              self.first_usable_lba,
189              self.last_usable_lba,
190              self.uuid,
191              self.part_entry_start,
192              self.part_count,
193              self.part_entry_size,
194              self.part_crc32) = struct.unpack(self.format, block)
195
196         def pack(self):
197             """Packs a GPT Header to a binary string."""
198             return struct.pack(self.format,
199                                self.signature,
200                                self.revision,
201                                self.hdr_size,
202                                self.header_crc32,
203                                self.current_lba,
204                                self.backup_lba,
205                                self.first_usable_lba,
206                                self.last_usable_lba,
207                                self.uuid,
208                                self.part_entry_start,
209                                self.part_count,
210                                self.part_entry_size,
211                                self.part_crc32)
212
213         @staticmethod
214         def size():
215             """Return the size of a GPT Header."""
216             return struct.calcsize(GPTPartitionTable.GPTHeader.format)
217
218         def __str__(self):
219             """Print a GPTHeader""" 
220             return "Signature: %s\n" % self.signature + \
221                    "Revision: %r\n" % self.revision + \
222                    "Header Size: %d\n" % self.hdr_size + \
223                    "CRC32: %d\n" % self.header_crc32 + \
224                    "Current LBA: %d\n" % self.current_lba + \
225                    "Backup LBA: %d\n" % self.backup_lba + \
226                    "First Usable LBA: %d\n" % self.first_usable_lba + \
227                    "Last Usable LBA: %d\n" % self.last_usable_lba + \
228                    "Disk GUID: %s\n" % uuid.UUID(bytes=self.uuid) + \
229                    "Partition entries starting LBA: %d\n" % \
230                    self.part_entry_start + \
231                    "Number of Partition entries: %d\n" % self.part_count + \
232                    "Size of a partition entry: %d\n" % self.part_entry_size + \
233                    "CRC32 of partition array: %s\n" % self.part_crc32
234
235     def __init__(self, disk):
236         """Create a GPTPartitionTable instance"""
237         self.disk = disk
238         with open(disk, "rb") as d:
239             # MBR (Logical block address 0)
240             lba0 = d.read(BLOCKSIZE)
241             self.mbr = MBR(lba0)
242
243             # Primary GPT Header (LBA 1)
244             raw_header = d.read(self.GPTHeader.size())
245             self.primary = self.GPTHeader(raw_header)
246
247             # Partition entries (LBA 2...34)
248             d.seek(self.primary.part_entry_start * BLOCKSIZE)
249             entries_size = self.primary.part_count * \
250                 self.primary.part_entry_size
251             self.part_entries = d.read(entries_size)
252
253             # Secondary GPT Header (LBA -1)
254             d.seek(self.primary.backup_lba * BLOCKSIZE)
255             raw_header = d.read(self.GPTHeader.size())
256             self.secondary = self.GPTHeader(raw_header)
257
258     def size(self):
259         """Return the payload size of GPT partitioned device."""
260         return (self.primary.backup_lba + 1) * BLOCKSIZE
261
262     def shrink(self, size, old_size):
263         """Move the secondary GPT Header entries to the address specified by
264         size parameter.
265         """
266
267         # Most partition manipulation programs leave 2048 sector after the last
268         # partition
269         aligned = size + 2048 * BLOCKSIZE
270
271         # new_size is at least: size + Partition Entries + Secondary GPT Header
272         new_size = aligned if aligned <= old_size else \
273             size + len(self.part_entries) + BLOCKSIZE
274
275         assert new_size <= old_size, "The secodary GPT fits in the device"
276
277         if new_size == self.size():
278             return new_size
279
280         lba_count = new_size // BLOCKSIZE
281
282         # Correct MBR
283         #TODO: Check if the partition tables is hybrid
284         self.mbr.part[0].sector_count = (new_size // BLOCKSIZE) - 1
285
286         # Fix Primary header
287         self.primary.header_crc32 = 0
288         self.primary.backup_lba = lba_count - 1  # LBA-1
289         self.primary.last_usable_lba = lba_count - 34  # LBA-34
290         self.primary.header_crc32 = \
291             binascii.crc32(self.primary.pack()) & 0xffffffff
292
293         # Fix Secondary header
294         self.secondary.header_crc32 = 0
295         self.secondary.current_lba = self.primary.backup_lba
296         self.secondary.last_usable_lba = lba_count - 34  # LBA-34
297         self.secondary.part_entry_start = lba_count - 33  # LBA-33
298         self.secondary.header_crc32 = \
299             binascii.crc32(self.secondary.pack()) & 0xffffffff
300
301         # Copy the new partition table back to the device
302         with open(self.disk, "wb") as d:
303             d.write(self.mbr.pack())
304             d.write(self.primary.pack())
305             d.write('\x00' * (BLOCKSIZE - self.primary.size()))
306             d.write(self.part_entries)
307             d.seek(self.secondary.part_entry_start * BLOCKSIZE)
308             d.write(self.part_entries)
309             d.seek(self.primary.backup_lba * BLOCKSIZE)
310             d.write(self.secondary.pack())
311             d.write('\x00' * (BLOCKSIZE - self.secondary.size()))
312
313         return new_size
314
315 if __name__ == '__main__':
316     ptable = GPTPartitionTable(sys.argv[1])
317
318     print "MBR:\n%s" % ptable.mbr
319     print "Primary partition table:\n%s" % ptable.primary
320     print "Secondary partition table:\n%s" % ptable.secondary
321
322 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :