2 # -*- coding: utf-8 -*-
4 # Copyright (C) 2013 GRNET S.A.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 """This module provides the code for handling OpenBSD disklabels"""
28 from collections import namedtuple
35 BBSIZE = 8192 # size of boot area with label
36 SBSIZE = 8192 # max size of fs superblock
38 DISKMAGIC = 0x82564557
42 """Represents a Master Boot Record."""
43 class Partition(object):
44 """Represents a partition entry in MBR"""
47 def __init__(self, raw_part):
48 """Create a Partition instance"""
56 ) = struct.unpack(self.format, raw_part)
59 """Pack the partition values into a binary string"""
60 return struct.pack(self.format,
70 """Returns the size of an MBR partition entry"""
71 return struct.calcsize(MBR.Partition.format)
74 start = self.unpack_chs(self.start)
75 end = self.unpack_chs(self.end)
76 return "%d %s %d %s %d %d" % (self.status, start, self.type, end,
77 self.first_sector, self.sector_count)
81 """Unpacks a CHS address string to a tuple."""
85 head = struct.unpack('<B', chs[0])[0]
86 sector = struct.unpack('<B', chs[1])[0] & 0x3f
87 cylinder = (struct.unpack('<B', chs[1])[0] & 0xC0) << 2 | \
88 struct.unpack('<B', chs[2])[0]
90 return (cylinder, head, sector)
93 def pack_chs(cylinder, head, sector):
94 """Packs a CHS tuple to an address string."""
96 assert 1 <= sector <= 63
97 assert 0 <= cylinder <= 1023
98 assert 0 <= head <= 255
101 byte1 = (cylinder >> 2) & 0xC0 | sector
102 byte2 = cylinder & 0xff
104 return struct.pack('<BBB', byte0, byte1, byte2)
106 format = "<444s2x16s16s16s16s2s"
108 Offset Length Contents
109 0 440(max. 446) code area
110 440 2(optional) disk signature
118 def __init__(self, block):
119 """Create an MBR instance"""
126 self.signature) = struct.unpack(self.format, block)
130 self.part[i] = self.Partition(raw_part[i])
134 """Return the size of a Master Boot Record."""
135 return struct.calcsize(MBR.format)
138 """Pack an MBR to a binary string."""
139 return struct.pack(self.format,
150 ret += "Partition %d: %s\n" % (i, self.part[i])
151 ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
152 hex(ord(self.signature[1])))
157 """Represents an OpenBSD Disklabel"""
158 format = "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
160 Offset Length Contents
165 24 16 Pack Identifier
166 32 4 Bytes per sector
167 36 4 Data sectors per track
168 40 4 Tracks per cilinder
169 44 4 Data cylinders per unit
170 48 4 Data sectors per cylynder
171 52 4 Data sectors per unit
172 56 8 Unique label identifier
173 64 4 Alt cylinders per unit
174 68 2 Start of useable region (high part)
175 70 2 Size of usable region (high part)
176 72 4 Start of useable region
177 76 4 End of usable region
179 84 5*4 Drive-type specific information
180 104 2 Number of data sectors (high part)
182 108 4*4 Reserved for future use
184 128 2 Xor of data Inclu. partitions
185 130 2 Number of partitions in following
186 132 4 size of boot area at sn0, bytes
187 136 4 Max size of fs superblock, bytes
188 140 16*16 Partition Table
191 class PartitionTable:
192 """Reprepsents an OpenBSD Partition Table"""
196 Offset Length Contents
197 0 4 Number of sectors in the partition
199 8 2 Starting sector (high part)
200 10 2 Number of sectors (high part)
202 13 1 Filesystem Fragment per block
203 14 2 FS cylinders per group
206 Partition = namedtuple(
207 'Partition', 'size, offset, offseth, sizeh, fstype, frag, cpg')
209 def __init__(self, ptable, pnumber):
210 """Create a Partition Table instance"""
213 size = struct.calcsize(self.format)
215 raw = cStringIO.StringIO(ptable)
217 for i in range(pnumber):
219 *struct.unpack(self.format, raw.read(size)))
225 """Print the Partition table"""
227 for i in range(len(self.part)):
228 val = "%s%s\n" % (val, str(self.part[i]))
232 """Packs the partition table into a binary string."""
234 for i in range(len(self.part)):
235 ret += struct.pack(self.format,
238 self.part[i].offseth,
245 def setpsize(self, i, size):
246 """Set size for partition i"""
248 self.part[i] = self.Partition(size & 0xffffffff, tmp.offset,
249 tmp.offseth, size >> 32, tmp.fstype,
252 def getpsize(self, i):
253 return (self.part[i].sizeh << 32) + self.part[i].size
255 def setpoffset(self, i, offset):
256 """Set offset for partition i"""
258 self.part[i] = self.Partition(tmp.size, offset & 0xffffffff,
259 offset >> 32, tmp.sizeh, tmp.frag,
262 def getpoffset(self, i):
263 return (self.part[i].offseth << 32) + self.part[i].offset
265 def __init__(self, disk):
266 """Create a DiskLabel instance"""
270 with open(disk, "rb") as d:
271 sector0 = d.read(BLOCKSIZE)
272 self.mbr = MBR(sector0)
275 if self.mbr.part[i].type == 0xa6: # OpenBSD type
279 assert self.part_num is not None, "No OpenBSD partition found"
281 d.seek(BLOCKSIZE * self.mbr.part[self.part_num].first_sector)
282 part_sector0 = d.read(BLOCKSIZE)
283 # The offset of the disklabel from the begining of the
284 # partition is one sector
285 part_sector1 = d.read(BLOCKSIZE)
314 ptable_raw) = struct.unpack(self.format, part_sector1)
316 assert self.magic == DISKMAGIC, "Disklabel is not valid"
318 self.ptable = self.PartitionTable(ptable_raw, self.npartitions)
320 def pack(self, checksum=None):
321 return struct.pack(self.format,
345 self.checksum if checksum is None else checksum,
350 ((364 - self.npartitions*16) * '\x00'))
352 def compute_checksum(self):
353 """Compute the checksum of the disklabel"""
355 raw = cStringIO.StringIO(self.pack(0))
360 checksum ^= struct.unpack('<H', uint16)[0]
367 def setdsize(self, dsize):
369 self.secperunith = dsize >> 32
370 self.secperunit = dsize & 0xffffffff
374 return (self.secperunith << 32) + self.secperunit
376 def setbstart(self, bstart):
377 """Set start of useable region"""
378 self.bstarth = bstart >> 32
379 self.bstart = bstart & 0xffffffff
382 """Get start of usable region"""
383 return (self.bstarth << 32) + self.bstart
385 def setbend(self, bend):
386 """Set end of useable region"""
387 self.bendh = bend >> 32
388 self.bend = bend & 0xffffffff
391 return (self.bendh << 32) + self.bend
393 def enlarge_disk(self, new_size):
394 """Enlarge the size of the disk"""
396 assert new_size >= self.secperunit, \
397 "New size cannot be smaller that %s" % self.secperunit
400 self.setdsize(new_size)
401 self.ncylinders = self.getdsize() // (self.nsectors * self.ntracks)
402 self.setbend(self.ncylinders * self.nsectors * self.ntracks)
404 # Partition 'c' descriptes the entire disk
405 self.ptable.setpsize(2, new_size)
408 start = self.mbr.part[self.part_num].first_sector
409 self.mbr.part[self.part_num].sector_count = self.getbend() - start
411 lba = self.getbend() - 1
412 cylinder = lba // (self.ntracks * self.nsectors)
413 header = (lba // self.nsectors) % self.ntracks
414 sector = (lba % self.nsectors) + 1
415 chs = MBR.Partition.pack_chs(cylinder, header, sector)
416 self.mbr.part[self.part_num].end = chs
418 self.checksum = self.compute_checksum()
421 """Write the disklabel back to the media"""
422 with open(self.disk, 'rw+b') as d:
423 d.write(self.mbr.pack())
425 d.seek((self.mbr.part[self.part_num].first_sector + 1) * BLOCKSIZE)
428 def get_last_partition_id(self):
429 """Returns the id of the last partition"""
432 # Don't check partition 'c' which is the whole disk
433 for i in filter(lambda x: x != 2, range(self.npartitions)):
434 curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
439 assert end > 0, "No partition found"
443 def enlarge_last_partition(self):
444 """Enlarge the last partition to cover up all the free space"""
446 part_num = self.get_last_partition_id()
448 end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
450 assert end > 0, "No partition found"
452 if self.ptable.part[part_num].fstype == 1: # Swap partition.
453 #TODO: Maybe create a warning?
456 if end > (self.getbend() - 1024):
459 self.ptable.setpsize(
460 part_num, self.getbend() - self.ptable.getpoffset(part_num) - 1024)
462 self.checksum = self.compute_checksum()
465 """Print the Disklabel"""
466 title1 = "Master Boot Record"
470 "%s\n%s\n%s\n" % (title1, len(title1) * "=", str(self.mbr)) + \
471 "%s\n%s\n" % (title2, len(title2) * "=") + \
472 "Magic Number: 0x%x\n" % self.magic + \
473 "Drive type: %d\n" % self.dtype + \
474 "Subtype: %d\n" % self.subtype + \
475 "Typename: %s\n" % self.typename + \
476 "Pack Identifier: %s\n" % self.packname + \
477 "Number of bytes per sector: %d\n" % self.secsize + \
478 "Number of data sectors per track: %d\n" % self.nsectors + \
479 "Number of tracks per cylinder: %d\n" % self.ntracks + \
480 "Number of data cylinders per unit: %d\n" % self.ncylinders + \
481 "Number of data sectors per cylinder: %d\n" % self.secpercyl + \
482 "Number of data sectors per unit: %d\n" % self.secperunit + \
483 "DUID: %s\n" % "".join(x.encode('hex') for x in self.uid) + \
484 "Alt. cylinders per unit: %d\n" % self.acylinders + \
485 "Start of useable region (high part): %d\n" % self.bstarth + \
486 "Size of useable region (high part): %d\n" % self.bendh + \
487 "Start of useable region: %d\n" % self.bstart + \
488 "End of usable region: %d\n" % self.bend + \
489 "Generic Flags: %r\n" % self.flags + \
490 "Drive data: %r\n" % self.drivedata + \
491 "Number of data sectors (high part): %d\n" % self.secperunith + \
492 "Version: %d\n" % self.version + \
493 "Reserved for future use: %r\n" % self.spare + \
494 "The magic number again: 0x%x\n" % self.magic2 + \
495 "Checksum: %d\n" % self.checksum + \
496 "Number of partitions: %d\n" % self.npartitions + \
497 "Size of boot aread at sn0: %d\n" % self.bbsize + \
498 "Max size of fs superblock: %d\n" % self.sbsize + \
502 if __name__ == '__main__':
504 usage = "Usage: %prog [options] <input_media>"
505 parser = optparse.OptionParser(usage=usage)
507 parser.add_option("-l", "--list", action="store_true", dest="list",
509 help="list the disklabel on the specified media")
510 parser.add_option("--print-last", action="store_true", dest="last_part",
512 help="print the label of the last partition")
513 parser.add_option("--print-last-linux", action="store_true",
514 dest="last_linux", default=False,
515 help="print the linux number for the last partition")
516 parser.add_option("--print-duid", action="store_true", dest="duid",
518 help="print the disklabel unique identifier")
519 parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
520 default=None, metavar="SIZE",
521 help="Enlarge the disk to this SIZE (in sectors)")
523 "-p", "--enlarge-partition", action="store_true",
524 dest="enlarge_partition", default=False,
525 help="Enlarge the last partition to cover up the free space")
527 options, args = parser.parse_args(sys.argv[1:])
530 parser.error("Wrong number of arguments")
532 disklabel = Disklabel(args[0])
539 print "%s" % "".join(x.encode('hex') for x in disklabel.uid)
542 if options.last_part:
543 print "%c" % chr(ord('a') + disklabel.get_last_partition_id())
545 if options.last_linux:
546 part_id = disklabel.get_last_partition_id()
547 # The linux kernel does not assign a partition for label 'c' that
548 # describes the whole disk
549 print part_id + (4 if part_id > 2 else 5)
551 if options.disk_size is not None:
552 disklabel.enlarge_disk(options.disk_size)
554 if options.enlarge_partition:
555 disklabel.enlarge_last_partition()
561 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :