3 # Copyright (C) 2012 GRNET S.A.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
30 """Represents a Master Boot Record."""
31 class Partition(object):
34 def __init__(self, raw_part):
42 ) = struct.unpack(self.format, raw_part)
45 return struct.pack(self.format,
56 """Returns the size of an MBR partition entry"""
57 return struct.calcsize(MBR.Partition.format)
59 def unpack_chs(self, chs):
60 """Unpacks a CHS address string to a tuple."""
64 head = struct.unpack('<B', chs[0])[0]
65 sector = struct.unpack('<B', chs[1])[0] & 0x3f
66 cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
67 struct.unpack('<B', chs[2])[0]
69 return (cylinder, head, sector)
71 def pack_chs(self, cylinder, head, sector):
72 """Packs a CHS tuple to an address string."""
74 assert 1 <= sector <= 63
75 assert 0 <= cylinder <= 1023
76 assert 0 <= head <= 255
79 byte1 = (cylinder >> 2) & 0xC0 | sector
80 byte2 = cylinder & 0xff
82 return struct.pack('<BBB', byte0, byte1, byte2)
84 format = "<444s2x16s16s16s16s2s"
86 Offset Length Contents
87 0 440(max. 446) code area
88 440 2(optional) disk signature
96 def __init__(self, block):
103 self.signature = struct.unpack(self.format, block)
107 self.part[i] = self.Partition(raw_part[i])
111 """Returns the size of a Master Boot Record."""
112 return struct.calcsize(MBR.format)
115 """Packs an MBR to a binary string."""
116 return struct.pack(self.format,
126 class GPTPartitionTable(object):
127 """Represents a GUID Partition Table."""
128 class GPTHeader(object):
129 """Represents a GPT Header of a GUID Partition Table."""
130 format = "<8s4sII4xQQQQ16sQIII"
132 Offset Length Contents
135 12 4 bytes Header size in little endian
136 16 4 bytes CRC32 of header
137 20 4 bytes Reserved; must be zero
138 24 8 bytes Current LBA
139 32 8 bytes Backup LBA
140 40 8 bytes First usable LBA for partitions
141 48 8 bytes Last usable LBA
142 56 16 bytes Disk GUID
143 72 8 bytes Partition entries starting LBA
144 80 4 bytes Number of partition entries
145 84 4 bytes Size of a partition entry
146 88 4 bytes CRC32 of partition array
147 92 * Reserved; must be zeroes
151 def __init__(self, block):
158 self.first_usable_lba, \
159 self.last_usable_lba, \
161 self.part_entry_start, \
163 self.part_entry_size, \
164 self.part_crc32 = struct.unpack(self.format, block)
167 """Packs a GPT Header to a binary string."""
168 return struct.pack(self.format,
175 self.first_usable_lba, \
176 self.last_usable_lba, \
178 self.part_entry_start, \
180 self.part_entry_size, \
186 """Returns the size of a GPT Header."""
187 return struct.calcsize(GPTPartitionTable.GPTHeader.format)
189 def __init__(self, disk):
191 with open(disk, "rb") as d:
192 # MBR (Logical block address 0)
193 lba0 = d.read(BLOCKSIZE)
196 # Primary GPT Header (LBA 1)
197 raw_header = d.read(self.GPTHeader.size())
198 self.primary = self.GPTHeader(raw_header)
200 # Partition entries (LBA 2...34)
201 d.seek(self.primary.part_entry_start * BLOCKSIZE)
202 entries_size = self.primary.part_count * \
203 self.primary.part_entry_size
204 self.part_entries = d.read(entries_size)
206 # Secondary GPT Header (LBA -1)
207 d.seek(self.primary.backup_lba * BLOCKSIZE)
208 raw_header = d.read(self.GPTHeader.size())
209 self.secondary = self.GPTHeader(raw_header)
212 """Returns the payload size of GPT partitioned device."""
213 return (self.primary.backup_lba + 1) * BLOCKSIZE
215 def fix(self, lba_count):
216 """Move the secondary GPT Header entries to the LBA specified by
220 assert lba_count * BLOCKSIZE > self.size()
223 #TODO: Check if the partition tables is hybrid
224 self.mbr.part[0].sector_count = lba_count - 1
227 self.primary.header_crc32 = 0
228 self.primary.backup_lba = lba_count - 1 # LBA-1
229 self.primary.last_usable_lba = lba_count - 34 # LBA-34
230 self.primary.header_crc32 = \
231 binascii.crc32(self.primary.pack()) & 0xffffffff
233 # Fix Secondary header
234 self.secondary.header_crc32 = 0
235 self.secondary.current_lba = self.primary.backup_lba
236 self.secondary.last_usable_lba = lba_count - 34 # LBA-34
237 self.secondary.part_entry_start = lba_count - 33 # LBA-33
238 self.secondary.header_crc32 = \
239 binascii.crc32(self.secondary.pack()) & 0xffffffff
241 # Copy the new partition table back to the device
242 with open(self.disk, "wb") as d:
243 d.write(self.mbr.pack())
244 d.write(self.primary.pack())
245 d.write('\x00' * (BLOCKSIZE - self.primary.size()))
246 d.seek(self.secondary.part_entry_start * BLOCKSIZE)
247 d.write(self.part_entries)
248 d.seek(self.primary.backup_lba * BLOCKSIZE)
249 d.write(self.secondary.pack())
250 d.write('\x00' * (BLOCKSIZE - self.secondary.size()))
253 if __name__ == '__main__':
254 usage = "Usage: %s <disk> <sectors>\n" % (sys.argv[0])
256 if len(sys.argv) != 3:
257 sys.stderr.write(usage)
261 mode = os.stat(disk).st_mode
262 if not stat.S_ISBLK(mode):
263 sys.stderr.write("Parameter disk must be a block device\n")
264 sys.stderr.write(usage)
268 size = int(sys.argv[2])
270 sys.stderr.write("Parameter new_size must be a number\n")
271 sys.stderr.write(usage)
274 ptable = GPTPartitionTable(disk)
275 if size * BLOCKSIZE == ptable.size():
276 sys.stderr.write("Nothing to do...\n")
277 elif size * BLOCKSIZE > ptable.size():
279 sys.stderr.write("GPT table was fixed\n")
281 sys.stderr.write("Disk is langer than size")
286 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :