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