#!/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 BSD disklabels""" import struct import sys import os import cStringIO import optparse import abc from collections import namedtuple from collections import OrderedDict 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""" fmt = " 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): """Get size for partition 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): """Get offset for partition i""" return (self.part[i].offseth << 32) + self.part[i].offset @property def fmt(self): return "> 32 self.field['secperunit'] = dsize & 0xffffffff def getdsize(self): """Get disk size""" return (self.field['secperunith'] << 32) + self.field['secperunit'] dsize = property(getdsize, setdsize, None, "disk size") def setbstart(self, bstart): """Set start of useable region""" self.field['bstarth'] = bstart >> 32 self.field['bstart'] = bstart & 0xffffffff def getbstart(self): """Get start of useable region""" return (self.field['bstarth'] << 32) + self.field['bstart'] bstart = property(getbstart, setbstart, None, "start of useable region") def setbend(self, bend): """Set end of useable region""" self.field['bendh'] = bend >> 32 self.field['bend'] = bend & 0xffffffff def getbend(self): """Get end of useable region""" return (self.field['bendh'] << 32) + self.field['bend'] bend = property(getbend, setbend, None, "end of useable region") def enlarge(self, new_size): """Enlarge the disk and return the last useable sector""" assert new_size >= self.dsize, \ "New size cannot be smaller that %d" % self.dsize # Fix the disklabel self.dsize = new_size self.field['ncylinders'] = self.dsize // (self.field['nsectors'] * self.field['ntracks']) self.bend = (self.field['ncylinders'] * self.field['nsectors'] * self.field['ntracks']) # Partition 'c' describes the entire disk self.ptable.setpsize(2, new_size) # Update the checksum self.field['checksum'] = self.compute_checksum() # The last useable sector is the end of the useable region minus one return self.bend - 1 def get_last_partition_id(self): """Returns the id of the last partition""" end = 0 # Don't check partition 'c' which is the whole disk for i in [n for n in range(len(self.ptable.part)) if n != 2]: 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.bend - 1024): return self.ptable.setpsize( part_num, self.bend - self.ptable.getpoffset(part_num) - 1024) self.field['checksum'] = self.compute_checksum() def __str__(self): """Print the Disklabel""" # Those values may contain null bytes typename = self.field['typename'].strip('\x00').strip() packname = self.field['packname'].strip('\x00').strip() duid = "".join(x.encode('hex') for x in self.field['uid']) title = "Disklabel" return \ "%s\n%s\n" % (title, len(title) * "=") + \ "Magic Number: 0x%(magic)x\n" \ "Drive type: %(dtype)d\n" \ "Subtype: %(subtype)d\n" % self.field + \ "Typename: %s\n" % typename + \ "Pack Identifier: %s\n" % packname + \ "# of bytes per sector: %(secsize)d\n" \ "# of data sectors per track: %(nsectors)d\n" \ "# of tracks per cylinder: %(ntracks)d\n" \ "# of data cylinders per unit: %(ncylinders)d\n" \ "# of data sectors per cylinder: %(secpercyl)d\n" \ "# of data sectors per unit: %(secperunit)d\n" % self.field + \ "DUID: %s\n" % duid + \ "Alt. cylinders per unit: %(acylinders)d\n" \ "Start of useable region (high part): %(bstarth)d\n" \ "Size of useable region (high part): %(bendh)d\n" \ "Start of useable region: %(bstart)d\n" \ "End of useable region: %(bend)d\n" \ "Generic Flags: %(flags)r\n" \ "Drive data: %(drivedata)r\n" \ "Number of data sectors (high part): %(secperunith)d\n" \ "Version: %(version)d\n" \ "Reserved for future use: %(spare)r\n" \ "The magic number again: 0x%(magic2)x\n" \ "Checksum: %(checksum)d\n" \ "Number of partitions: %(npartitions)d\n" \ "Size of boot area at sn0: %(bbsize)d\n" \ "Max size of fs superblock: %(sbsize)d\n" % self.field + \ "%s" % self.ptable def main(): """Main entry point""" 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("--get-last-partition", action="store_true", dest="last_part", default=False, help="print the label of the last partition") parser.add_option( "--get-duid", action="store_true", dest="duid", default=False, help="print the Disklabel Unique Identifier (OpenBSD only)") 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") disk = Disk(args[0]) if options.list: print disk return 0 if options.duid: print "%s" % "".join(x.encode('hex') for x in disk.get_duid()) return 0 if options.last_part: print "%c" % chr(ord('a') + disk.get_last_partition_id()) if options.disk_size is not None: disk.enlarge(options.disk_size) if options.enlarge_partition: disk.enlarge_last_partition() disk.write() return 0 if __name__ == '__main__': sys.exit(main()) # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :