Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / disklabel.py @ c27ba032

History | View | Annotate | Download (26.7 kB)

1
#!/usr/bin/env python
2
#
3
# -*- coding: utf-8 -*-
4
#
5
# Copyright (C) 2013 GRNET S.A.
6
#
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
# General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20
# 02110-1301, USA.
21

    
22
"""This module provides the code for handling BSD disklabels"""
23

    
24
import struct
25
import sys
26
import os
27
import cStringIO
28
import optparse
29
import abc
30

    
31
from collections import namedtuple
32
from collections import OrderedDict
33

    
34
BLOCKSIZE = 512
35

    
36
LABELSECTOR = 1
37
LABELOFFSET = 0
38

    
39
BBSIZE = 8192  # size of boot area with label
40
SBSIZE = 8192  # max size of fs superblock
41

    
42
DISKMAGIC = 0x82564557
43

    
44

    
45
class MBR(object):
46
    """Represents a Master Boot Record."""
47
    class Partition(object):
48
        """Represents a partition entry in MBR"""
49
        fmt = "<B3sB3sLL"
50

    
51
        def __init__(self, raw_part):
52
            """Create a Partition instance"""
53
            (self.status,
54
             self.start,
55
             self.type,
56
             self.end,
57
             self.first_sector,
58
             self.sector_count
59
             ) = struct.unpack(self.fmt, raw_part)
60

    
61
        def pack(self):
62
            """Pack the partition values into a binary string"""
63
            return struct.pack(self.fmt,
64
                               self.status,
65
                               self.start,
66
                               self.type,
67
                               self.end,
68
                               self.first_sector,
69
                               self.sector_count)
70

    
71
        @staticmethod
72
        def size():
73
            """Returns the size of an MBR partition entry"""
74
            return struct.calcsize(MBR.Partition.fmt)
75

    
76
        def __str__(self):
77
            start = self.unpack_chs(self.start)
78
            end = self.unpack_chs(self.end)
79
            return "%d %s %d %s %d %d" % (self.status, start, self.type, end,
80
                                          self.first_sector, self.sector_count)
81

    
82
        @staticmethod
83
        def unpack_chs(chs):
84
            """Unpacks a CHS address string to a tuple."""
85

    
86
            assert len(chs) == 3
87

    
88
            head = struct.unpack('<B', chs[0])[0]
89
            sector = struct.unpack('<B', chs[1])[0] & 0x3f
90
            cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
91
                struct.unpack('<B', chs[2])[0]
92

    
93
            return (cylinder, head, sector)
94

    
95
        @staticmethod
96
        def pack_chs(cylinder, head, sector):
97
            """Packs a CHS tuple to an address string."""
98

    
99
            assert 1 <= sector <= 63
100
            assert 0 <= head <= 255
101
            assert 0 <= cylinder
102

    
103
            # If the cylinders overflow then put the value (1023, 254, 63) to
104
            # the tuple. At least this is what OpenBSD does.
105
            if cylinder > 1023:
106
                cylinder = 1023
107
                head = 254
108
                sector = 63
109

    
110
            byte0 = head
111
            byte1 = (cylinder >> 2) & 0xC0 | sector
112
            byte2 = cylinder & 0xff
113

    
114
            return struct.pack('<BBB', byte0, byte1, byte2)
115

    
116
    def __init__(self, block):
117
        """Create an MBR instance"""
118

    
119
        self.fmt = "<444s2x16s16s16s16s2s"
120
        raw_part = {}     # Offset  Length          Contents
121
        (self.code_area,  # 0       440(max. 446)   code area
122
                          # 440     2(optional)     disk signature
123
                          # 444     2               Usually nulls
124
         raw_part[0],     # 446     16              Partition 0
125
         raw_part[1],     # 462     16              Partition 1
126
         raw_part[2],     # 478     16              Partition 2
127
         raw_part[3],     # 494     16              Partition 3
128
         self.signature   # 510     2               MBR signature
129
         ) = struct.unpack(self.fmt, block)
130

    
131
        self.part = {}
132
        for i in range(4):
133
            self.part[i] = self.Partition(raw_part[i])
134

    
135
    def size(self):
136
        """Return the size of a Master Boot Record."""
137
        return struct.calcsize(self.fmt)
138

    
139
    def pack(self):
140
        """Pack an MBR to a binary string."""
141
        return struct.pack(self.fmt,
142
                           self.code_area,
143
                           self.part[0].pack(),
144
                           self.part[1].pack(),
145
                           self.part[2].pack(),
146
                           self.part[3].pack(),
147
                           self.signature)
148

    
149
    def __str__(self):
150
        """Print the MBR"""
151
        ret = ""
152
        for i in range(4):
153
            ret += "Partition %d: %s\n" % (i, self.part[i])
154
        ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
155
                                       hex(ord(self.signature[1])))
156
        title = "Master Boot Record"
157
        return "%s\n%s\n%s\n" % (title, len(title) * "=", ret)
158

    
159

    
160
class Disk(object):
161
    """Represents a BSD Disk"""
162

    
163
    def __init__(self, device):
164
        """Create a Disk instance"""
165
        self.device = device
166
        self.part_num = None
167
        self.disklabel = None
168

    
169
        with open(device, "rb") as d:
170
            sector0 = d.read(BLOCKSIZE)
171
            self.mbr = MBR(sector0)
172

    
173
            for i in range(4):
174
                ptype = self.mbr.part[i].type
175
                if ptype in (0xa5, 0xa6, 0xa9):
176
                    d.seek(BLOCKSIZE * self.mbr.part[i].first_sector)
177
                    self.part_num = i
178
                    if ptype == 0xa5:  # FreeBSD
179
                        self.disklabel = BSDDisklabel(d)
180
                    elif ptype == 0xa6:  # OpenBSD
181
                        self.disklabel = OpenBSDDisklabel(d)
182
                    else:  # NetBSD
183
                        self.disklabel = BSDDisklabel(d)
184
                    break
185

    
186
        assert self.disklabel is not None, "No *BSD partition found"
187

    
188
    def write(self):
189
        """Write the changes back to the media"""
190
        with open(self.device, 'rw+b') as d:
191
            d.write(self.mbr.pack())
192

    
193
            d.seek(self.mbr.part[self.part_num].first_sector * BLOCKSIZE)
194
            self.disklabel.write_to(d)
195

    
196
    def __str__(self):
197
        """Print the partitioning info of the Disk"""
198
        return str(self.mbr) + str(self.disklabel)
199

    
200
    def enlarge(self, new_size):
201
        """Enlarge the disk and return the last usable sector"""
202

    
203
        # Fix the disklabel
204
        end = self.disklabel.enlarge(new_size)
205

    
206
        # Fix the MBR
207
        start = self.mbr.part[self.part_num].first_sector
208
        self.mbr.part[self.part_num].sector_count = end - start + 1
209

    
210
        ntracks = self.disklabel.field['ntracks']
211
        nsectors = self.disklabel.field['nsectors']
212

    
213
        cylinder = end // (ntracks * nsectors)
214
        header = (end // nsectors) % ntracks
215
        sector = (end % nsectors) + 1
216
        chs = MBR.Partition.pack_chs(cylinder, header, sector)
217
        self.mbr.part[self.part_num].end = chs
218

    
219
    def enlarge_last_partition(self):
220
        """Enlarge the last partition to cover up all the free space"""
221
        self.disklabel.enlarge_last_partition()
222

    
223
    def get_last_partition_id(self):
224
        """Get the ID of the last partition"""
225
        return self.disklabel.get_last_partition_id()
226

    
227
    def get_duid(self):
228
        """Get the Disklabel Unique Identifier (works only for OpenBSD)"""
229
        if 'uid' in self.disklabel.field:
230
            return self.disklabel.field['uid']
231

    
232
        return ""
233

    
234

    
235
class DisklabelBase(object):
236
    """Disklabel base class"""
237
    __metaclass__ = abc.ABCMeta
238

    
239
    def __init__(self, device):
240
        """Create a Disklabel instance"""
241

    
242
        # Subclasses need to overwrite this
243
        self.field = None
244
        self.ptable = None
245

    
246
    @abc.abstractproperty
247
    def fmt(self):
248
        """Fields format string for the disklabel fields"""
249
        pass
250

    
251
    def pack(self, checksum=None):
252
        """Return a binary copy of the Disklabel block"""
253

    
254
        out = OrderedDict()
255
        for k, v in self.field.iteritems():
256
            out[k] = v
257

    
258
        if checksum is not None:
259
            out['checksum'] = checksum
260

    
261
        return struct.pack(self.fmt, * out.values() + [self.ptable.pack()])
262

    
263
    def compute_checksum(self):
264
        """Compute the checksum of the disklabel"""
265

    
266
        raw = cStringIO.StringIO(self.pack(0))
267
        checksum = 0
268
        try:
269
            uint16 = raw.read(2)
270
            while uint16 != "":
271
                checksum ^= struct.unpack('<H', uint16)[0]
272
                uint16 = raw.read(2)
273
        finally:
274
            raw.close()
275

    
276
        return checksum
277

    
278
    @abc.abstractmethod
279
    def enlarge(self, new_size):
280
        """Enlarge the disk and return the last usable sector"""
281
        pass
282

    
283
    def write_to(self, device):
284
        """Write the disklabel to a device"""
285

    
286
        # The disklabel starts at sector 1
287
        device.seek(BLOCKSIZE, os.SEEK_CUR)
288
        device.write(self.pack())
289

    
290
    @abc.abstractmethod
291
    def enlarge_last_partition(self):
292
        """Enlarge the last partition to consume all the usable space"""
293
        pass
294

    
295
    @abc.abstractmethod
296
    def get_last_partition_id(self):
297
        """Get the ID of the last partition"""
298
        pass
299

    
300
    @abc.abstractmethod
301
    def __str__(self):
302
        """Print the Disklabel"""
303
        pass
304

    
305

    
306
class PartitionTableBase(object):
307
    """Base Class for disklabel partition tables"""
308
    __metaclass__ = abc.ABCMeta
309

    
310
    @abc.abstractproperty
311
    def fmt(self):
312
        """Partition fields format string"""
313
        pass
314

    
315
    @abc.abstractproperty
316
    def fields(self):
317
        """The partition fields"""
318
        pass
319

    
320
    def __init__(self, ptable, pnumber):
321
        """Create a Partition Table instance"""
322

    
323
        self.Partition = namedtuple('Partition', self.fields)
324
        self.part = []
325

    
326
        size = struct.calcsize(self.fmt)
327
        raw = cStringIO.StringIO(ptable)
328
        try:
329
            for _ in xrange(pnumber):
330
                self.part.append(
331
                    self.Partition(*struct.unpack(self.fmt, raw.read(size)))
332
                    )
333
        finally:
334
            raw.close()
335

    
336
    def __str__(self):
337
        """Print the Partition table"""
338
        val = ""
339
        for i in xrange(len(self.part)):
340
            val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
341
        return val
342

    
343
    def pack(self):
344
        """Packs the partition table into a binary string."""
345
        ret = ""
346
        for i in xrange(len(self.part)):
347
            ret += struct.pack(self.fmt, *self.part[i])
348
        return ret + ((364 - len(self.part) * 16) * '\x00')
349

    
350

    
351
class BSDDisklabel(DisklabelBase):
352
    """Represents an BSD Disklabel"""
353

    
354
    class PartitionTable(PartitionTableBase):
355
        """Represents a BSD Partition Table"""
356

    
357
        @property
358
        def fmt(self):
359
            """Partition fields format string"""
360
            return "<IIIBBH"
361

    
362
        @property
363
        def fields(self):
364
            """The partition fields"""
365
            return [    # Offset  Length Contents
366
                'size',     # 0       4      Number of sectors in partition
367
                'offset',   # 4       4      Starting sector of the partition
368
                'fsize',    # 8       4      File system basic fragment size
369
                'fstype',   # 12      1      File system type
370
                'frag',     # 13      1      File system fragments per block
371
                'cpg'       # 14      2      File system cylinders per group
372
                ]
373

    
374
    @property
375
    def fmt(self):
376
        return "<IHH16s16sIIIIIIHHIHHHHIII20s20sIHHII364s"
377

    
378
    def __init__(self, device):
379
        """Create a BSD DiskLabel instance"""
380
        super(BSDDisklabel, self).__init__(device)
381

    
382
        # Disklabel starts at offset one
383
        device.seek(BLOCKSIZE, os.SEEK_CUR)
384
        sector1 = device.read(BLOCKSIZE)
385

    
386
        d_ = OrderedDict()      # Off  Len    Content
387
        (d_["magic"],           # 0    4      Magic
388
         d_["dtype"],           # 4    2      Drive Type
389
         d_["subtype"],         # 6    2      Subtype
390
         d_["typename"],        # 8    16     Type Name
391
         d_["packname"],        # 24   16     Pack Identifier
392
         d_["secsize"],         # 32   4      Bytes per sector
393
         d_["nsectors"],        # 36   4      Data sectors per track
394
         d_["ntracks"],         # 40   4      Tracks per cylinder
395
         d_["ncylinders"],      # 44   4      Data cylinders per unit
396
         d_["secpercyl"],       # 48   4      Data sectors per cylinder
397
         d_["secperunit"],      # 52   4      Data sectors per unit
398
         d_["sparespertrack"],  # 56   2      Spare sectors per track
399
         d_["sparespercyl"],    # 58   2      Spare sectors per cylinder
400
         d_["acylinders"],      # 60   4      Alternative cylinders per unit
401
         d_["rpm"],             # 64   2      Rotation Speed
402
         d_["interleave"],      # 66   2      Hardware sector interleave
403
         d_["trackskew"],       # 68   2      Sector 0 skew, per track
404
         d_["cylskew"],         # 70   2      Sector 0 skew, per cylinder
405
         d_["headswitch"],      # 72   4      Head switch time
406
         d_["trkseek"],         # 76   4      Track-to-track seek
407
         d_["flags"],           # 80   4      Generic Flags
408
         d_["drivedata"],       # 84   5*4    Drive-type specific information
409
         d_["spare"],           # 104  5*4    Reserved for future use
410
         d_["magic2"],          # 124  4      Magic Number
411
         d_["checksum"],        # 128  2      Xor of data including partitions
412
         d_["npartitions"],     # 130  2      Number of partitions following
413
         d_["bbsize"],          # 132  4      size of boot area at sn0, bytes
414
         d_["sbsize"],          # 136  4      Max size of fs superblock, bytes
415
         ptable_raw             # 140  16*16  Partition Table
416
         ) = struct.unpack(self.fmt, sector1)
417

    
418
        assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
419
        self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
420
        self.field = d_
421

    
422
    def enlarge(self, new_size):
423
        raise NotImplementedError
424

    
425
    def enlarge_last_partition(self):
426
        raise NotImplementedError
427

    
428
    def get_last_partition_id(self):
429
        raise NotImplementedError
430

    
431
    def __str__(self):
432
        """Print the Disklabel"""
433

    
434
        title = "Disklabel"
435

    
436
        # Those fields may contain null bytes
437
        typename = self.field['typename'].strip('\x00').strip()
438
        packname = self.field['packname'].strip('\x00').strip()
439

    
440
        return \
441
            "%s\n%s\n" % (title, len(title) * "=") + \
442
            "Magic Number: 0x%(magic)x\n" \
443
            "Drive type: %(dtype)d\n" \
444
            "Subtype: %(subtype)d\n" % self.field + \
445
            "Typename: %s\n" % typename + \
446
            "Pack Identifier: %s\n" % packname + \
447
            "# of bytes per sector: %(secsize)d\n" \
448
            "# of data sectors per track: %(nsectors)d\n" \
449
            "# of tracks per cylinder: %(ntracks)d\n" \
450
            "# of data cylinders per unit: %(ncylinders)d\n" \
451
            "# of data sectors per cylinder: %(secpercyl)d\n" \
452
            "# of data sectors per unit: %(secperunit)d\n" \
453
            "# of spare sectors per track: %(sparespertrack)d\n" \
454
            "# of spare sectors per cylinder: %(sparespercyl)d\n" \
455
            "Alt. cylinders per unit: %(acylinders)d\n" \
456
            "Rotational speed: %(rpm)d\n" \
457
            "Hardware sector interleave: %(interleave)d\n" \
458
            "Sector 0 skew, per track: %(trackskew)d\n" \
459
            "Sector 0 skew, per cylinder: %(cylskew)d\n" \
460
            "Head switch time, usec: %(headswitch)d\n" \
461
            "Track-to-track seek, usec: %(trkseek)d\n" \
462
            "Generic Flags: %(flags)r\n" \
463
            "Drive data: %(drivedata)r\n" \
464
            "Reserved for future use: %(spare)r\n" \
465
            "The magic number again: 0x%(magic2)x\n" \
466
            "Checksum: %(checksum)d\n" \
467
            "Number of partitions: %(npartitions)d\n"  \
468
            "Size of boot aread at sn0: %(bbsize)d\n"  \
469
            "Max size of fs superblock: %(sbsize)d\n" % self.field + \
470
            "%s" % self.ptable
471

    
472

    
473
class OpenBSDDisklabel(DisklabelBase):
474
    """Represents an OpenBSD Disklabel"""
475

    
476
    class PartitionTable(PartitionTableBase):
477
        """Reprepsents an OpenBSD Partition Table"""
478

    
479
        @property
480
        def fmt(self):
481
            return "<IIHHBBH"
482

    
483
        @property
484
        def fields(self):
485
            return [    # Offset  Length Contents
486
                'size',     # 0       4      Number of sectors in the partition
487
                'offset',   # 4       4      Starting sector of the partition
488
                'offseth',  # 8       2      Starting sector (high part)
489
                'sizeh',    # 10      2      Number of sectors (high part)
490
                'fstype',   # 12      1      Filesystem type
491
                'frag',     # 13      1      Filesystem Fragments per block
492
                'cpg'       # 14      2      FS cylinders per group
493
                ]
494

    
495
        def setpsize(self, i, size):
496
            """Set size for partition i"""
497
            tmp = self.part[i]
498
            self.part[i] = self.Partition(
499
                size & 0xffffffff, tmp.offset, tmp.offseth, size >> 32,
500
                tmp.fstype, tmp.frag, tmp.cpg)
501

    
502
        def getpsize(self, i):
503
            """Get size for partition i"""
504
            return (self.part[i].sizeh << 32) + self.part[i].size
505

    
506
        def setpoffset(self, i, offset):
507
            """Set offset for partition i"""
508
            tmp = self.part[i]
509
            self.part[i] = self.Partition(
510
                tmp.size, offset & 0xffffffff, offset >> 32, tmp.sizeh,
511
                tmp.frag, tmp.cpg)
512

    
513
        def getpoffset(self, i):
514
            """Get offset for partition i"""
515
            return (self.part[i].offseth << 32) + self.part[i].offset
516

    
517
    @property
518
    def fmt(self):
519
        return "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
520

    
521
    def __init__(self, device):
522
        """Create a DiskLabel instance"""
523

    
524
        super(OpenBSDDisklabel, self).__init__(device)
525

    
526
        # Disklabel starts at offset one
527
        device.seek(BLOCKSIZE, os.SEEK_CUR)
528
        sector1 = device.read(BLOCKSIZE)
529

    
530
        d_ = OrderedDict()   # Off  Len    Content
531
        (d_["magic"],        # 0    4      Magic
532
         d_["dtype"],        # 4    2      Drive Type
533
         d_["subtype"],      # 6    2      Subtype
534
         d_["typename"],     # 8    16     Type Name
535
         d_["packname"],     # 24   16     Pack Identifier
536
         d_["secsize"],      # 32   4      Bytes per sector
537
         d_["nsectors"],     # 36   4      Data sectors per track
538
         d_["ntracks"],      # 40   4      Tracks per cylinder
539
         d_["ncylinders"],   # 44   4      Data cylinders per unit
540
         d_["secpercyl"],    # 48   4      Data sectors per cylinder
541
         d_["secperunit"],   # 52   4      Data sectors per unit
542
         d_["uid"],          # 56   8      Unique label identifier
543
         d_["acylinders"],   # 64   4      Alt cylinders per unit
544
         d_["bstarth"],      # 68   2      Start of useable region (high part)
545
         d_["bendh"],        # 70   2      Size of usable region (high part)
546
         d_["bstart"],       # 72   4      Start of useable region
547
         d_["bend"],         # 76   4      End of usable region
548
         d_["flags"],        # 80   4      Generic Flags
549
         d_["drivedata"],    # 84   5*4    Drive-type specific information
550
         d_["secperunith"],  # 104  2      Number of data sectors (high part)
551
         d_["version"],      # 106  2      Version
552
         d_["spare"],        # 108  4*4    Reserved for future use
553
         d_["magic2"],       # 124  4      Magic number
554
         d_["checksum"],     # 128  2      Xor of data including partitions
555
         d_["npartitions"],  # 130  2      Number of partitions in following
556
         d_["bbsize"],       # 132  4      size of boot area at sn0, bytes
557
         d_["sbsize"],       # 136  4      Max size of fs superblock, bytes
558
         ptable_raw          # 140  16*16  Partition Table
559
         ) = struct.unpack(self.fmt, sector1)
560

    
561
        assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
562
        self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
563
        self.field = d_
564

    
565
    def setdsize(self, dsize):
566
        """Set disk size"""
567
        self.field['secperunith'] = dsize >> 32
568
        self.field['secperunit'] = dsize & 0xffffffff
569

    
570
    def getdsize(self):
571
        """Get disk size"""
572
        return (self.field['secperunith'] << 32) + self.field['secperunit']
573

    
574
    dsize = property(getdsize, setdsize, None, "disk size")
575

    
576
    def setbstart(self, bstart):
577
        """Set start of useable region"""
578
        self.field['bstarth'] = bstart >> 32
579
        self.field['bstart'] = bstart & 0xffffffff
580

    
581
    def getbstart(self):
582
        """Get start of usable region"""
583
        return (self.field['bstarth'] << 32) + self.field['bstart']
584

    
585
    bstart = property(getbstart, setbstart, None, "start of usable region")
586

    
587
    def setbend(self, bend):
588
        """Set end of useable region"""
589
        self.field['bendh'] = bend >> 32
590
        self.field['bend'] = bend & 0xffffffff
591

    
592
    def getbend(self):
593
        """Get end of usable region"""
594
        return (self.field['bendh'] << 32) + self.field['bend']
595

    
596
    bend = property(getbend, setbend, None, "end of usable region")
597

    
598
    def enlarge(self, new_size):
599
        """Enlarge the disk and return the last usable sector"""
600

    
601
        assert new_size >= self.dsize, \
602
            "New size cannot be smaller that %s" % self.dsize
603

    
604
        # Fix the disklabel
605
        self.dsize = new_size
606
        self.field['ncylinders'] = self.dsize // (self.field['nsectors'] *
607
                                                  self.field['ntracks'])
608
        self.bend = (self.field['ncylinders'] * self.field['nsectors'] *
609
                     self.field['ntracks'])
610

    
611
        # Partition 'c' descriptes the entire disk
612
        self.ptable.setpsize(2, new_size)
613

    
614
        # Update the checksum
615
        self.field['checksum'] = self.compute_checksum()
616

    
617
        # The last usable sector is the end of the usable region minus one
618
        return self.bend - 1
619

    
620
    def get_last_partition_id(self):
621
        """Returns the id of the last partition"""
622
        part = 0
623
        end = 0
624
        # Don't check partition 'c' which is the whole disk
625
        for i in filter(lambda x: x != 2, range(len(self.ptable.part))):
626
            curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
627
            if end < curr_end:
628
                end = curr_end
629
                part = i
630

    
631
        assert end > 0, "No partition found"
632

    
633
        return part
634

    
635
    def enlarge_last_partition(self):
636
        """Enlarge the last partition to cover up all the free space"""
637

    
638
        part_num = self.get_last_partition_id()
639

    
640
        end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
641

    
642
        assert end > 0, "No partition found"
643

    
644
        if self.ptable.part[part_num].fstype == 1:  # Swap partition.
645
            #TODO: Maybe create a warning?
646
            return
647

    
648
        if end > (self.bend - 1024):
649
            return
650

    
651
        self.ptable.setpsize(
652
            part_num, self.bend - self.ptable.getpoffset(part_num) - 1024)
653

    
654
        self.field['checksum'] = self.compute_checksum()
655

    
656
    def __str__(self):
657
        """Print the Disklabel"""
658

    
659
        # Those values may contain null bytes
660
        typename = self.field['typename'].strip('\x00').strip()
661
        packname = self.field['packname'].strip('\x00').strip()
662

    
663
        duid = "".join(x.encode('hex') for x in self.field['uid'])
664

    
665
        title = "Disklabel"
666
        return \
667
            "%s\n%s\n" % (title, len(title) * "=") + \
668
            "Magic Number: 0x%(magic)x\n" \
669
            "Drive type: %(dtype)d\n" \
670
            "Subtype: %(subtype)d\n" % self.field + \
671
            "Typename: %s\n" % typename + \
672
            "Pack Identifier: %s\n" % packname + \
673
            "# of bytes per sector: %(secsize)d\n" \
674
            "# of data sectors per track: %(nsectors)d\n" \
675
            "# of tracks per cylinder: %(ntracks)d\n" \
676
            "# of data cylinders per unit: %(ncylinders)d\n" \
677
            "# of data sectors per cylinder: %(secpercyl)d\n" \
678
            "# of data sectors per unit: %(secperunit)d\n" % self.field + \
679
            "DUID: %s\n" % duid + \
680
            "Alt. cylinders per unit: %(acylinders)d\n" \
681
            "Start of useable region (high part): %(bstarth)d\n" \
682
            "Size of useable region (high part): %(bendh)d\n" \
683
            "Start of useable region: %(bstart)d\n" \
684
            "End of usable region: %(bend)d\n" \
685
            "Generic Flags: %(flags)r\n" \
686
            "Drive data: %(drivedata)r\n" \
687
            "Number of data sectors (high part): %(secperunith)d\n" \
688
            "Version: %(version)d\n" \
689
            "Reserved for future use: %(spare)r\n" \
690
            "The magic number again: 0x%(magic2)x\n" \
691
            "Checksum: %(checksum)d\n" \
692
            "Number of partitions: %(npartitions)d\n"  \
693
            "Size of boot aread at sn0: %(bbsize)d\n"  \
694
            "Max size of fs superblock: %(sbsize)d\n" % self.field + \
695
            "%s" % self.ptable
696

    
697

    
698
def main():
699
    """Main entry point"""
700
    usage = "Usage: %prog [options] <input_media>"
701
    parser = optparse.OptionParser(usage=usage)
702

    
703
    parser.add_option("-l", "--list", action="store_true", dest="list",
704
                      default=False,
705
                      help="list the disklabel on the specified media")
706
    parser.add_option("--get-last-partition", action="store_true",
707
                      dest="last_part", default=False,
708
                      help="print the label of the last partition")
709
    parser.add_option(
710
        "--get-duid", action="store_true", dest="duid", default=False,
711
        help="print the Disklabel Unique Identifier (OpenBSD only)")
712
    parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
713
                      default=None, metavar="SIZE",
714
                      help="Enlarge the disk to this SIZE (in sectors)")
715
    parser.add_option(
716
        "-p", "--enlarge-partition", action="store_true",
717
        dest="enlarge_partition", default=False,
718
        help="Enlarge the last partition to cover up the free space")
719

    
720
    options, args = parser.parse_args(sys.argv[1:])
721

    
722
    if len(args) != 1:
723
        parser.error("Wrong number of arguments")
724

    
725
    disk = Disk(args[0])
726

    
727
    if options.list:
728
        print disk
729
        return 0
730

    
731
    if options.duid:
732
        print "%s" % "".join(x.encode('hex') for x in disk.get_duid())
733
        return 0
734

    
735
    if options.last_part:
736
        print "%c" % chr(ord('a') + disk.get_last_partition_id())
737

    
738
    if options.disk_size is not None:
739
        disk.enlarge(options.disk_size)
740

    
741
    if options.enlarge_partition:
742
        disk.enlarge_last_partition()
743

    
744
    disk.write()
745
    return 0
746

    
747

    
748
if __name__ == '__main__':
749
    sys.exit(main())
750

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