Statistics
| Branch: | Tag: | Revision:

root / snf-image-helper / disklabel.py @ 287c1028

History | View | Annotate | Download (25.8 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

    
30
from collections import namedtuple
31
from collections import OrderedDict
32

    
33
BLOCKSIZE = 512
34

    
35
LABELSECTOR = 1
36
LABELOFFSET = 0
37

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

    
41
DISKMAGIC = 0x82564557
42

    
43

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

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

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

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

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

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

    
85
            assert len(chs) == 3
86

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

    
92
            return (cylinder, head, sector)
93

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

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

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

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

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

    
115
    format = "<444s2x16s16s16s16s2s"
116
    """
117
    Offset  Length          Contents
118
    0       440(max. 446)   code area
119
    440     2(optional)     disk signature
120
    444     2               Usually nulls
121
    446     16              Partition 0
122
    462     16              Partition 1
123
    478     16              Partition 2
124
    494     16              Partition 3
125
    510     2               MBR signature
126
    """
127
    def __init__(self, block):
128
        """Create an MBR instance"""
129
        raw_part = {}
130
        (self.code_area,
131
         raw_part[0],
132
         raw_part[1],
133
         raw_part[2],
134
         raw_part[3],
135
         self.signature) = struct.unpack(self.format, block)
136

    
137
        self.part = {}
138
        for i in range(4):
139
            self.part[i] = self.Partition(raw_part[i])
140

    
141
    @staticmethod
142
    def size():
143
        """Return the size of a Master Boot Record."""
144
        return struct.calcsize(MBR.format)
145

    
146
    def pack(self):
147
        """Pack an MBR to a binary string."""
148
        return struct.pack(self.format,
149
                           self.code_area,
150
                           self.part[0].pack(),
151
                           self.part[1].pack(),
152
                           self.part[2].pack(),
153
                           self.part[3].pack(),
154
                           self.signature)
155

    
156
    def __str__(self):
157
        ret = ""
158
        for i in range(4):
159
            ret += "Partition %d: %s\n" % (i, self.part[i])
160
        ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
161
                                       hex(ord(self.signature[1])))
162
        title = "Master Boot Record"
163
        return "%s\n%s\n%s\n" % (title, len(title) * "=", ret)
164

    
165

    
166
class Disk(object):
167
    """Represents an BSD Disk"""
168

    
169
    def __init__(self, device):
170
        """Create a Disk instance"""
171
        self.device = device
172
        self.part_num = None
173
        self.disklabel = None
174

    
175
        with open(device, "rb") as d:
176
            sector0 = d.read(BLOCKSIZE)
177
            self.mbr = MBR(sector0)
178

    
179
            for i in range(4):
180
                ptype = self.mbr.part[i].type
181
                if ptype in (0xa5, 0xa6, 0xa9):
182
                    d.seek(BLOCKSIZE * self.mbr.part[i].first_sector)
183
                    self.part_num = i
184
                    if ptype == 0xa5:  # FreeBSD
185
                        self.disklabel = BSD_Disklabel(d)
186
                    elif ptype == 0xa6:  # OpenBSD
187
                        self.disklabel = OpenBSD_Disklabel(d)
188
                    else:  # NetBSD
189
                        self.disklabel = BSD_Disklabel(d)
190
                    break
191

    
192
        assert self.disklabel is not None, "No *BSD partition found"
193

    
194
    def write(self):
195
        """Write the changes back to the media"""
196
        with open(self.device, 'rw+b') as d:
197
            d.write(self.mbr.pack())
198

    
199
            d.seek(self.mbr.part[self.part_num].first_sector * BLOCKSIZE)
200
            self.disklabel.write_to(d)
201

    
202
    def __str__(self):
203
        return str(self.mbr) + str(self.disklabel)
204

    
205
    def enlarge(self, new_size):
206
        """Enlarge the disk and return the last usable sector"""
207

    
208
        # Fix the disklabel
209
        end = self.disklabel.enlarge(new_size)
210

    
211
        # Fix the MBR
212
        start = self.mbr.part[self.part_num].first_sector
213
        self.mbr.part[self.part_num].sector_count = end - start + 1
214

    
215
        ntracks = self.disklabel.field['ntracks']
216
        nsectors = self.disklabel.field['nsectors']
217

    
218
        cylinder = end // (ntracks * nsectors)
219
        header = (end // nsectors) % ntracks
220
        sector = (end % nsectors) + 1
221
        chs = MBR.Partition.pack_chs(cylinder, header, sector)
222
        self.mbr.part[self.part_num].end = chs
223

    
224
    def enlarge_last_partition(self):
225
        """Enlarge the last partition to cover up all the free space"""
226
        self.disklabel.enlarge_last_partition()
227

    
228
    def get_last_partition_id(self):
229
        """Get the ID of the last partition"""
230
        return self.disklabel.get_last_partition_id()
231

    
232
    def get_duid(self):
233
        """Get the Disk UID (work only on OpenBSD)"""
234
        if 'uid' in self.disklabel.field:
235
            return self.disklabel.field['uid']
236

    
237
        return ""
238

    
239

    
240
class DisklabelBase(object):
241
    """Disklabel base class"""
242

    
243
    def __init__(self, device):
244
        """Create a Disklabel instance"""
245
        raise NotImplementedError
246

    
247
    def pack(self, checksum=None):
248
        """Return a binary copy of the Disklabel block"""
249

    
250
        out = OrderedDict()
251
        for k, v in self.field.items():
252
            out[k] = v
253

    
254
        if checksum is not None:
255
            out['checksum'] = checksum
256

    
257
        return struct.pack(self.format, * out.values() + [self.ptable.pack()])
258

    
259
    def compute_checksum(self):
260
        """Compute the checksum of the disklabel"""
261

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

    
272
        return checksum
273

    
274
    def enlarge(self, new_size):
275
        """Enlarge the disk and return the last usable sector"""
276
        raise NotImplementedError
277

    
278
    def write_to(self, device):
279
        """Write the disklabel to a device"""
280

    
281
        # The disklabel starts at sector 1
282
        device.seek(BLOCKSIZE, os.SEEK_CUR)
283
        device.write(self.pack())
284

    
285
    def enlarge_last_partition(self):
286
        """Enlarge the last partition to consume all the usable space"""
287
        raise NotImplementedError
288

    
289
    def get_last_partition_id(self):
290
        """Get the ID of the last partition"""
291
        raise NotImplementedError
292

    
293
    def __str__(self):
294
        """Print the Disklabel"""
295
        raise NotImplementedError
296

    
297

    
298
class PartitionTableBase(object):
299
    """Base Class for disklabel partition tables"""
300

    
301
    @property
302
    def format(self):
303
        """Partition table format string"""
304
        raise NotImplementedError
305

    
306
    Partition = namedtuple('Partition', '')
307

    
308
    def __init__(self, ptable, pnumber):
309
        """Create a Partition Table instance"""
310
        self.part = []
311

    
312
        size = struct.calcsize(self.format)
313

    
314
        raw = cStringIO.StringIO(ptable)
315
        try:
316
            for i in range(pnumber):
317
                p = self.Partition(
318
                    *struct.unpack(self.format, raw.read(size)))
319
                self.part.append(p)
320
        finally:
321
            raw.close()
322

    
323
    def __str__(self):
324
        """Print the Partition table"""
325
        val = ""
326
        for i in range(len(self.part)):
327
            val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
328
        return val
329

    
330
    def pack(self):
331
        """Packs the partition table into a binary string."""
332
        ret = ""
333
        for i in range(len(self.part)):
334
            ret += struct.pack(self.format, *self.part[i])
335
        return ret + ((364 - len(self.part) * 16) * '\x00')
336

    
337

    
338
class BSD_Disklabel(DisklabelBase):
339
    """Represents an BSD Disklabel"""
340

    
341
    class PartitionTable(PartitionTableBase):
342
        """Represents a BSD Partition Table"""
343
        format = "<IIIBBH"
344
        """
345
        Partition Entry:
346
        Offset  Length          Contents
347
        0       4               Number of sectors in partition
348
        4       4               Starting sector
349
        8       4               Filesystem basic fragment size
350
        12      1               Filesystem type
351
        13      1               Filesystem fragments per block
352
        14      2               Filesystem cylinders per group
353
        """
354

    
355
        Partition = namedtuple(
356
            'Partition', 'size, offset, fsize, fstype, frag, cpg')
357

    
358
    format = "<IHH16s16sIIIIIIHHIHHHHIII20s20sIHHII364s"
359

    
360
    def __init__(self, device):
361
        """Create a BSD DiskLabel instance"""
362

    
363
        device.seek(BLOCKSIZE, os.SEEK_CUR)
364
        # The offset of the disklabel from the beginning of the partition is
365
        # one sector
366
        sector1 = device.read(BLOCKSIZE)
367

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

    
400
        assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
401
        self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
402
        self.field = d_
403

    
404
    def __str__(self):
405
        """Print the Disklabel"""
406

    
407
        title = "Disklabel"
408
        typename = self.field['typename'].strip('\x00').strip()
409
        packname = self.field['packname'].strip('\x00').strip()
410

    
411
        return \
412
            "%s\n%s\n" % (title, len(title) * "=") + \
413
            "Magic Number: 0x%(magic)x\n" \
414
            "Drive type: %(dtype)d\n" \
415
            "Subtype: %(subtype)d\n" % self.field + \
416
            "Typename: %s\n" % typename + \
417
            "Pack Identifier: %s\n" % packname + \
418
            "# of bytes per sector: %(secsize)d\n" \
419
            "# of data sectors per track: %(nsectors)d\n" \
420
            "# of tracks per cylinder: %(ntracks)d\n" \
421
            "# of data cylinders per unit: %(ncylinders)d\n" \
422
            "# of data sectors per cylinder: %(secpercyl)d\n" \
423
            "# of data sectors per unit: %(secperunit)d\n" \
424
            "# of spare sectors per track: %(sparespertrack)d\n" \
425
            "# of spare sectors per cylinder: %(sparespercyl)d\n" \
426
            "Alt. cylinders per unit: %(acylinders)d\n" \
427
            "Rotational speed: %(rpm)d\n" \
428
            "Hardware sector interleave: %(interleave)d\n" \
429
            "Sector 0 skew, per track: %(trackskew)d\n" \
430
            "Sector 0 skew, per cylinder: %(cylskew)d\n" \
431
            "Head switch time, usec: %(headswitch)d\n" \
432
            "Track-to-track seek, usec: %(trkseek)d\n" \
433
            "Generic Flags: %(flags)r\n" \
434
            "Drive data: %(drivedata)r\n" \
435
            "Reserved for future use: %(spare)r\n" \
436
            "The magic number again: 0x%(magic2)x\n" \
437
            "Checksum: %(checksum)d\n" \
438
            "Number of partitions: %(npartitions)d\n"  \
439
            "Size of boot aread at sn0: %(bbsize)d\n"  \
440
            "Max size of fs superblock: %(sbsize)d\n" % self.field + \
441
            "%s" % self.ptable
442

    
443

    
444
class OpenBSD_Disklabel(DisklabelBase):
445
    """Represents an OpenBSD Disklabel"""
446

    
447
    class PartitionTable(PartitionTableBase):
448
        """Reprepsents an OpenBSD Partition Table"""
449
        format = "<IIHHBBH"
450
        """
451
        Partition Entry:
452
        Offset  Length          Contents
453
        0       4               Number of sectors in the partition
454
        4       4               Starting sector
455
        8       2               Starting sector (high part)
456
        10      2               Number of sectors (high part)
457
        12      1               Filesystem type
458
        13      1               Filesystem Fragment per block
459
        14      2               FS cylinders per group
460
        """
461
        Partition = namedtuple(
462
            'Partition', 'size, offset, offseth, sizeh, fstype, frag, cpg')
463

    
464
        def setpsize(self, i, size):
465
            """Set size for partition i"""
466
            tmp = self.part[i]
467
            self.part[i] = self.Partition(
468
                size & 0xffffffff, tmp.offset, tmp.offseth, size >> 32,
469
                tmp.fstype, tmp.frag, tmp.cpg)
470

    
471
        def getpsize(self, i):
472
            """Get size for partition i"""
473
            return (self.part[i].sizeh << 32) + self.part[i].size
474

    
475
        def setpoffset(self, i, offset):
476
            """Set offset for partition i"""
477
            tmp = self.part[i]
478
            self.part[i] = self.Partition(
479
                tmp.size, offset & 0xffffffff, offset >> 32, tmp.sizeh,
480
                tmp.frag, tmp.cpg)
481

    
482
        def getpoffset(self, i):
483
            """Get offset for partition i"""
484
            return (self.part[i].offseth << 32) + self.part[i].offset
485

    
486
    format = "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
487

    
488
    def __init__(self, device):
489
        """Create a DiskLabel instance"""
490

    
491
        device.seek(BLOCKSIZE, os.SEEK_CUR)
492
        # The offset of the disklabel from the beginning of the partition is
493
        # one sector
494
        sector1 = device.read(BLOCKSIZE)
495

    
496
        d_ = OrderedDict()   # Off  Len    Content
497
        (d_["magic"],        # 0    4      Magic
498
         d_["dtype"],        # 4    2      Drive Type
499
         d_["subtype"],      # 6    2      Subtype
500
         d_["typename"],     # 8    16     Type Name
501
         d_["packname"],     # 24   16     Pack Identifier
502
         d_["secsize"],      # 32   4      Bytes per sector
503
         d_["nsectors"],     # 36   4      Data sectors per track
504
         d_["ntracks"],      # 40   4      Tracks per cylinder
505
         d_["ncylinders"],   # 44   4      Data cylinders per unit
506
         d_["secpercyl"],    # 48   4      Data sectors per cylinder
507
         d_["secperunit"],   # 52   4      Data sectors per unit
508
         d_["uid"],          # 56   8      Unique label identifier
509
         d_["acylinders"],   # 64   4      Alt cylinders per unit
510
         d_["bstarth"],      # 68   2      Start of useable region (high part)
511
         d_["bendh"],        # 70   2      Size of usable region (high part)
512
         d_["bstart"],       # 72   4      Start of useable region
513
         d_["bend"],         # 76   4      End of usable region
514
         d_["flags"],        # 80   4      Generic Flags
515
         d_["drivedata"],    # 84   5*4    Drive-type specific information
516
         d_["secperunith"],  # 104  2      Number of data sectors (high part)
517
         d_["version"],      # 106  2      Version
518
         d_["spare"],        # 108  4*4    Reserved for future use
519
         d_["magic2"],       # 124  4      Magic number
520
         d_["checksum"],     # 128  2      Xor of data including partitions
521
         d_["npartitions"],  # 130  2      Number of partitions in following
522
         d_["bbsize"],       # 132  4      size of boot area at sn0, bytes
523
         d_["sbsize"],       # 136  4      Max size of fs superblock, bytes
524
         ptable_raw          # 140  16*16  Partition Table
525
         ) = struct.unpack(self.format, sector1)
526

    
527
        assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
528
        self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
529
        self.field = d_
530

    
531
    def setdsize(self, dsize):
532
        """Set disk size"""
533
        self.field['secperunith'] = dsize >> 32
534
        self.field['secperunit'] = dsize & 0xffffffff
535

    
536
    def getdsize(self):
537
        """Get disk size"""
538
        return (self.field['secperunith'] << 32) + self.field['secperunit']
539

    
540
    dsize = property(getdsize, setdsize, None, "disk size")
541

    
542
    def setbstart(self, bstart):
543
        """Set start of useable region"""
544
        self.field['bstarth'] = bstart >> 32
545
        self.field['bstart'] = bstart & 0xffffffff
546

    
547
    def getbstart(self):
548
        """Get start of usable region"""
549
        return (self.field['bstarth'] << 32) + self.field['bstart']
550

    
551
    bstart = property(getbstart, setbstart, None, "usable region start")
552

    
553
    def setbend(self, bend):
554
        """Set size of useable region"""
555
        self.field['bendh'] = bend >> 32
556
        self.field['bend'] = bend & 0xffffffff
557

    
558
    def getbend(self):
559
        """Get size of usable region"""
560
        return (self.field['bendh'] << 32) + self.field['bend']
561

    
562
    bend = property(getbend, setbend, None, "usable region size")
563

    
564
    def enlarge(self, new_size):
565
        """Enlarge the disk and return the last usable sector"""
566

    
567
        assert new_size >= self.dsize, \
568
            "New size cannot be smaller that %s" % self.dsize
569

    
570
        # Fix the disklabel
571
        self.dsize = new_size
572
        self.field['ncylinders'] = self.dsize // (self.field['nsectors'] *
573
                                                  self.field['ntracks'])
574
        self.bend = (self.field['ncylinders'] * self.field['nsectors'] *
575
                     self.field['ntracks'])
576

    
577
        # Partition 'c' descriptes the entire disk
578
        self.ptable.setpsize(2, new_size)
579

    
580
        # Update the checksum
581
        self.field['checksum'] = self.compute_checksum()
582

    
583
        # bend is the size and not the end of the usable region. I named it
584
        # like this because this is how it is named in OpenBSD. To get the last
585
        # usable sector you need to reduce this value by one.
586
        return self.bend - 1
587

    
588
    def get_last_partition_id(self):
589
        """Returns the id of the last partition"""
590
        part = 0
591
        end = 0
592
        # Don't check partition 'c' which is the whole disk
593
        for i in filter(lambda x: x != 2, range(len(self.ptable.part))):
594
            curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
595
            if end < curr_end:
596
                end = curr_end
597
                part = i
598

    
599
        assert end > 0, "No partition found"
600

    
601
        return part
602

    
603
    def enlarge_last_partition(self):
604
        """Enlarge the last partition to cover up all the free space"""
605

    
606
        part_num = self.get_last_partition_id()
607

    
608
        end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
609

    
610
        assert end > 0, "No partition found"
611

    
612
        if self.ptable.part[part_num].fstype == 1:  # Swap partition.
613
            #TODO: Maybe create a warning?
614
            return
615

    
616
        if end > (self.bend - 1024):
617
            return
618

    
619
        self.ptable.setpsize(
620
            part_num, self.bend - self.ptable.getpoffset(part_num) - 1024)
621

    
622
        self.field['checksum'] = self.compute_checksum()
623

    
624
    def __str__(self):
625
        """Print the Disklabel"""
626

    
627
        title = "Disklabel"
628
        typename = self.field['typename'].strip('\x00').strip()
629
        packname = self.field['packname'].strip('\x00').strip()
630
        duid = "".join(x.encode('hex') for x in self.field['uid'])
631
        return \
632
            "%s\n%s\n" % (title, len(title) * "=") + \
633
            "Magic Number: 0x%(magic)x\n" \
634
            "Drive type: %(dtype)d\n" \
635
            "Subtype: %(subtype)d\n" % self.field + \
636
            "Typename: %s\n" % typename + \
637
            "Pack Identifier: %s\n" % packname + \
638
            "# of bytes per sector: %(secsize)d\n" \
639
            "# of data sectors per track: %(nsectors)d\n" \
640
            "# of tracks per cylinder: %(ntracks)d\n" \
641
            "# of data cylinders per unit: %(ncylinders)d\n" \
642
            "# of data sectors per cylinder: %(secpercyl)d\n" \
643
            "# of data sectors per unit: %(secperunit)d\n" % self.field + \
644
            "DUID: %s\n" % duid + \
645
            "Alt. cylinders per unit: %(acylinders)d\n" \
646
            "Start of useable region (high part): %(bstarth)d\n" \
647
            "Size of useable region (high part): %(bendh)d\n" \
648
            "Start of useable region: %(bstart)d\n" \
649
            "End of usable region: %(bend)d\n" \
650
            "Generic Flags: %(flags)r\n" \
651
            "Drive data: %(drivedata)r\n" \
652
            "Number of data sectors (high part): %(secperunith)d\n" \
653
            "Version: %(version)d\n" \
654
            "Reserved for future use: %(spare)r\n" \
655
            "The magic number again: 0x%(magic2)x\n" \
656
            "Checksum: %(checksum)d\n" \
657
            "Number of partitions: %(npartitions)d\n"  \
658
            "Size of boot aread at sn0: %(bbsize)d\n"  \
659
            "Max size of fs superblock: %(sbsize)d\n" % self.field + \
660
            "%s" % self.ptable
661

    
662

    
663
if __name__ == '__main__':
664

    
665
    usage = "Usage: %prog [options] <input_media>"
666
    parser = optparse.OptionParser(usage=usage)
667

    
668
    parser.add_option("-l", "--list", action="store_true", dest="list",
669
                      default=False,
670
                      help="list the disklabel on the specified media")
671
    parser.add_option("--get-last-partition", action="store_true",
672
                      dest="last_part", default=False,
673
                      help="print the label of the last partition")
674
    parser.add_option("--get-duid", action="store_true", dest="duid",
675
                      default=False,
676
                      help="print the disklabel unique identifier")
677
    parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
678
                      default=None, metavar="SIZE",
679
                      help="Enlarge the disk to this SIZE (in sectors)")
680
    parser.add_option(
681
        "-p", "--enlarge-partition", action="store_true",
682
        dest="enlarge_partition", default=False,
683
        help="Enlarge the last partition to cover up the free space")
684

    
685
    options, args = parser.parse_args(sys.argv[1:])
686

    
687
    if len(args) != 1:
688
        parser.error("Wrong number of arguments")
689

    
690
    disk = Disk(args[0])
691

    
692
    if options.list:
693
        print disk
694
        sys.exit(0)
695

    
696
    if options.duid:
697
        print "%s" % "".join(x.encode('hex') for x in disk.get_duid())
698
        sys.exit(0)
699

    
700
    if options.last_part:
701
        print "%c" % chr(ord('a') + disk.get_last_partition_id())
702

    
703
    if options.disk_size is not None:
704
        disk.enlarge(options.disk_size)
705

    
706
    if options.enlarge_partition:
707
        disk.enlarge_last_partition()
708

    
709
    disk.write()
710

    
711
sys.exit(0)
712

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