Print partition type in hex in disklabel.py
[snf-image] / snf-image-helper / disklabel.py
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             return "%02Xh %s %02Xh %s %d %d" % (self.status,
78                                                 self.unpack_chs(self.start),
79                                                 self.type,
80                                                 self.unpack_chs(self.end),
81                                                 self.first_sector,
82                                                 self.sector_count)
83
84         @staticmethod
85         def unpack_chs(chs):
86             """Unpacks a CHS address string to a tuple."""
87
88             assert len(chs) == 3
89
90             head = struct.unpack('<B', chs[0])[0]
91             sector = struct.unpack('<B', chs[1])[0] & 0x3f
92             cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
93                 struct.unpack('<B', chs[2])[0]
94
95             return (cylinder, head, sector)
96
97         @staticmethod
98         def pack_chs(cylinder, head, sector):
99             """Packs a CHS tuple to an address string."""
100
101             assert 1 <= sector <= 63
102             assert 0 <= head <= 255
103             assert 0 <= cylinder
104
105             # If the cylinders overflow then put the value (1023, 254, 63) to
106             # the tuple. At least this is what OpenBSD does.
107             if cylinder > 1023:
108                 cylinder = 1023
109                 head = 254
110                 sector = 63
111
112             byte0 = head
113             byte1 = (cylinder >> 2) & 0xC0 | sector
114             byte2 = cylinder & 0xff
115
116             return struct.pack('<BBB', byte0, byte1, byte2)
117
118     def __init__(self, block):
119         """Create an MBR instance"""
120
121         self.fmt = "<444s2x16s16s16s16s2s"
122         raw_part = {}     # Offset  Length          Contents
123         (self.code_area,  # 0       440(max. 446)   code area
124                           # 440     2(optional)     disk signature
125                           # 444     2               Usually nulls
126          raw_part[0],     # 446     16              Partition 0
127          raw_part[1],     # 462     16              Partition 1
128          raw_part[2],     # 478     16              Partition 2
129          raw_part[3],     # 494     16              Partition 3
130          self.signature   # 510     2               MBR signature
131          ) = struct.unpack(self.fmt, block)
132
133         self.part = {}
134         for i in range(4):
135             self.part[i] = self.Partition(raw_part[i])
136
137     def size(self):
138         """Return the size of a Master Boot Record."""
139         return struct.calcsize(self.fmt)
140
141     def pack(self):
142         """Pack an MBR to a binary string."""
143         return struct.pack(self.fmt,
144                            self.code_area,
145                            self.part[0].pack(),
146                            self.part[1].pack(),
147                            self.part[2].pack(),
148                            self.part[3].pack(),
149                            self.signature)
150
151     def __str__(self):
152         """Print the MBR"""
153         ret = ""
154         for i in range(4):
155             ret += "Partition %d: %s\n" % (i, self.part[i])
156         ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
157                                        hex(ord(self.signature[1])))
158         title = "Master Boot Record"
159         return "%s\n%s\n%s\n" % (title, len(title) * "=", ret)
160
161
162 class Disk(object):
163     """Represents a BSD Disk"""
164
165     def __init__(self, device):
166         """Create a Disk instance"""
167         self.device = device
168         self.part_num = None
169         self.disklabel = None
170
171         with open(device, "rb") as d:
172             sector0 = d.read(BLOCKSIZE)
173             self.mbr = MBR(sector0)
174
175             for i in range(4):
176                 ptype = self.mbr.part[i].type
177                 if ptype in (0xa5, 0xa6, 0xa9):
178                     d.seek(BLOCKSIZE * self.mbr.part[i].first_sector)
179                     self.part_num = i
180                     if ptype == 0xa5:  # FreeBSD
181                         self.disklabel = BSDDisklabel(d)
182                     elif ptype == 0xa6:  # OpenBSD
183                         self.disklabel = OpenBSDDisklabel(d)
184                     else:  # NetBSD
185                         self.disklabel = BSDDisklabel(d)
186                     break
187
188         assert self.disklabel is not None, "No *BSD partition found"
189
190     def write(self):
191         """Write the changes back to the media"""
192         with open(self.device, 'rw+b') as d:
193             d.write(self.mbr.pack())
194
195             d.seek(self.mbr.part[self.part_num].first_sector * BLOCKSIZE)
196             self.disklabel.write_to(d)
197
198     def __str__(self):
199         """Print the partitioning info of the Disk"""
200         return str(self.mbr) + str(self.disklabel)
201
202     def enlarge(self, new_size):
203         """Enlarge the disk and return the last usable sector"""
204
205         # Fix the disklabel
206         end = self.disklabel.enlarge(new_size)
207
208         # Fix the MBR
209         start = self.mbr.part[self.part_num].first_sector
210         self.mbr.part[self.part_num].sector_count = end - start + 1
211
212         ntracks = self.disklabel.field['ntracks']
213         nsectors = self.disklabel.field['nsectors']
214
215         cylinder = end // (ntracks * nsectors)
216         header = (end // nsectors) % ntracks
217         sector = (end % nsectors) + 1
218         chs = MBR.Partition.pack_chs(cylinder, header, sector)
219         self.mbr.part[self.part_num].end = chs
220
221     def enlarge_last_partition(self):
222         """Enlarge the last partition to cover up all the free space"""
223         self.disklabel.enlarge_last_partition()
224
225     def get_last_partition_id(self):
226         """Get the ID of the last partition"""
227         return self.disklabel.get_last_partition_id()
228
229     def get_duid(self):
230         """Get the Disklabel Unique Identifier (works only for OpenBSD)"""
231         if 'uid' in self.disklabel.field:
232             return self.disklabel.field['uid']
233
234         return ""
235
236
237 class DisklabelBase(object):
238     """Disklabel base class"""
239     __metaclass__ = abc.ABCMeta
240
241     def __init__(self, device):
242         """Create a Disklabel instance"""
243
244         # Subclasses need to overwrite this
245         self.field = None
246         self.ptable = None
247
248     @abc.abstractproperty
249     def fmt(self):
250         """Fields format string for the disklabel fields"""
251         pass
252
253     def pack(self, checksum=None):
254         """Return a binary copy of the Disklabel block"""
255
256         out = OrderedDict()
257         for k, v in self.field.iteritems():
258             out[k] = v
259
260         if checksum is not None:
261             out['checksum'] = checksum
262
263         return struct.pack(self.fmt, * out.values() + [self.ptable.pack()])
264
265     def compute_checksum(self):
266         """Compute the checksum of the disklabel"""
267
268         raw = cStringIO.StringIO(self.pack(0))
269         checksum = 0
270         try:
271             uint16 = raw.read(2)
272             while uint16 != "":
273                 checksum ^= struct.unpack('<H', uint16)[0]
274                 uint16 = raw.read(2)
275         finally:
276             raw.close()
277
278         return checksum
279
280     @abc.abstractmethod
281     def enlarge(self, new_size):
282         """Enlarge the disk and return the last usable sector"""
283         pass
284
285     def write_to(self, device):
286         """Write the disklabel to a device"""
287
288         # The disklabel starts at sector 1
289         device.seek(BLOCKSIZE, os.SEEK_CUR)
290         device.write(self.pack())
291
292     @abc.abstractmethod
293     def enlarge_last_partition(self):
294         """Enlarge the last partition to consume all the usable space"""
295         pass
296
297     @abc.abstractmethod
298     def get_last_partition_id(self):
299         """Get the ID of the last partition"""
300         pass
301
302     @abc.abstractmethod
303     def __str__(self):
304         """Print the Disklabel"""
305         pass
306
307
308 class PartitionTableBase(object):
309     """Base Class for disklabel partition tables"""
310     __metaclass__ = abc.ABCMeta
311
312     @abc.abstractproperty
313     def fmt(self):
314         """Partition fields format string"""
315         pass
316
317     @abc.abstractproperty
318     def fields(self):
319         """The partition fields"""
320         pass
321
322     def __init__(self, ptable, pnumber):
323         """Create a Partition Table instance"""
324
325         self.Partition = namedtuple('Partition', self.fields)
326         self.part = []
327
328         size = struct.calcsize(self.fmt)
329         raw = cStringIO.StringIO(ptable)
330         try:
331             for _ in xrange(pnumber):
332                 self.part.append(
333                     self.Partition(*struct.unpack(self.fmt, raw.read(size)))
334                     )
335         finally:
336             raw.close()
337
338     def __str__(self):
339         """Print the Partition table"""
340         val = ""
341         for i in xrange(len(self.part)):
342             val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
343         return val
344
345     def pack(self):
346         """Packs the partition table into a binary string."""
347         ret = ""
348         for i in xrange(len(self.part)):
349             ret += struct.pack(self.fmt, *self.part[i])
350         return ret + ((364 - len(self.part) * 16) * '\x00')
351
352
353 class BSDDisklabel(DisklabelBase):
354     """Represents an BSD Disklabel"""
355
356     class PartitionTable(PartitionTableBase):
357         """Represents a BSD Partition Table"""
358
359         @property
360         def fmt(self):
361             """Partition fields format string"""
362             return "<IIIBBH"
363
364         @property
365         def fields(self):
366             """The partition fields"""
367             return [    # Offset  Length Contents
368                 'size',     # 0       4      Number of sectors in partition
369                 'offset',   # 4       4      Starting sector of the partition
370                 'fsize',    # 8       4      File system basic fragment size
371                 'fstype',   # 12      1      File system type
372                 'frag',     # 13      1      File system fragments per block
373                 'cpg'       # 14      2      File system cylinders per group
374                 ]
375
376     @property
377     def fmt(self):
378         return "<IHH16s16sIIIIIIHHIHHHHIII20s20sIHHII364s"
379
380     def __init__(self, device):
381         """Create a BSD DiskLabel instance"""
382         super(BSDDisklabel, self).__init__(device)
383
384         # Disklabel starts at offset one
385         device.seek(BLOCKSIZE, os.SEEK_CUR)
386         sector1 = device.read(BLOCKSIZE)
387
388         d_ = OrderedDict()      # Off  Len    Content
389         (d_["magic"],           # 0    4      Magic
390          d_["dtype"],           # 4    2      Drive Type
391          d_["subtype"],         # 6    2      Subtype
392          d_["typename"],        # 8    16     Type Name
393          d_["packname"],        # 24   16     Pack Identifier
394          d_["secsize"],         # 32   4      Bytes per sector
395          d_["nsectors"],        # 36   4      Data sectors per track
396          d_["ntracks"],         # 40   4      Tracks per cylinder
397          d_["ncylinders"],      # 44   4      Data cylinders per unit
398          d_["secpercyl"],       # 48   4      Data sectors per cylinder
399          d_["secperunit"],      # 52   4      Data sectors per unit
400          d_["sparespertrack"],  # 56   2      Spare sectors per track
401          d_["sparespercyl"],    # 58   2      Spare sectors per cylinder
402          d_["acylinders"],      # 60   4      Alternative cylinders per unit
403          d_["rpm"],             # 64   2      Rotation Speed
404          d_["interleave"],      # 66   2      Hardware sector interleave
405          d_["trackskew"],       # 68   2      Sector 0 skew, per track
406          d_["cylskew"],         # 70   2      Sector 0 skew, per cylinder
407          d_["headswitch"],      # 72   4      Head switch time
408          d_["trkseek"],         # 76   4      Track-to-track seek
409          d_["flags"],           # 80   4      Generic Flags
410          d_["drivedata"],       # 84   5*4    Drive-type specific information
411          d_["spare"],           # 104  5*4    Reserved for future use
412          d_["magic2"],          # 124  4      Magic Number
413          d_["checksum"],        # 128  2      Xor of data including partitions
414          d_["npartitions"],     # 130  2      Number of partitions following
415          d_["bbsize"],          # 132  4      size of boot area at sn0, bytes
416          d_["sbsize"],          # 136  4      Max size of fs superblock, bytes
417          ptable_raw             # 140  16*16  Partition Table
418          ) = struct.unpack(self.fmt, sector1)
419
420         assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
421         self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
422         self.field = d_
423
424     def enlarge(self, new_size):
425         raise NotImplementedError
426
427     def enlarge_last_partition(self):
428         raise NotImplementedError
429
430     def get_last_partition_id(self):
431         raise NotImplementedError
432
433     def __str__(self):
434         """Print the Disklabel"""
435
436         title = "Disklabel"
437
438         # Those fields may contain null bytes
439         typename = self.field['typename'].strip('\x00').strip()
440         packname = self.field['packname'].strip('\x00').strip()
441
442         return \
443             "%s\n%s\n" % (title, len(title) * "=") + \
444             "Magic Number: 0x%(magic)x\n" \
445             "Drive type: %(dtype)d\n" \
446             "Subtype: %(subtype)d\n" % self.field + \
447             "Typename: %s\n" % typename + \
448             "Pack Identifier: %s\n" % packname + \
449             "# of bytes per sector: %(secsize)d\n" \
450             "# of data sectors per track: %(nsectors)d\n" \
451             "# of tracks per cylinder: %(ntracks)d\n" \
452             "# of data cylinders per unit: %(ncylinders)d\n" \
453             "# of data sectors per cylinder: %(secpercyl)d\n" \
454             "# of data sectors per unit: %(secperunit)d\n" \
455             "# of spare sectors per track: %(sparespertrack)d\n" \
456             "# of spare sectors per cylinder: %(sparespercyl)d\n" \
457             "Alt. cylinders per unit: %(acylinders)d\n" \
458             "Rotational speed: %(rpm)d\n" \
459             "Hardware sector interleave: %(interleave)d\n" \
460             "Sector 0 skew, per track: %(trackskew)d\n" \
461             "Sector 0 skew, per cylinder: %(cylskew)d\n" \
462             "Head switch time, usec: %(headswitch)d\n" \
463             "Track-to-track seek, usec: %(trkseek)d\n" \
464             "Generic Flags: %(flags)r\n" \
465             "Drive data: %(drivedata)r\n" \
466             "Reserved for future use: %(spare)r\n" \
467             "The magic number again: 0x%(magic2)x\n" \
468             "Checksum: %(checksum)d\n" \
469             "Number of partitions: %(npartitions)d\n"  \
470             "Size of boot aread at sn0: %(bbsize)d\n"  \
471             "Max size of fs superblock: %(sbsize)d\n" % self.field + \
472             "%s" % self.ptable
473
474
475 class OpenBSDDisklabel(DisklabelBase):
476     """Represents an OpenBSD Disklabel"""
477
478     class PartitionTable(PartitionTableBase):
479         """Reprepsents an OpenBSD Partition Table"""
480
481         @property
482         def fmt(self):
483             return "<IIHHBBH"
484
485         @property
486         def fields(self):
487             return [    # Offset  Length Contents
488                 'size',     # 0       4      Number of sectors in the partition
489                 'offset',   # 4       4      Starting sector of the partition
490                 'offseth',  # 8       2      Starting sector (high part)
491                 'sizeh',    # 10      2      Number of sectors (high part)
492                 'fstype',   # 12      1      Filesystem type
493                 'frag',     # 13      1      Filesystem Fragments per block
494                 'cpg'       # 14      2      FS cylinders per group
495                 ]
496
497         def setpsize(self, i, size):
498             """Set size for partition i"""
499             tmp = self.part[i]
500             self.part[i] = self.Partition(
501                 size & 0xffffffff, tmp.offset, tmp.offseth, size >> 32,
502                 tmp.fstype, tmp.frag, tmp.cpg)
503
504         def getpsize(self, i):
505             """Get size for partition i"""
506             return (self.part[i].sizeh << 32) + self.part[i].size
507
508         def setpoffset(self, i, offset):
509             """Set offset for partition i"""
510             tmp = self.part[i]
511             self.part[i] = self.Partition(
512                 tmp.size, offset & 0xffffffff, offset >> 32, tmp.sizeh,
513                 tmp.frag, tmp.cpg)
514
515         def getpoffset(self, i):
516             """Get offset for partition i"""
517             return (self.part[i].offseth << 32) + self.part[i].offset
518
519     @property
520     def fmt(self):
521         return "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
522
523     def __init__(self, device):
524         """Create a DiskLabel instance"""
525
526         super(OpenBSDDisklabel, self).__init__(device)
527
528         # Disklabel starts at offset one
529         device.seek(BLOCKSIZE, os.SEEK_CUR)
530         sector1 = device.read(BLOCKSIZE)
531
532         d_ = OrderedDict()   # Off  Len    Content
533         (d_["magic"],        # 0    4      Magic
534          d_["dtype"],        # 4    2      Drive Type
535          d_["subtype"],      # 6    2      Subtype
536          d_["typename"],     # 8    16     Type Name
537          d_["packname"],     # 24   16     Pack Identifier
538          d_["secsize"],      # 32   4      Bytes per sector
539          d_["nsectors"],     # 36   4      Data sectors per track
540          d_["ntracks"],      # 40   4      Tracks per cylinder
541          d_["ncylinders"],   # 44   4      Data cylinders per unit
542          d_["secpercyl"],    # 48   4      Data sectors per cylinder
543          d_["secperunit"],   # 52   4      Data sectors per unit
544          d_["uid"],          # 56   8      Unique label identifier
545          d_["acylinders"],   # 64   4      Alt cylinders per unit
546          d_["bstarth"],      # 68   2      Start of useable region (high part)
547          d_["bendh"],        # 70   2      Size of usable region (high part)
548          d_["bstart"],       # 72   4      Start of useable region
549          d_["bend"],         # 76   4      End of usable region
550          d_["flags"],        # 80   4      Generic Flags
551          d_["drivedata"],    # 84   5*4    Drive-type specific information
552          d_["secperunith"],  # 104  2      Number of data sectors (high part)
553          d_["version"],      # 106  2      Version
554          d_["spare"],        # 108  4*4    Reserved for future use
555          d_["magic2"],       # 124  4      Magic number
556          d_["checksum"],     # 128  2      Xor of data including partitions
557          d_["npartitions"],  # 130  2      Number of partitions in following
558          d_["bbsize"],       # 132  4      size of boot area at sn0, bytes
559          d_["sbsize"],       # 136  4      Max size of fs superblock, bytes
560          ptable_raw          # 140  16*16  Partition Table
561          ) = struct.unpack(self.fmt, sector1)
562
563         assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
564         self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
565         self.field = d_
566
567     def setdsize(self, dsize):
568         """Set disk size"""
569         self.field['secperunith'] = dsize >> 32
570         self.field['secperunit'] = dsize & 0xffffffff
571
572     def getdsize(self):
573         """Get disk size"""
574         return (self.field['secperunith'] << 32) + self.field['secperunit']
575
576     dsize = property(getdsize, setdsize, None, "disk size")
577
578     def setbstart(self, bstart):
579         """Set start of useable region"""
580         self.field['bstarth'] = bstart >> 32
581         self.field['bstart'] = bstart & 0xffffffff
582
583     def getbstart(self):
584         """Get start of usable region"""
585         return (self.field['bstarth'] << 32) + self.field['bstart']
586
587     bstart = property(getbstart, setbstart, None, "start of usable region")
588
589     def setbend(self, bend):
590         """Set end of useable region"""
591         self.field['bendh'] = bend >> 32
592         self.field['bend'] = bend & 0xffffffff
593
594     def getbend(self):
595         """Get end of usable region"""
596         return (self.field['bendh'] << 32) + self.field['bend']
597
598     bend = property(getbend, setbend, None, "end of usable region")
599
600     def enlarge(self, new_size):
601         """Enlarge the disk and return the last usable sector"""
602
603         assert new_size >= self.dsize, \
604             "New size cannot be smaller that %s" % self.dsize
605
606         # Fix the disklabel
607         self.dsize = new_size
608         self.field['ncylinders'] = self.dsize // (self.field['nsectors'] *
609                                                   self.field['ntracks'])
610         self.bend = (self.field['ncylinders'] * self.field['nsectors'] *
611                      self.field['ntracks'])
612
613         # Partition 'c' descriptes the entire disk
614         self.ptable.setpsize(2, new_size)
615
616         # Update the checksum
617         self.field['checksum'] = self.compute_checksum()
618
619         # The last usable sector is the end of the usable region minus one
620         return self.bend - 1
621
622     def get_last_partition_id(self):
623         """Returns the id of the last partition"""
624         part = 0
625         end = 0
626         # Don't check partition 'c' which is the whole disk
627         for i in filter(lambda x: x != 2, range(len(self.ptable.part))):
628             curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
629             if end < curr_end:
630                 end = curr_end
631                 part = i
632
633         assert end > 0, "No partition found"
634
635         return part
636
637     def enlarge_last_partition(self):
638         """Enlarge the last partition to cover up all the free space"""
639
640         part_num = self.get_last_partition_id()
641
642         end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
643
644         assert end > 0, "No partition found"
645
646         if self.ptable.part[part_num].fstype == 1:  # Swap partition.
647             #TODO: Maybe create a warning?
648             return
649
650         if end > (self.bend - 1024):
651             return
652
653         self.ptable.setpsize(
654             part_num, self.bend - self.ptable.getpoffset(part_num) - 1024)
655
656         self.field['checksum'] = self.compute_checksum()
657
658     def __str__(self):
659         """Print the Disklabel"""
660
661         # Those values may contain null bytes
662         typename = self.field['typename'].strip('\x00').strip()
663         packname = self.field['packname'].strip('\x00').strip()
664
665         duid = "".join(x.encode('hex') for x in self.field['uid'])
666
667         title = "Disklabel"
668         return \
669             "%s\n%s\n" % (title, len(title) * "=") + \
670             "Magic Number: 0x%(magic)x\n" \
671             "Drive type: %(dtype)d\n" \
672             "Subtype: %(subtype)d\n" % self.field + \
673             "Typename: %s\n" % typename + \
674             "Pack Identifier: %s\n" % packname + \
675             "# of bytes per sector: %(secsize)d\n" \
676             "# of data sectors per track: %(nsectors)d\n" \
677             "# of tracks per cylinder: %(ntracks)d\n" \
678             "# of data cylinders per unit: %(ncylinders)d\n" \
679             "# of data sectors per cylinder: %(secpercyl)d\n" \
680             "# of data sectors per unit: %(secperunit)d\n" % self.field + \
681             "DUID: %s\n" % duid + \
682             "Alt. cylinders per unit: %(acylinders)d\n" \
683             "Start of useable region (high part): %(bstarth)d\n" \
684             "Size of useable region (high part): %(bendh)d\n" \
685             "Start of useable region: %(bstart)d\n" \
686             "End of usable region: %(bend)d\n" \
687             "Generic Flags: %(flags)r\n" \
688             "Drive data: %(drivedata)r\n" \
689             "Number of data sectors (high part): %(secperunith)d\n" \
690             "Version: %(version)d\n" \
691             "Reserved for future use: %(spare)r\n" \
692             "The magic number again: 0x%(magic2)x\n" \
693             "Checksum: %(checksum)d\n" \
694             "Number of partitions: %(npartitions)d\n"  \
695             "Size of boot aread at sn0: %(bbsize)d\n"  \
696             "Max size of fs superblock: %(sbsize)d\n" % self.field + \
697             "%s" % self.ptable
698
699
700 def main():
701     """Main entry point"""
702     usage = "Usage: %prog [options] <input_media>"
703     parser = optparse.OptionParser(usage=usage)
704
705     parser.add_option("-l", "--list", action="store_true", dest="list",
706                       default=False,
707                       help="list the disklabel on the specified media")
708     parser.add_option("--get-last-partition", action="store_true",
709                       dest="last_part", default=False,
710                       help="print the label of the last partition")
711     parser.add_option(
712         "--get-duid", action="store_true", dest="duid", default=False,
713         help="print the Disklabel Unique Identifier (OpenBSD only)")
714     parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
715                       default=None, metavar="SIZE",
716                       help="Enlarge the disk to this SIZE (in sectors)")
717     parser.add_option(
718         "-p", "--enlarge-partition", action="store_true",
719         dest="enlarge_partition", default=False,
720         help="Enlarge the last partition to cover up the free space")
721
722     options, args = parser.parse_args(sys.argv[1:])
723
724     if len(args) != 1:
725         parser.error("Wrong number of arguments")
726
727     disk = Disk(args[0])
728
729     if options.list:
730         print disk
731         return 0
732
733     if options.duid:
734         print "%s" % "".join(x.encode('hex') for x in disk.get_duid())
735         return 0
736
737     if options.last_part:
738         print "%c" % chr(ord('a') + disk.get_last_partition_id())
739
740     if options.disk_size is not None:
741         disk.enlarge(options.disk_size)
742
743     if options.enlarge_partition:
744         disk.enlarge_last_partition()
745
746     disk.write()
747     return 0
748
749
750 if __name__ == '__main__':
751     sys.exit(main())
752
753 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :