Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / fix_gpt.py @ 21be5a41

History | View | Annotate | Download (9.1 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright (C) 2012 GRNET S.A.
4
#
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.
9
#
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.
14
#
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
18
# 02110-1301, USA.
19

    
20
import struct
21
import sys
22
import binascii
23
import stat
24
import os
25

    
26
BLOCKSIZE = 512
27

    
28

    
29
class MBR(object):
30
    """Represents a Master Boot Record."""
31
    class Partition(object):
32
        format = "<B3sB3sLL"
33

    
34
        def __init__(self, raw_part):
35
            (
36
                self.status,
37
                self.start,
38
                self.type,
39
                self.end,
40
                self.first_sector,
41
                self.sector_count
42
            ) = struct.unpack(self.format, raw_part)
43

    
44
        def pack(self):
45
            return struct.pack(self.format,
46
                self.status,
47
                self.start,
48
                self.type,
49
                self.end,
50
                self.first_sector,
51
                self.sector_count
52
            )
53

    
54
        @staticmethod
55
        def size():
56
            """Returns the size of an MBR partition entry"""
57
            return struct.calcsize(MBR.Partition.format)
58

    
59
        def unpack_chs(self, chs):
60
            """Unpacks a CHS address string to a tuple."""
61

    
62
            assert len(chs) == 3
63

    
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]
68

    
69
            return (cylinder, head, sector)
70

    
71
        def pack_chs(self, cylinder, head, sector):
72
            """Packs a CHS tuple to an address string."""
73

    
74
            assert 1 <= sector <= 63
75
            assert 0 <= cylinder <= 1023
76
            assert 0 <= head <= 255
77

    
78
            byte0 = head
79
            byte1 = (cylinder >> 2) & 0xC0 | sector
80
            byte2 = cylinder & 0xff
81

    
82
            return struct.pack('<BBB', byte0, byte1, byte2)
83

    
84
    format = "<444s2x16s16s16s16s2s"
85
    """
86
    Offset  Length          Contents
87
    0       440(max. 446)   code area
88
    440     2(optional)     disk signature
89
    444     2               Usually nulls
90
    446     16              Partition 0
91
    462     16              Partition 1
92
    478     16              Partition 2
93
    494     16              Partition 3
94
    510     2               MBR signature
95
    """
96
    def __init__(self, block):
97
        raw_part = {}
98
        self.code_area, \
99
        raw_part[0], \
100
        raw_part[1], \
101
        raw_part[2], \
102
        raw_part[3], \
103
        self.signature = struct.unpack(self.format, block)
104

    
105
        self.part = {}
106
        for i in range(4):
107
            self.part[i] = self.Partition(raw_part[i])
108

    
109
    @staticmethod
110
    def size():
111
        """Returns the size of a Master Boot Record."""
112
        return struct.calcsize(MBR.format)
113

    
114
    def pack(self):
115
        """Packs an MBR to a binary string."""
116
        return struct.pack(self.format,
117
            self.code_area,
118
            self.part[0].pack(),
119
            self.part[1].pack(),
120
            self.part[2].pack(),
121
            self.part[3].pack(),
122
            self.signature
123
        )
124

    
125

    
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"
131
        """
132
        Offset        Length                 Contents
133
        0       8 bytes         Signature
134
        8       4 bytes         Revision
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
148
        LBA    size            Total
149
        """
150

    
151
        def __init__(self, block):
152
            self.signature, \
153
            self.revision, \
154
            self.hdr_size, \
155
            self.header_crc32, \
156
            self.current_lba, \
157
            self.backup_lba, \
158
            self.first_usable_lba, \
159
            self.last_usable_lba, \
160
            self.uuid, \
161
            self.part_entry_start, \
162
            self.part_count, \
163
            self.part_entry_size, \
164
            self.part_crc32 = struct.unpack(self.format, block)
165

    
166
        def pack(self):
167
            """Packs a GPT Header to a binary string."""
168
            return struct.pack(self.format,
169
                self.signature, \
170
                self.revision, \
171
                self.hdr_size, \
172
                self.header_crc32, \
173
                self.current_lba, \
174
                self.backup_lba, \
175
                self.first_usable_lba, \
176
                self.last_usable_lba, \
177
                self.uuid, \
178
                self.part_entry_start, \
179
                self.part_count, \
180
                self.part_entry_size, \
181
                self.part_crc32
182
            )
183

    
184
        @staticmethod
185
        def size():
186
            """Returns the size of a GPT Header."""
187
            return struct.calcsize(GPTPartitionTable.GPTHeader.format)
188

    
189
    def __init__(self, disk):
190
        self.disk = disk
191
        with open(disk, "rb") as d:
192
            # MBR (Logical block address 0)
193
            lba0 = d.read(BLOCKSIZE)
194
            self.mbr = MBR(lba0)
195

    
196
            # Primary GPT Header (LBA 1)
197
            raw_header = d.read(self.GPTHeader.size())
198
            self.primary = self.GPTHeader(raw_header)
199

    
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)
205

    
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)
210

    
211
    def size(self):
212
        """Returns the payload size of GPT partitioned device."""
213
        return (self.primary.backup_lba + 1) * BLOCKSIZE
214

    
215
    def fix(self, lba_count):
216
        """Move the secondary GPT Header entries to the LBA specified by
217
        lba_count parameter.
218
        """
219

    
220
        assert lba_count * BLOCKSIZE > self.size()
221

    
222
        # Correct MBR
223
        #TODO: Check if the partition tables is hybrid
224
        self.mbr.part[0].sector_count = lba_count - 1
225

    
226
        # Fix Primary header
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
232

    
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
240

    
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()))
251

    
252

    
253
if __name__ == '__main__':
254
    usage = "Usage: %s <disk> <sectors>\n" % (sys.argv[0])
255

    
256
    if len(sys.argv) != 3:
257
        sys.stderr.write(usage)
258
        sys.exit(1)
259

    
260
    disk = sys.argv[1]
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)
265
        sys.exit(1)
266

    
267
    try:
268
        size = int(sys.argv[2])
269
    except ValueError:
270
        sys.stderr.write("Parameter new_size must be a number\n")
271
        sys.stderr.write(usage)
272
        sys.exit(1)
273

    
274
    ptable = GPTPartitionTable(disk)
275
    if size * BLOCKSIZE == ptable.size():
276
        sys.stderr.write("Nothing to do...\n")
277
    elif size * BLOCKSIZE > ptable.size():
278
        ptable.fix(size)
279
        sys.stderr.write("GPT table was fixed\n")
280
    else:
281
        sys.stderr.write("Disk is langer than size")
282
        exit(1)
283

    
284
    sys.exit(0)
285

    
286
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :