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 <= head <= 255
100 # If the cylinders overflow then put the value (1023, 254, 63) to
101 # the tuple. At least this is what OpenBSD does.
108 byte1 = (cylinder >> 2) & 0xC0 | sector
109 byte2 = cylinder & 0xff
111 return struct.pack('<BBB', byte0, byte1, byte2)
113 format = "<444s2x16s16s16s16s2s"
115 Offset Length Contents
116 0 440(max. 446) code area
117 440 2(optional) disk signature
125 def __init__(self, block):
126 """Create an MBR instance"""
133 self.signature) = struct.unpack(self.format, block)
137 self.part[i] = self.Partition(raw_part[i])
141 """Return the size of a Master Boot Record."""
142 return struct.calcsize(MBR.format)
145 """Pack an MBR to a binary string."""
146 return struct.pack(self.format,
157 ret += "Partition %d: %s\n" % (i, self.part[i])
158 ret += "Signature: %s %s\n" % (hex(ord(self.signature[0])),
159 hex(ord(self.signature[1])))
164 """Represents an OpenBSD Disklabel"""
165 format = "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
167 Offset Length Contents
172 24 16 Pack Identifier
173 32 4 Bytes per sector
174 36 4 Data sectors per track
175 40 4 Tracks per cilinder
176 44 4 Data cylinders per unit
177 48 4 Data sectors per cylynder
178 52 4 Data sectors per unit
179 56 8 Unique label identifier
180 64 4 Alt cylinders per unit
181 68 2 Start of useable region (high part)
182 70 2 Size of usable region (high part)
183 72 4 Start of useable region
184 76 4 End of usable region
186 84 5*4 Drive-type specific information
187 104 2 Number of data sectors (high part)
189 108 4*4 Reserved for future use
191 128 2 Xor of data Inclu. partitions
192 130 2 Number of partitions in following
193 132 4 size of boot area at sn0, bytes
194 136 4 Max size of fs superblock, bytes
195 140 16*16 Partition Table
198 class PartitionTable:
199 """Reprepsents an OpenBSD Partition Table"""
203 Offset Length Contents
204 0 4 Number of sectors in the partition
206 8 2 Starting sector (high part)
207 10 2 Number of sectors (high part)
209 13 1 Filesystem Fragment per block
210 14 2 FS cylinders per group
213 Partition = namedtuple(
214 'Partition', 'size, offset, offseth, sizeh, fstype, frag, cpg')
216 def __init__(self, ptable, pnumber):
217 """Create a Partition Table instance"""
220 size = struct.calcsize(self.format)
222 raw = cStringIO.StringIO(ptable)
224 for i in range(pnumber):
226 *struct.unpack(self.format, raw.read(size)))
232 """Print the Partition table"""
234 for i in range(len(self.part)):
235 val = "%s%s\n" % (val, str(self.part[i]))
239 """Packs the partition table into a binary string."""
241 for i in range(len(self.part)):
242 ret += struct.pack(self.format,
245 self.part[i].offseth,
252 def setpsize(self, i, size):
253 """Set size for partition i"""
255 self.part[i] = self.Partition(size & 0xffffffff, tmp.offset,
256 tmp.offseth, size >> 32, tmp.fstype,
259 def getpsize(self, i):
260 return (self.part[i].sizeh << 32) + self.part[i].size
262 def setpoffset(self, i, offset):
263 """Set offset for partition i"""
265 self.part[i] = self.Partition(tmp.size, offset & 0xffffffff,
266 offset >> 32, tmp.sizeh, tmp.frag,
269 def getpoffset(self, i):
270 return (self.part[i].offseth << 32) + self.part[i].offset
272 def __init__(self, disk):
273 """Create a DiskLabel instance"""
277 with open(disk, "rb") as d:
278 sector0 = d.read(BLOCKSIZE)
279 self.mbr = MBR(sector0)
282 if self.mbr.part[i].type == 0xa6: # OpenBSD type
286 assert self.part_num is not None, "No OpenBSD partition found"
288 d.seek(BLOCKSIZE * self.mbr.part[self.part_num].first_sector)
289 part_sector0 = d.read(BLOCKSIZE)
290 # The offset of the disklabel from the begining of the
291 # partition is one sector
292 part_sector1 = d.read(BLOCKSIZE)
321 ptable_raw) = struct.unpack(self.format, part_sector1)
323 assert self.magic == DISKMAGIC, "Disklabel is not valid"
325 self.ptable = self.PartitionTable(ptable_raw, self.npartitions)
327 def pack(self, checksum=None):
328 return struct.pack(self.format,
352 self.checksum if checksum is None else checksum,
357 ((364 - self.npartitions * 16) * '\x00'))
359 def compute_checksum(self):
360 """Compute the checksum of the disklabel"""
362 raw = cStringIO.StringIO(self.pack(0))
367 checksum ^= struct.unpack('<H', uint16)[0]
374 def setdsize(self, dsize):
376 self.secperunith = dsize >> 32
377 self.secperunit = dsize & 0xffffffff
381 return (self.secperunith << 32) + self.secperunit
383 def setbstart(self, bstart):
384 """Set start of useable region"""
385 self.bstarth = bstart >> 32
386 self.bstart = bstart & 0xffffffff
389 """Get start of usable region"""
390 return (self.bstarth << 32) + self.bstart
392 def setbend(self, bend):
393 """Set end of useable region"""
394 self.bendh = bend >> 32
395 self.bend = bend & 0xffffffff
398 return (self.bendh << 32) + self.bend
400 def enlarge_disk(self, new_size):
401 """Enlarge the size of the disk"""
403 assert new_size >= self.secperunit, \
404 "New size cannot be smaller that %s" % self.secperunit
407 self.setdsize(new_size)
408 self.ncylinders = self.getdsize() // (self.nsectors * self.ntracks)
409 self.setbend(self.ncylinders * self.nsectors * self.ntracks)
411 # Partition 'c' descriptes the entire disk
412 self.ptable.setpsize(2, new_size)
415 start = self.mbr.part[self.part_num].first_sector
416 self.mbr.part[self.part_num].sector_count = self.getbend() - start
418 lba = self.getbend() - 1
419 cylinder = lba // (self.ntracks * self.nsectors)
420 header = (lba // self.nsectors) % self.ntracks
421 sector = (lba % self.nsectors) + 1
422 chs = MBR.Partition.pack_chs(cylinder, header, sector)
423 self.mbr.part[self.part_num].end = chs
425 self.checksum = self.compute_checksum()
428 """Write the disklabel back to the media"""
429 with open(self.disk, 'rw+b') as d:
430 d.write(self.mbr.pack())
432 d.seek((self.mbr.part[self.part_num].first_sector + 1) * BLOCKSIZE)
435 def get_last_partition_id(self):
436 """Returns the id of the last partition"""
439 # Don't check partition 'c' which is the whole disk
440 for i in filter(lambda x: x != 2, range(self.npartitions)):
441 curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
446 assert end > 0, "No partition found"
450 def enlarge_last_partition(self):
451 """Enlarge the last partition to cover up all the free space"""
453 part_num = self.get_last_partition_id()
455 end = self.ptable.getpsize(part_num) + self.ptable.getpoffset(part_num)
457 assert end > 0, "No partition found"
459 if self.ptable.part[part_num].fstype == 1: # Swap partition.
460 #TODO: Maybe create a warning?
463 if end > (self.getbend() - 1024):
466 self.ptable.setpsize(
467 part_num, self.getbend() - self.ptable.getpoffset(part_num) - 1024)
469 self.checksum = self.compute_checksum()
472 """Print the Disklabel"""
473 title1 = "Master Boot Record"
477 "%s\n%s\n%s\n" % (title1, len(title1) * "=", str(self.mbr)) + \
478 "%s\n%s\n" % (title2, len(title2) * "=") + \
479 "Magic Number: 0x%x\n" % self.magic + \
480 "Drive type: %d\n" % self.dtype + \
481 "Subtype: %d\n" % self.subtype + \
482 "Typename: %s\n" % self.typename + \
483 "Pack Identifier: %s\n" % self.packname + \
484 "Number of bytes per sector: %d\n" % self.secsize + \
485 "Number of data sectors per track: %d\n" % self.nsectors + \
486 "Number of tracks per cylinder: %d\n" % self.ntracks + \
487 "Number of data cylinders per unit: %d\n" % self.ncylinders + \
488 "Number of data sectors per cylinder: %d\n" % self.secpercyl + \
489 "Number of data sectors per unit: %d\n" % self.secperunit + \
490 "DUID: %s\n" % "".join(x.encode('hex') for x in self.uid) + \
491 "Alt. cylinders per unit: %d\n" % self.acylinders + \
492 "Start of useable region (high part): %d\n" % self.bstarth + \
493 "Size of useable region (high part): %d\n" % self.bendh + \
494 "Start of useable region: %d\n" % self.bstart + \
495 "End of usable region: %d\n" % self.bend + \
496 "Generic Flags: %r\n" % self.flags + \
497 "Drive data: %r\n" % self.drivedata + \
498 "Number of data sectors (high part): %d\n" % self.secperunith + \
499 "Version: %d\n" % self.version + \
500 "Reserved for future use: %r\n" % self.spare + \
501 "The magic number again: 0x%x\n" % self.magic2 + \
502 "Checksum: %d\n" % self.checksum + \
503 "Number of partitions: %d\n" % self.npartitions + \
504 "Size of boot aread at sn0: %d\n" % self.bbsize + \
505 "Max size of fs superblock: %d\n" % self.sbsize + \
509 if __name__ == '__main__':
511 usage = "Usage: %prog [options] <input_media>"
512 parser = optparse.OptionParser(usage=usage)
514 parser.add_option("-l", "--list", action="store_true", dest="list",
516 help="list the disklabel on the specified media")
517 parser.add_option("--print-last", action="store_true", dest="last_part",
519 help="print the label of the last partition")
520 parser.add_option("--print-last-linux", action="store_true",
521 dest="last_linux", default=False,
522 help="print the linux number for the last partition")
523 parser.add_option("--print-duid", action="store_true", dest="duid",
525 help="print the disklabel unique identifier")
526 parser.add_option("-d", "--enlarge-disk", type="int", dest="disk_size",
527 default=None, metavar="SIZE",
528 help="Enlarge the disk to this SIZE (in sectors)")
530 "-p", "--enlarge-partition", action="store_true",
531 dest="enlarge_partition", default=False,
532 help="Enlarge the last partition to cover up the free space")
534 options, args = parser.parse_args(sys.argv[1:])
537 parser.error("Wrong number of arguments")
539 disklabel = Disklabel(args[0])
546 print "%s" % "".join(x.encode('hex') for x in disklabel.uid)
549 if options.last_part:
550 print "%c" % chr(ord('a') + disklabel.get_last_partition_id())
552 if options.last_linux:
553 part_id = disklabel.get_last_partition_id()
554 # The linux kernel does not assign a partition for label 'c' that
555 # describes the whole disk
556 print part_id + (4 if part_id > 2 else 5)
558 if options.disk_size is not None:
559 disklabel.enlarge_disk(options.disk_size)
561 if options.enlarge_partition:
562 disklabel.enlarge_last_partition()
568 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :