Revision 331aa0ec

b/image_creator/disk.py
33 33

  
34 34
from image_creator.util import get_command
35 35
from image_creator.util import warn, progress, success, output, FatalError
36

  
36
from image_creator.gpt import GPTPartitionTable
37 37
import stat
38 38
import os
39 39
import tempfile
......
200 200
            raise FatalError("Multiple operating systems found."
201 201
                            "We only support images with one filesystem.")
202 202
        self.root = roots[0]
203
        self.gdev = self.g.part_to_dev(self.root)
204
        self.parttype = self.g.part_get_parttype(self.gdev)
205

  
203 206
        self.ostype = self.g.inspect_get_type(self.root)
204 207
        self.distro = self.g.inspect_get_distro(self.root)
205 208
        success('found a(n) %s system' % self.distro)
......
256 259
        """
257 260
        output("Shrinking image (this may take a while)...", False)
258 261

  
259
        dev = self.g.part_to_dev(self.root)
260
        parttype = self.g.part_get_parttype(dev)
261
        if parttype != 'msdos':
262
        if self.parttype not in 'msdos' 'gpt':
262 263
            raise FatalError("You have a %s partition table. "
263
                "Only msdos partitions are supported" % parttype)
264
                "Only msdos and gpt partitions are supported" % self.parttype)
264 265

  
265
        last_partition = self.g.part_list(dev)[-1]
266
        last_partition = self.g.part_list(self.gdev)[-1]
266 267

  
267 268
        if last_partition['part_num'] > 4:
268 269
            raise FatalError("This disk contains logical partitions. "
269 270
                "Only primary partitions are supported.")
270 271

  
271
        part_dev = "%s%d" % (dev, last_partition['part_num'])
272
        part_dev = "%s%d" % (self.gdev, last_partition['part_num'])
272 273
        fs_type = self.g.vfs_type(part_dev)
273 274
        if not re.match("ext[234]", fs_type):
274 275
            warn("Don't know how to resize %s partitions." % vfs_type)
275
            return
276
            return self.size()
276 277

  
277 278
        self.g.e2fsck_f(part_dev)
278 279
        self.g.resize2fs_M(part_dev)
......
283 284
        block_cnt = int(
284 285
            filter(lambda x: x[0] == 'Block count', out)[0][1])
285 286

  
286
        sector_size = self.g.blockdev_getss(dev)
287
        sector_size = self.g.blockdev_getss(self.gdev)
287 288

  
288 289
        start = last_partition['part_start'] / sector_size
289 290
        end = start + (block_size * block_cnt) / sector_size - 1
290 291

  
291
        self.g.part_del(dev, last_partition['part_num'])
292
        self.g.part_add(dev, 'p', start, end)
292
        self.g.part_del(self.gdev, last_partition['part_num'])
293
        self.g.part_add(self.gdev, 'p', start, end)
293 294

  
294 295
        new_size = (end + 1) * sector_size
295 296
        success("new image size is %dMB" %
296 297
                            ((new_size + 2 ** 20 - 1) // 2 ** 20))
298

  
299
        if self.parttype == 'gpt':
300
            ptable = GPTPartitionTable(self.device)
301
            return ptable.shrink(new_size)
302

  
297 303
        return new_size
298 304

  
299 305
    def size(self):
......
302 308
        The size returned by this method is the size of the space occupied by
303 309
        the partitions (including the space before the first partition).
304 310
        """
305
        dev = self.g.part_to_dev(self.root)
306
        last = self.g.part_list(dev)[-1]
307 311

  
308
        return last['part_end'] + 1
312
        if self.parttype == 'msdos':
313
            dev = self.g.part_to_dev(self.root)
314
            last = self.g.part_list(dev)[-1]
315
            return last['part_end'] + 1
316
        elif self.parttype == 'gpt':
317
            ptable = GPTPartitionTable(self.device)
318
            return ptable.size()
319
        else:
320
            raise FatalError("Unsupported partition table type: %s" % parttype)
309 321

  
310 322
    def dump(self, outfile):
311 323
        """Dumps the content of device into a file.
b/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
    class Partition(object):
44
        format = "<B3sB3sLL"
45

  
46
        def __init__(self, raw_part):
47

  
48
            (   self.status,
49
                self.start,
50
                self.type,
51
                self.end,
52
                self.first_sector,
53
                self.sector_count
54
            ) = struct.unpack(self.format, raw_part)
55

  
56
        def pack(self):
57
            return struct.pack(self.format,
58
                self.status,
59
                self.start,
60
                self.type,
61
                self.end,
62
                self.first_sector,
63
                self.sector_count
64
            )
65

  
66
        def show(self):
67
            start = self.unpack_chs(self.start)
68
            end = self.unpack_chs(self.end)
69
            print "%d %s %d %s %d %d" % (self.status, start, self.type, end,
70
                self.first_sector, self.sector_count)
71

  
72
        def unpack_chs(self, chs):
73

  
74
            assert len(chs) == 3
75

  
76
            head = struct.unpack('<B', chs[0])[0]
77
            sector = struct.unpack('<B', chs[1])[0] & 0x3f
78
            cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
79
                struct.unpack('<B', chs[2])[0]
80

  
81
            return (cylinder, head, sector)
82

  
83
        def pack_chs(self, cylinder, head, sector):
84

  
85
            assert 1 <= sector <= 63
86
            assert 0 <= cylinder <= 1023
87
            assert 0 <= head <= 255
88

  
89
            byte0 = head
90
            byte1 = (cylinder >> 2) & 0xC0 | sector
91
            byte2 = cylinder & 0xff
92

  
93
            return struct.pack('<BBB', byte0, byte1, byte2)
94

  
95
    format = "<444s2x16s16s16s16s2s"
96
    """
97
    Offset  Length          Contents
98
    0       440(max. 446)   code area
99
    440     2(optional)     disk signature
100
    444     2               Usually nulls
101
    446     16              Partition 0
102
    462     16              Partition 1
103
    478     16              Partition 2
104
    494     16              Partition 3
105
    510     2               MBR signature
106
    """
107
    def __init__(self, block):
108
        raw_part = {}
109
        self.code_area, \
110
        raw_part[0], \
111
        raw_part[1], \
112
        raw_part[2], \
113
        raw_part[3], \
114
        self.signature = struct.unpack(self.format, block)
115

  
116
        self.part = {}
117
        for i in range(4):
118
            self.part[i] = self.Partition(raw_part[i])
119

  
120
    def pack(self):
121
        return struct.pack(self.format,
122
            self.code_area,
123
            self.part[0].pack(),
124
            self.part[1].pack(),
125
            self.part[2].pack(),
126
            self.part[3].pack(),
127
            self.signature
128
        )
129

  
130
    def show(self):
131
        for i in range(4):
132
            print "Part %d: " % i,
133
            self.part[i].show()
134

  
135

  
136
class GPTPartitionTable(object):
137
    class GPTHeader(object):
138
        format = "<8s4sII4xQQQQ16sQIII"
139
        """
140
        Offset	Length 	        Contents
141
        0       8 bytes         Signature
142
        8       4 bytes 	Revision
143
        12      4 bytes 	Header size in little endian
144
        16 	4 bytes 	CRC32 of header
145
        20 	4 bytes 	Reserved; must be zero
146
        24 	8 bytes 	Current LBA
147
        32 	8 bytes 	Backup LBA
148
        40 	8 bytes 	First usable LBA for partitions
149
        48 	8 bytes 	Last usable LBA
150
        56 	16 bytes 	Disk GUID
151
        72 	8 bytes 	Partition entries starting LBA
152
        80 	4 bytes 	Number of partition entries
153
        84 	4 bytes 	Size of a partition entry
154
        88 	4 bytes 	CRC32 of partition array
155
        92 	* 	        Reserved; must be zeroes
156
        LBA    size            Total
157
        """
158

  
159
        def __init__(self, block):
160
            self.signature, \
161
            self.revision, \
162
            self.size, \
163
            self.header_crc32, \
164
            self.current_lba, \
165
            self.backup_lba, \
166
            self.first_usable_lba, \
167
            self.last_usable_lba, \
168
            self.uuid, \
169
            self.part_entry_start, \
170
            self.part_count, \
171
            self.part_entry_size, \
172
            self.part_crc32 = struct.unpack(self.format, block)
173

  
174
        def pack(self):
175
            return struct.pack(self.format,
176
                self.signature, \
177
                self.revision, \
178
                self.size, \
179
                self.header_crc32, \
180
                self.current_lba, \
181
                self.backup_lba, \
182
                self.first_usable_lba, \
183
                self.last_usable_lba, \
184
                self.uuid, \
185
                self.part_entry_start, \
186
                self.part_count, \
187
                self.part_entry_size, \
188
                self.part_crc32
189
            )
190

  
191
        def show(self):
192
            print "Signature: %s" % self.signature
193
            print "Revision: %r" % self.revision
194
            print "Header Size: %d" % self.size
195
            print "CRC32: %d" % self.header_crc32
196
            print "Current LBA: %d" % self.current_lba
197
            print "Backup LBA: %d" % self.backup_lba
198
            print "First Usable LBA: %d" % self.first_usable_lba
199
            print "Last Usable LBA: %d" % self.last_usable_lba
200
            print "Disk GUID: %s" % uuid.UUID(bytes=self.uuid)
201
            print "Partition entries starting LBA: %d" % self.part_entry_start
202
            print "Number of Partition entries: %d" % self.part_count
203
            print "Size of a partition entry: %d" % self.part_entry_size
204
            print "CRC32 of partition array: %s" % self.part_crc32
205

  
206
    def __init__(self, disk):
207
        self.disk = disk
208
        with open(disk, "rb") as d:
209
            #MBR (Logical block address 0)
210
            lba0 = d.read(BLOCKSIZE)
211
            self.mbr = MBR(lba0)
212
            # Primary GPT Header (LBA 1)
213
            lba1 = d.read(BLOCKSIZE)
214
            self.primary = self.GPTHeader(lba1[:92])
215
            # Partition entries (LBA 2...34)
216
            d.seek(self.primary.part_entry_start * BLOCKSIZE)
217
            entries_size = self.primary.part_count * \
218
                                                self.primary.part_entry_size
219
            self.part_entries = d.read(entries_size)
220
            # Secondary GPT Header (LBA -1)
221
            d.seek(self.primary.backup_lba * BLOCKSIZE)
222
            lba_1 = d.read(BLOCKSIZE)
223
            self.secondary = self.GPTHeader(lba_1[:92])
224

  
225
    def size(self):
226
        return (self.primary.backup_lba + 1) * BLOCKSIZE 
227

  
228
    def shrink(self, size):
229

  
230
        if size == self.size():
231
            return size
232

  
233
        assert size < self.size()
234

  
235
        # new_size = size + Partition Entries + Secondary GPT Header
236
        new_size = size + len(self.part_entries) + BLOCKSIZE
237
        new_size = ((new_size + 4095) // 4096) * 4096  # align to 4K
238
        lba_count = new_size // BLOCKSIZE
239

  
240
        # Correct MBR
241
        #TODO: Check for hybrid partition tables
242
        self.mbr.part[0].sector_count = (new_size // BLOCKSIZE) - 1
243

  
244
        # Correct Primary header
245
        self.primary.header_crc32 = 0
246
        self.primary.backup_lba = lba_count - 1  # LBA-1
247
        self.primary.last_usable_lba = lba_count - 34  # LBA-34
248
        self.primary.header_crc32 = \
249
                            binascii.crc32(self.primary.pack()) & 0xffffffff
250

  
251
        # Correct Secondary header entries
252
        self.secondary.header_crc32 = 0
253
        self.secondary.current_lba = self.primary.backup_lba
254
        self.secondary.last_usable_lba = lba_count - 34  # LBA-34
255
        self.secondary.part_entry_start = lba_count - 33  # LBA-33
256
        self.secondary.header_crc32 = \
257
                            binascii.crc32(self.secondary.pack()) & 0xffffffff
258

  
259
        # Copy the new partition table back to the device
260
        with open(self.disk, "wb") as d:
261
            d.write(self.mbr.pack())
262
            d.write(struct.pack("%ss" % BLOCKSIZE, '\x00' * BLOCKSIZE))
263
            d.seek(BLOCKSIZE)
264
            d.write(self.primary.pack())
265
            d.seek(self.secondary.part_entry_start * BLOCKSIZE)
266
            d.write(self.part_entries)
267
            d.seek(self.primary.backup_lba * BLOCKSIZE)
268
            d.write(struct.pack("%ss" % BLOCKSIZE, '\x00' * BLOCKSIZE))
269
            d.seek(self.primary.backup_lba * BLOCKSIZE)
270
            d.write(self.secondary.pack())
271

  
272
        return new_size
273

  
274
if __name__ == '__main__':
275
    ptable = GPTPartitionTable(sys.argv[1])
276

  
277
    print "MBR:"
278
    ptable.mbr.show()
279
    print
280
    print "Primary partition table:"
281
    ptable.primary.show()
282
    print
283
    print "Secondary partition table:"
284
    ptable.secondary.show()
285

  
286
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
b/image_creator/util.py
113 113
    BLOCKSIZE = 2 ** 22  # 4MB
114 114

  
115 115
    progressbar = progress("Calculating md5sum:", 'mb')
116
    progressbar.max = (size // (2 ** 20))
116
    progressbar.max = ((size + 2 ** 20 - 1) // (2 ** 20))
117 117
    md5 = hashlib.md5()
118 118
    with open(filename, "r") as src:
119 119
        left = size

Also available in: Unified diff