#!/usr/bin/env python # # -*- coding: utf-8 -*- # # Copyright (C) 2013 GRNET S.A. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. """This module provides the code for handling OpenBSD disklabels""" import struct import sys import cStringIO import optparse from collections import namedtuple BLOCKSIZE = 512 LABELSECTOR = 1 LABELOFFSET = 0 BBSIZE = 8192 # size of boot area with label SBSIZE = 8192 # max size of fs superblock DISKMAGIC = 0x82564557 class MBR(object): """Represents a Master Boot Record.""" class Partition(object): """Represents a partition entry in MBR""" format = " 1023: cylinder = 1023 head = 254 sector = 63 byte0 = head byte1 = (cylinder >> 2) & 0xC0 | sector byte2 = cylinder & 0xff return struct.pack('> 32, tmp.fstype, tmp.frag, tmp.cpg) def getpsize(self, i): return (self.part[i].sizeh << 32) + self.part[i].size def setpoffset(self, i, offset): """Set offset for partition i""" tmp = self.part[i] self.part[i] = self.Partition(tmp.size, offset & 0xffffffff, offset >> 32, tmp.sizeh, tmp.frag, tmp.cpg) def getpoffset(self, i): return (self.part[i].offseth << 32) + self.part[i].offset def __init__(self, disk): """Create a DiskLabel instance""" self.disk = disk self.part_num = None with open(disk, "rb") as d: sector0 = d.read(BLOCKSIZE) self.mbr = MBR(sector0) for i in range(4): if self.mbr.part[i].type == 0xa6: # OpenBSD type self.part_num = i break assert self.part_num is not None, "No OpenBSD partition found" d.seek(BLOCKSIZE * self.mbr.part[self.part_num].first_sector) part_sector0 = d.read(BLOCKSIZE) # The offset of the disklabel from the begining of the # partition is one sector part_sector1 = d.read(BLOCKSIZE) (self.magic, self.dtype, self.subtype, self.typename, self.packname, self.secsize, self.nsectors, self.ntracks, self.ncylinders, self.secpercyl, self.secperunit, self.uid, self.acylinders, self.bstarth, self.bendh, self.bstart, self.bend, self.flags, self.drivedata, self.secperunith, self.version, self.spare, self.magic2, self.checksum, self.npartitions, self.bbsize, self.sbsize, ptable_raw) = struct.unpack(self.format, part_sector1) assert self.magic == DISKMAGIC, "Disklabel is not valid" self.ptable = self.PartitionTable(ptable_raw, self.npartitions) def pack(self, checksum=None): return struct.pack(self.format, self.magic, self.dtype, self.subtype, self.typename, self.packname, self.secsize, self.nsectors, self.ntracks, self.ncylinders, self.secpercyl, self.secperunit, self.uid, self.acylinders, self.bstarth, self.bendh, self.bstart, self.bend, self.flags, self.drivedata, self.secperunith, self.version, self.spare, self.magic2, self.checksum if checksum is None else checksum, self.npartitions, self.bbsize, self.sbsize, self.ptable.pack() + ((364 - self.npartitions * 16) * '\x00')) def compute_checksum(self): """Compute the checksum of the disklabel""" raw = cStringIO.StringIO(self.pack(0)) checksum = 0 try: uint16 = raw.read(2) while uint16 != "": checksum ^= struct.unpack('> 32 self.secperunit = dsize & 0xffffffff def getdsize(self): """Get disk size""" return (self.secperunith << 32) + self.secperunit def setbstart(self, bstart): """Set start of useable region""" self.bstarth = bstart >> 32 self.bstart = bstart & 0xffffffff def getbstart(self): """Get start of usable region""" return (self.bstarth << 32) + self.bstart def setbend(self, bend): """Set end of useable region""" self.bendh = bend >> 32 self.bend = bend & 0xffffffff def getbend(self): return (self.bendh << 32) + self.bend def enlarge_disk(self, new_size): """Enlarge the size of the disk""" assert new_size >= self.secperunit, \ "New size cannot be smaller that %s" % self.secperunit # Fix the disklabel self.setdsize(new_size) self.ncylinders = self.getdsize() // (self.nsectors * self.ntracks) self.setbend(self.ncylinders * self.nsectors * self.ntracks) # Partition 'c' descriptes the entire disk self.ptable.setpsize(2, new_size) # Fix the MBR table start = self.mbr.part[self.part_num].first_sector self.mbr.part[self.part_num].sector_count = self.getbend() - start lba = self.getbend() - 1 cylinder = lba // (self.ntracks * self.nsectors) header = (lba // self.nsectors) % self.ntracks sector = (lba % self.nsectors) + 1 chs = MBR.Partition.pack_chs(cylinder, header, sector) self.mbr.part[self.part_num].end = chs self.checksum = self.compute_checksum() def write(self): """Write the disklabel back to the media""" with open(self.disk, 'rw+b') as d: d.write(self.mbr.pack()) d.seek((self.mbr.part[self.part_num].first_sector + 1) * BLOCKSIZE) d.write(self.pack()) def get_last_partition_id(self): """Returns the id of the last partition""" part = 0 end = 0 # Don't check partition 'c' which is the whole disk for i in filter(lambda x: x != 2, range(self.npartitions)): curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i) if end < curr_end: end = curr_end part = i assert end > 0, "No partition found" return part def enlarge_last_partition(self): """Enlarge the last partition to cover up all the free space""" part_num = self.get_last_partition_id() end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num) assert end > 0, "No partition found" if self.ptable.part[part_num].fstype == 1: # Swap partition. #TODO: Maybe create a warning? return if end > (self.getbend() - 1024): return self.ptable.setpsize( part_num, self.getbend() - self.ptable.getpoffset(part_num) - 1024) self.checksum = self.compute_checksum() def __str__(self): """Print the Disklabel""" title1 = "Master Boot Record" title2 = "Disklabel" return \ "%s\n%s\n%s\n" % (title1, len(title1) * "=", str(self.mbr)) + \ "%s\n%s\n" % (title2, len(title2) * "=") + \ "Magic Number: 0x%x\n" % self.magic + \ "Drive type: %d\n" % self.dtype + \ "Subtype: %d\n" % self.subtype + \ "Typename: %s\n" % self.typename + \ "Pack Identifier: %s\n" % self.packname + \ "Number of bytes per sector: %d\n" % self.secsize + \ "Number of data sectors per track: %d\n" % self.nsectors + \ "Number of tracks per cylinder: %d\n" % self.ntracks + \ "Number of data cylinders per unit: %d\n" % self.ncylinders + \ "Number of data sectors per cylinder: %d\n" % self.secpercyl + \ "Number of data sectors per unit: %d\n" % self.secperunit + \ "DUID: %s\n" % "".join(x.encode('hex') for x in self.uid) + \ "Alt. cylinders per unit: %d\n" % self.acylinders + \ "Start of useable region (high part): %d\n" % self.bstarth + \ "Size of useable region (high part): %d\n" % self.bendh + \ "Start of useable region: %d\n" % self.bstart + \ "End of usable region: %d\n" % self.bend + \ "Generic Flags: %r\n" % self.flags + \ "Drive data: %r\n" % self.drivedata + \ "Number of data sectors (high part): %d\n" % self.secperunith + \ "Version: %d\n" % self.version + \ "Reserved for future use: %r\n" % self.spare + \ "The magic number again: 0x%x\n" % self.magic2 + \ "Checksum: %d\n" % self.checksum + \ "Number of partitions: %d\n" % self.npartitions + \ "Size of boot aread at sn0: %d\n" % self.bbsize + \ "Max size of fs superblock: %d\n" % self.sbsize + \ "%s" % self.ptable if __name__ == '__main__': usage = "Usage: %prog [options] " parser = optparse.OptionParser(usage=usage) parser.add_option("-l", "--list", action="store_true", dest="list", default=False, help="list the disklabel on the specified media") parser.add_option("--print-last", action="store_true", dest="last_part", default=False, help="print the label of the last partition") parser.add_option("--print-last-linux", action="store_true", dest="last_linux", default=False, help="print the linux number for the last partition") parser.add_option("--print-duid", action="store_true", dest="duid", default=False, help="print the disklabel unique identifier") parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size", default=None, metavar="SIZE", help="Enlarge the disk to this SIZE (in sectors)") parser.add_option( "-p", "--enlarge-partition", action="store_true", dest="enlarge_partition", default=False, help="Enlarge the last partition to cover up the free space") options, args = parser.parse_args(sys.argv[1:]) if len(args) != 1: parser.error("Wrong number of arguments") disklabel = Disklabel(args[0]) if options.list: print disklabel sys.exit(0) if options.duid: print "%s" % "".join(x.encode('hex') for x in disklabel.uid) sys.exit(0) if options.last_part: print "%c" % chr(ord('a') + disklabel.get_last_partition_id()) if options.last_linux: part_id = disklabel.get_last_partition_id() # The linux kernel does not assign a partition for label 'c' that # describes the whole disk print part_id + (4 if part_id > 2 else 5) if options.disk_size is not None: disklabel.enlarge_disk(options.disk_size) if options.enlarge_partition: disklabel.enlarge_last_partition() disklabel.write() sys.exit(0) # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :