3 # -*- coding: utf-8 -*-
5 # Copyright (C) 2013 GRNET S.A.
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.
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.
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
22 """This module provides the code for handling OpenBSD disklabels"""
29 from collections import namedtuple
36 BBSIZE = 8192 # size of boot area with label
37 SBSIZE = 8192 # max size of fs superblock
39 DISKMAGIC = 0x82564557
43 """Represents a Master Boot Record."""
44 class Partition(object):
45 """Represents a partition entry in MBR"""
48 def __init__(self, raw_part):
49 """Create a Partition instance"""
57 ) = struct.unpack(self.format, raw_part)
60 """Pack the partition values into a binary string"""
61 return struct.pack(self.format,
71 """Returns the size of an MBR partition entry"""
72 return struct.calcsize(MBR.Partition.format)
75 start = self.unpack_chs(self.start)
76 end = self.unpack_chs(self.end)
77 return "%d %s %d %s %d %d" % (self.status, start, self.type, end,
78 self.first_sector, self.sector_count)
82 """Unpacks a CHS address string to a tuple."""
86 head = struct.unpack('<B', chs[0])[0]
87 sector = struct.unpack('<B', chs[1])[0] & 0x3f
88 cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
89 struct.unpack('<B', chs[2])[0]
91 return (cylinder, head, sector)
94 def pack_chs(cylinder, head, sector):
95 """Packs a CHS tuple to an address string."""
97 assert 1 <= sector <= 63
98 assert 0 <= head <= 255
101 # If the cylinders overflow then put the value (1023, 254, 63) to
102 # the tuple. At least this is what OpenBSD does.
109 byte1 = (cylinder >> 2) & 0xC0 | sector
110 byte2 = cylinder & 0xff
112 return struct.pack('<BBB', byte0, byte1, byte2)
114 format = "<444s2x16s16s16s16s2s"
116 Offset Length Contents
117 0 440(max. 446) code area
118 440 2(optional) disk signature
126 def __init__(self, block):
127 """Create an MBR instance"""
134 self.signature) = struct.unpack(self.format, block)
138 self.part[i] = self.Partition(raw_part[i])
142 """Return the size of a Master Boot Record."""
143 return struct.calcsize(MBR.format)
146 """Pack an MBR to a binary string."""
147 return struct.pack(self.format,
158 ret += "Partition %d: %s\n" % (i, self.part[i])
159 ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
160 hex(ord(self.signature[1])))
165 """Represents an OpenBSD Disklabel"""
166 format = "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
168 Offset Length Contents
173 24 16 Pack Identifier
174 32 4 Bytes per sector
175 36 4 Data sectors per track
176 40 4 Tracks per cilinder
177 44 4 Data cylinders per unit
178 48 4 Data sectors per cylynder
179 52 4 Data sectors per unit
180 56 8 Unique label identifier
181 64 4 Alt cylinders per unit
182 68 2 Start of useable region (high part)
183 70 2 Size of usable region (high part)
184 72 4 Start of useable region
185 76 4 End of usable region
187 84 5*4 Drive-type specific information
188 104 2 Number of data sectors (high part)
190 108 4*4 Reserved for future use
192 128 2 Xor of data Inclu. partitions
193 130 2 Number of partitions in following
194 132 4 size of boot area at sn0, bytes
195 136 4 Max size of fs superblock, bytes
196 140 16*16 Partition Table
199 class PartitionTable:
200 """Reprepsents an OpenBSD Partition Table"""
204 Offset Length Contents
205 0 4 Number of sectors in the partition
207 8 2 Starting sector (high part)
208 10 2 Number of sectors (high part)
210 13 1 Filesystem Fragment per block
211 14 2 FS cylinders per group
214 Partition = namedtuple(
215 'Partition', 'size, offset, offseth, sizeh, fstype, frag, cpg')
217 def __init__(self, ptable, pnumber):
218 """Create a Partition Table instance"""
221 size = struct.calcsize(self.format)
223 raw = cStringIO.StringIO(ptable)
225 for i in range(pnumber):
227 *struct.unpack(self.format, raw.read(size)))
233 """Print the Partition table"""
235 for i in range(len(self.part)):
236 val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
240 """Packs the partition table into a binary string."""
242 for i in range(len(self.part)):
243 ret += struct.pack(self.format,
246 self.part[i].offseth,
253 def setpsize(self, i, size):
254 """Set size for partition i"""
256 self.part[i] = self.Partition(size & 0xffffffff, tmp.offset,
257 tmp.offseth, size >> 32, tmp.fstype,
260 def getpsize(self, i):
261 return (self.part[i].sizeh << 32) + self.part[i].size
263 def setpoffset(self, i, offset):
264 """Set offset for partition i"""
266 self.part[i] = self.Partition(tmp.size, offset & 0xffffffff,
267 offset >> 32, tmp.sizeh, tmp.frag,
270 def getpoffset(self, i):
271 return (self.part[i].offseth << 32) + self.part[i].offset
273 def __init__(self, disk):
274 """Create a DiskLabel instance"""
278 with open(disk, "rb") as d:
279 sector0 = d.read(BLOCKSIZE)
280 self.mbr = MBR(sector0)
283 if self.mbr.part[i].type == 0xa6: # OpenBSD type
287 assert self.part_num is not None, "No OpenBSD partition found"
289 d.seek(BLOCKSIZE * self.mbr.part[self.part_num].first_sector)
290 part_sector0 = d.read(BLOCKSIZE)
291 # The offset of the disklabel from the begining of the
292 # partition is one sector
293 part_sector1 = d.read(BLOCKSIZE)
322 ptable_raw) = struct.unpack(self.format, part_sector1)
324 assert self.magic == DISKMAGIC, "Disklabel is not valid"
326 self.ptable = self.PartitionTable(ptable_raw, self.npartitions)
328 def pack(self, checksum=None):
329 return struct.pack(self.format,
353 self.checksum if checksum is None else checksum,
358 ((364 - self.npartitions * 16) * '\x00'))
360 def compute_checksum(self):
361 """Compute the checksum of the disklabel"""
363 raw = cStringIO.StringIO(self.pack(0))
368 checksum ^= struct.unpack('<H', uint16)[0]
375 def setdsize(self, dsize):
377 self.secperunith = dsize >> 32
378 self.secperunit = dsize & 0xffffffff
382 return (self.secperunith << 32) + self.secperunit
384 def setbstart(self, bstart):
385 """Set start of useable region"""
386 self.bstarth = bstart >> 32
387 self.bstart = bstart & 0xffffffff
390 """Get start of usable region"""
391 return (self.bstarth << 32) + self.bstart
393 def setbend(self, bend):
394 """Set end of useable region"""
395 self.bendh = bend >> 32
396 self.bend = bend & 0xffffffff
399 return (self.bendh << 32) + self.bend
401 def enlarge_disk(self, new_size):
402 """Enlarge the size of the disk"""
404 assert new_size >= self.secperunit, \
405 "New size cannot be smaller that %s" % self.secperunit
408 self.setdsize(new_size)
409 self.ncylinders = self.getdsize() // (self.nsectors * self.ntracks)
410 self.setbend(self.ncylinders * self.nsectors * self.ntracks)
412 # Partition 'c' descriptes the entire disk
413 self.ptable.setpsize(2, new_size)
416 start = self.mbr.part[self.part_num].first_sector
417 self.mbr.part[self.part_num].sector_count = self.getbend() - start
419 lba = self.getbend() - 1
420 cylinder = lba // (self.ntracks * self.nsectors)
421 header = (lba // self.nsectors) % self.ntracks
422 sector = (lba % self.nsectors) + 1
423 chs = MBR.Partition.pack_chs(cylinder, header, sector)
424 self.mbr.part[self.part_num].end = chs
426 self.checksum = self.compute_checksum()
429 """Write the disklabel back to the media"""
430 with open(self.disk, 'rw+b') as d:
431 d.write(self.mbr.pack())
433 d.seek((self.mbr.part[self.part_num].first_sector + 1) * BLOCKSIZE)
436 def get_last_partition_id(self):
437 """Returns the id of the last partition"""
440 # Don't check partition 'c' which is the whole disk
441 for i in filter(lambda x: x != 2, range(self.npartitions)):
442 curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
447 assert end > 0, "No partition found"
451 def enlarge_last_partition(self):
452 """Enlarge the last partition to cover up all the free space"""
454 part_num = self.get_last_partition_id()
456 end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
458 assert end > 0, "No partition found"
460 if self.ptable.part[part_num].fstype == 1: # Swap partition.
461 #TODO: Maybe create a warning?
464 if end > (self.getbend() - 1024):
467 self.ptable.setpsize(
468 part_num, self.getbend() - self.ptable.getpoffset(part_num) - 1024)
470 self.checksum = self.compute_checksum()
473 """Print the Disklabel"""
474 title1 = "Master Boot Record"
478 "%s\n%s\n%s\n" % (title1, len(title1) * "=", str(self.mbr)) + \
479 "%s\n%s\n" % (title2, len(title2) * "=") + \
480 "Magic Number: 0x%x\n" % self.magic + \
481 "Drive type: %d\n" % self.dtype + \
482 "Subtype: %d\n" % self.subtype + \
483 "Typename: %s\n" % self.typename.strip('\x00').strip() + \
484 "Pack Identifier: %s\n" % self.packname.strip('\x00').strip() + \
485 "Number of bytes per sector: %d\n" % self.secsize + \
486 "Number of data sectors per track: %d\n" % self.nsectors + \
487 "Number of tracks per cylinder: %d\n" % self.ntracks + \
488 "Number of data cylinders per unit: %d\n" % self.ncylinders + \
489 "Number of data sectors per cylinder: %d\n" % self.secpercyl + \
490 "Number of data sectors per unit: %d\n" % self.secperunit + \
491 "DUID: %s\n" % "".join(x.encode('hex') for x in self.uid) + \
492 "Alt. cylinders per unit: %d\n" % self.acylinders + \
493 "Start of useable region (high part): %d\n" % self.bstarth + \
494 "Size of useable region (high part): %d\n" % self.bendh + \
495 "Start of useable region: %d\n" % self.bstart + \
496 "End of usable region: %d\n" % self.bend + \
497 "Generic Flags: %r\n" % self.flags + \
498 "Drive data: %r\n" % self.drivedata + \
499 "Number of data sectors (high part): %d\n" % self.secperunith + \
500 "Version: %d\n" % self.version + \
501 "Reserved for future use: %r\n" % self.spare + \
502 "The magic number again: 0x%x\n" % self.magic2 + \
503 "Checksum: %d\n" % self.checksum + \
504 "Number of partitions: %d\n" % self.npartitions + \
505 "Size of boot aread at sn0: %d\n" % self.bbsize + \
506 "Max size of fs superblock: %d\n" % self.sbsize + \
510 if __name__ == '__main__':
512 usage = "Usage: %prog [options] <input_media>"
513 parser = optparse.OptionParser(usage=usage)
515 parser.add_option("-l", "--list", action="store_true", dest="list",
517 help="list the disklabel on the specified media")
518 parser.add_option("--get-last-partition", action="store_true",
519 dest="last_part", default=False,
520 help="print the label of the last partition")
521 parser.add_option("--get-duid", action="store_true", dest="duid",
523 help="print the disklabel unique identifier")
524 parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
525 default=None, metavar="SIZE",
526 help="Enlarge the disk to this SIZE (in sectors)")
528 "-p", "--enlarge-partition", action="store_true",
529 dest="enlarge_partition", default=False,
530 help="Enlarge the last partition to cover up the free space")
532 options, args = parser.parse_args(sys.argv[1:])
535 parser.error("Wrong number of arguments")
537 disklabel = Disklabel(args[0])
544 print "%s" % "".join(x.encode('hex') for x in disklabel.uid)
547 if options.last_part:
548 print "%c" % chr(ord('a') + disklabel.get_last_partition_id())
550 if options.disk_size is not None:
551 disklabel.enlarge_disk(options.disk_size)
553 if options.enlarge_partition:
554 disklabel.enlarge_last_partition()
560 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :