Revision 501e19ec

b/snf-image-helper/disklabel.py
28 28
import optparse
29 29

  
30 30
from collections import namedtuple
31
from collections import OrderedDict
31 32

  
32 33
BLOCKSIZE = 512
33 34

  
......
48 49

  
49 50
        def __init__(self, raw_part):
50 51
            """Create a Partition instance"""
51
            (
52
                self.status,
53
                self.start,
54
                self.type,
55
                self.end,
56
                self.first_sector,
57
                self.sector_count
58
            ) = struct.unpack(self.format, raw_part)
52
            (self.status,
53
             self.start,
54
             self.type,
55
             self.end,
56
             self.first_sector,
57
             self.sector_count
58
             ) = struct.unpack(self.format, raw_part)
59 59

  
60 60
        def pack(self):
61 61
            """Pack the partition values into a binary string"""
......
163 163
        return "%s\n%s\n%s\n" % (title, len(title) * "=", ret)
164 164

  
165 165

  
166
class PartitionTableBase(object):
167
    """Base Class for disklabel partition tables"""
168
    format = ""
169

  
170
    Partition = namedtuple('Partition', '')
171

  
172
    def __init__(self, ptable, pnumber):
173
        """Create a Partition Table instance"""
174
        self.part = []
175

  
176
        size = struct.calcsize(self.format)
177

  
178
        raw = cStringIO.StringIO(ptable)
179
        try:
180
            for i in range(pnumber):
181
                p = self.Partition(
182
                    *struct.unpack(self.format, raw.read(size)))
183
                self.part.append(p)
184
        finally:
185
            raw.close()
186

  
187
    def __str__(self):
188
        """Print the Partition table"""
189
        val = ""
190
        for i in range(len(self.part)):
191
            val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
192
        return val
193

  
194
    def pack(self):
195
        """Packs the partition table into a binary string."""
196
        ret = ""
197
        for i in range(len(self.part)):
198
            ret += struct.pack(self.format, *self.part[i])
199
        return ret
200

  
201

  
202 166
class Disk(object):
203 167
    """Represents an BSD Disk"""
204 168

  
......
248 212
        start = self.mbr.part[self.part_num].first_sector
249 213
        self.mbr.part[self.part_num].sector_count = end - start + 1
250 214

  
251
        cylinder = end // (self.disklabel.ntracks * self.disklabel.nsectors)
252
        header = (end // self.disklabel.nsectors) % self.disklabel.ntracks
253
        sector = (end % self.disklabel.nsectors) + 1
215
        ntracks = self.disklabel.field['ntracks']
216
        nsectors = self.disklabel.field['nsectors']
217

  
218
        cylinder = end // (ntracks * nsectors)
219
        header = (end // nsectors) % ntracks
220
        sector = (end % nsectors) + 1
254 221
        chs = MBR.Partition.pack_chs(cylinder, header, sector)
255 222
        self.mbr.part[self.part_num].end = chs
256 223

  
257 224
    def enlarge_last_partition(self):
225
        """Enlarge the last partition to cover up all the free space"""
258 226
        self.disklabel.enlarge_last_partition()
259 227

  
228
    def get_last_partition_id(self):
229
        """Get the ID of the last partition"""
230
        return self.disklabel.get_last_partition_id()
231

  
232
    def get_duid(self):
233
        return self.disklabel.field['uid']
234

  
260 235

  
261 236
class DisklabelBase(object):
262 237
    """Disklabel base class"""
......
267 242

  
268 243
    def pack(self, checksum=None):
269 244
        """Return a binary copy of the Disklabel block"""
270
        raise NotImplementedError
245

  
246
        out = OrderedDict()
247
        for k, v in self.field.items():
248
            out[k] = v
249

  
250
        if checksum is not None:
251
            out['checksum'] = checksum
252

  
253
        return struct.pack(self.format, * out.values() + [self.ptable.pack()])
271 254

  
272 255
    def compute_checksum(self):
273 256
        """Compute the checksum of the disklabel"""
......
290 273

  
291 274
    def write_to(self, device):
292 275
        """Write the disklabel to a device"""
293
        raise NotImplementedError
276

  
277
        # The disklabel starts at sector 1
278
        device.seek(BLOCKSIZE, os.SEEK_CUR)
279
        device.write(self.pack())
294 280

  
295 281
    def enlarge_last_partition(self):
296 282
        """Enlarge the last partition to consume all the usable space"""
297 283
        raise NotImplementedError
298 284

  
285
    def get_last_partition_id(self):
286
        """Get the ID of the last partition"""
287
        raise NotImplementedError
288

  
299 289
    def __str__(self):
300 290
        """Print the Disklabel"""
301 291
        raise NotImplementedError
302 292

  
303 293

  
294
class PartitionTableBase(object):
295
    """Base Class for disklabel partition tables"""
296

  
297
    @property
298
    def format(self):
299
        """Partition table format string"""
300
        raise NotImplementedError
301

  
302
    Partition = namedtuple('Partition', '')
303

  
304
    def __init__(self, ptable, pnumber):
305
        """Create a Partition Table instance"""
306
        self.part = []
307

  
308
        size = struct.calcsize(self.format)
309

  
310
        raw = cStringIO.StringIO(ptable)
311
        try:
312
            for i in range(pnumber):
313
                p = self.Partition(
314
                    *struct.unpack(self.format, raw.read(size)))
315
                self.part.append(p)
316
        finally:
317
            raw.close()
318

  
319
    def __str__(self):
320
        """Print the Partition table"""
321
        val = ""
322
        for i in range(len(self.part)):
323
            val += "%c: %s\n" % (chr(ord('a') + i), str(self.part[i]))
324
        return val
325

  
326
    def pack(self):
327
        """Packs the partition table into a binary string."""
328
        ret = ""
329
        for i in range(len(self.part)):
330
            ret += struct.pack(self.format, *self.part[i])
331
        return ret + ((364 - len(self.part) * 16) * '\x00')
332

  
333

  
304 334
class BSD_Disklabel(DisklabelBase):
305 335
    """Represents an BSD Disklabel"""
306 336

  
......
321 351
        Partition = namedtuple(
322 352
            'Partition', 'size, offset, fsize, fstype, frag, cpg')
323 353

  
324
    format = "<IHH16s16sIIIIIIHHIHHHHIII20s20sIHHII64s"
354
    format = "<IHH16s16sIIIIIIHHIHHHHIII20s20sIHHII364s"
325 355
    """
326 356
    Offset  Length          Contents
327 357
    0       4               Magic
......
362 392
    class PartitionTable(PartitionTableBase):
363 393
        """Reprepsents an OpenBSD Partition Table"""
364 394
        format = "<IIHHBBH"
395

  
365 396
        """
366 397
        Partition Entry:
367 398
        Offset  Length          Contents
......
380 411
        def setpsize(self, i, size):
381 412
            """Set size for partition i"""
382 413
            tmp = self.part[i]
383
            self.part[i] = self.Partition(size & 0xffffffff, tmp.offset,
384
                                          tmp.offseth, size >> 32, tmp.fstype,
385
                                          tmp.frag, tmp.cpg)
414
            self.part[i] = self.Partition(
415
                size & 0xffffffff, tmp.offset, tmp.offseth, size >> 32,
416
                tmp.fstype, tmp.frag, tmp.cpg)
386 417

  
387 418
        def getpsize(self, i):
388 419
            """Get size for partition i"""
......
391 422
        def setpoffset(self, i, offset):
392 423
            """Set offset for partition i"""
393 424
            tmp = self.part[i]
394
            self.part[i] = self.Partition(tmp.size, offset & 0xffffffff,
395
                                          offset >> 32, tmp.sizeh, tmp.frag,
396
                                          tmp.cpg)
425
            self.part[i] = self.Partition(
426
                tmp.size, offset & 0xffffffff, offset >> 32, tmp.sizeh,
427
                tmp.frag, tmp.cpg)
397 428

  
398 429
        def getpoffset(self, i):
399 430
            """Get offset for partition i"""
400 431
            return (self.part[i].offseth << 32) + self.part[i].offset
401 432

  
402 433
    format = "<IHH16s16sIIIIII8sIHHIII20sHH16sIHHII364s"
403
    """
404
    Offset  Length          Contents
405
    0       4               Magic
406
    4       2               Drive Type
407
    6       2               Subtype
408
    8       16              Type Name
409
    24      16              Pack Identifier
410
    32      4               Bytes per sector
411
    36      4               Data sectors per track
412
    40      4               Tracks per cylinder
413
    44      4               Data cylinders per unit
414
    48      4               Data sectors per cylinder
415
    52      4               Data sectors per unit
416
    56      8               Unique label identifier
417
    64      4               Alt cylinders per unit
418
    68      2               Start of useable region (high part)
419
    70      2               Size of usable region (high part)
420
    72      4               Start of useable region
421
    76      4               End of usable region
422
    80      4               Generic Flags
423
    84      5*4             Drive-type specific information
424
    104     2               Number of data sectors (high part)
425
    106     2               Version
426
    108     4*4             Reserved for future use
427
    124     4               Magic number
428
    128     2               Xor of data including partitions
429
    130     2               Number of partitions in following
430
    132     4               size of boot area at sn0, bytes
431
    136     4               Max size of fs superblock, bytes
432
    140     16*16           Partition Table
433
    """
434

  
434 435
    def __init__(self, device):
435 436
        """Create a DiskLabel instance"""
436 437

  
......
439 440
        # one sector
440 441
        sector1 = device.read(BLOCKSIZE)
441 442

  
442
        (self.magic,
443
         self.dtype,
444
         self.subtype,
445
         self.typename,
446
         self.packname,
447
         self.secsize,
448
         self.nsectors,
449
         self.ntracks,
450
         self.ncylinders,
451
         self.secpercyl,
452
         self.secperunit,
453
         self.uid,
454
         self.acylinders,
455
         self.bstarth,
456
         self.bendh,
457
         self.bstart,
458
         self.bend,
459
         self.flags,
460
         self.drivedata,
461
         self.secperunith,
462
         self.version,
463
         self.spare,
464
         self.magic2,
465
         self.checksum,
466
         self.npartitions,
467
         self.bbsize,
468
         self.sbsize,
469
         ptable_raw) = struct.unpack(self.format, sector1)
470

  
471
        assert self.magic == DISKMAGIC, "Disklabel is not valid"
472

  
473
        self.ptable = self.PartitionTable(ptable_raw, self.npartitions)
474

  
475
    def pack(self, checksum=None):
476
        return struct.pack(self.format,
477
                           self.magic,
478
                           self.dtype,
479
                           self.subtype,
480
                           self.typename,
481
                           self.packname,
482
                           self.secsize,
483
                           self.nsectors,
484
                           self.ntracks,
485
                           self.ncylinders,
486
                           self.secpercyl,
487
                           self.secperunit,
488
                           self.uid,
489
                           self.acylinders,
490
                           self.bstarth,
491
                           self.bendh,
492
                           self.bstart,
493
                           self.bend,
494
                           self.flags,
495
                           self.drivedata,
496
                           self.secperunith,
497
                           self.version,
498
                           self.spare,
499
                           self.magic2,
500
                           self.checksum if checksum is None else checksum,
501
                           self.npartitions,
502
                           self.bbsize,
503
                           self.sbsize,
504
                           self.ptable.pack() +
505
                           ((364 - self.npartitions * 16) * '\x00'))
443
        d_ = OrderedDict()   # Off  Len    Content
444
        (d_["magic"],        # 0    4      Magic
445
         d_["dtype"],        # 4    2      Drive Type
446
         d_["subtype"],      # 6    2      Subtype
447
         d_["typename"],     # 8    16     Type Name
448
         d_["packname"],     # 24   16     Pack Identifier
449
         d_["secsize"],      # 32   4      Bytes per sector
450
         d_["nsectors"],     # 36   4      Data sectors per track
451
         d_["ntracks"],      # 40   4      Tracks per cylinder
452
         d_["ncylinders"],   # 44   4      Data cylinders per unit
453
         d_["secpercyl"],    # 48   4      Data sectors per cylinder
454
         d_["secperunit"],   # 52   4      Data sectors per unit
455
         d_["uid"],          # 56   8      Unique label identifier
456
         d_["acylinders"],   # 64   4      Alt cylinders per unit
457
         d_["bstarth"],      # 68   2      Start of useable region (high part)
458
         d_["bendh"],        # 70   2      Size of usable region (high part)
459
         d_["bstart"],       # 72   4      Start of useable region
460
         d_["bend"],         # 76   4      End of usable region
461
         d_["flags"],        # 80   4      Generic Flags
462
         d_["drivedata"],    # 84   5*4    Drive-type specific information
463
         d_["secperunith"],  # 104  2      Number of data sectors (high part)
464
         d_["version"],      # 106  2      Version
465
         d_["spare"],        # 108  4*4    Reserved for future use
466
         d_["magic2"],       # 124  4      Magic number
467
         d_["checksum"],     # 128  2      Xor of data including partitions
468
         d_["npartitions"],  # 130  2      Number of partitions in following
469
         d_["bbsize"],       # 132  4      size of boot area at sn0, bytes
470
         d_["sbsize"],       # 136  4      Max size of fs superblock, bytes
471
         ptable_raw          # 140  16*16  Partition Table
472
         ) = struct.unpack(self.format, sector1)
473

  
474
        assert d_['magic'] == d_['magic2'] == DISKMAGIC, "Disklabel not valid"
475
        self.ptable = self.PartitionTable(ptable_raw, d_['npartitions'])
476
        self.field = d_
506 477

  
507 478
    def setdsize(self, dsize):
508 479
        """Set disk size"""
509
        self.secperunith = dsize >> 32
510
        self.secperunit = dsize & 0xffffffff
480
        self.field['secperunith'] = dsize >> 32
481
        self.field['secperunit'] = dsize & 0xffffffff
511 482

  
512 483
    def getdsize(self):
513 484
        """Get disk size"""
514
        return (self.secperunith << 32) + self.secperunit
485
        return (self.field['secperunith'] << 32) + self.field['secperunit']
486

  
487
    dsize = property(getdsize, setdsize, None, "disk size")
515 488

  
516 489
    def setbstart(self, bstart):
517 490
        """Set start of useable region"""
518
        self.bstarth = bstart >> 32
519
        self.bstart = bstart & 0xffffffff
491
        self.field['bstarth'] = bstart >> 32
492
        self.field['bstart'] = bstart & 0xffffffff
520 493

  
521 494
    def getbstart(self):
522 495
        """Get start of usable region"""
523
        return (self.bstarth << 32) + self.bstart
496
        return (self.field['bstarth'] << 32) + self.field['bstart']
497

  
498
    bstart = property(getbstart, setbstart, None, "usable region start")
524 499

  
525 500
    def setbend(self, bend):
526 501
        """Set size of useable region"""
527
        self.bendh = bend >> 32
528
        self.bend = bend & 0xffffffff
502
        self.field['bendh'] = bend >> 32
503
        self.field['bend'] = bend & 0xffffffff
529 504

  
530 505
    def getbend(self):
531 506
        """Get size of usable region"""
532
        return (self.bendh << 32) + self.bend
507
        return (self.field['bendh'] << 32) + self.field['bend']
508

  
509
    bend = property(getbend, setbend, None, "usable region size")
533 510

  
534 511
    def enlarge(self, new_size):
535 512
        """Enlarge the disk and return the last usable sector"""
536 513

  
537
        assert new_size >= self.getdsize(), \
538
            "New size cannot be smaller that %s" % self.getdsize()
514
        assert new_size >= self.dsize, \
515
            "New size cannot be smaller that %s" % self.dsize
539 516

  
540 517
        # Fix the disklabel
541
        self.setdsize(new_size)
542
        self.ncylinders = self.getdsize() // (self.nsectors * self.ntracks)
543
        self.setbend(self.ncylinders * self.nsectors * self.ntracks)
518
        self.dsize = new_size
519
        self.field['ncylinders'] = self.dsize // (self.field['nsectors'] *
520
                                                  self.field['ntracks'])
521
        self.bend = (self.field['ncylinders'] * self.field['nsectors'] *
522
                     self.field['ntracks'])
544 523

  
545 524
        # Partition 'c' descriptes the entire disk
546 525
        self.ptable.setpsize(2, new_size)
......
548 527
        # Update the checksum
549 528
        self.checksum = self.compute_checksum()
550 529

  
551
        # getbend() gives back the size of the usable region and not the end of
552
        # the usable region. I named it like this because this is how it is
553
        # named in OpenBSD. To get the last usable sector you need to reduce
554
        # this value by one.
555
        return self.getbend() - 1
556

  
557
    def write_to(self, device):
558
        """Write the disklabel to a device"""
559

  
560
        # The disklabel starts at sector 1
561
        device.seek(BLOCKSIZE, os.SEEK_CUR)
562
        device.write(self.pack())
530
        # bend is the size and not the end of the usable region. I named it
531
        # like this because this is how it is named in OpenBSD. To get the last
532
        # usable sector you need to reduce this value by one.
533
        return self.bend - 1
563 534

  
564 535
    def get_last_partition_id(self):
565 536
        """Returns the id of the last partition"""
566 537
        part = 0
567 538
        end = 0
568 539
        # Don't check partition 'c' which is the whole disk
569
        for i in filter(lambda x: x != 2, range(self.npartitions)):
540
        for i in filter(lambda x: x != 2, range(len(self.ptable.part))):
570 541
            curr_end = self.ptable.getpsize(i) + self.ptable.getpoffset(i)
571 542
            if end < curr_end:
572 543
                end = curr_end
......
589 560
            #TODO: Maybe create a warning?
590 561
            return
591 562

  
592
        if end > (self.getbend() - 1024):
563
        if end > (self.bend - 1024):
593 564
            return
594 565

  
595 566
        self.ptable.setpsize(
596
            part_num, self.getbend() - self.ptable.getpoffset(part_num) - 1024)
567
            part_num, self.bend - self.ptable.getpoffset(part_num) - 1024)
597 568

  
598
        self.checksum = self.compute_checksum()
569
        self.field['checksum'] = self.compute_checksum()
599 570

  
600 571
    def __str__(self):
601 572
        """Print the Disklabel"""
602 573

  
603 574
        title = "Disklabel"
575
        typename = self.field['typename'].strip('\x00').strip()
576
        packname = self.field['packname'].strip('\x00').strip()
577
        duid = "".join(x.encode('hex') for x in self.field['uid'])
604 578
        return \
605 579
            "%s\n%s\n" % (title, len(title) * "=") + \
606
            "Magic Number: 0x%x\n" % self.magic + \
607
            "Drive type: %d\n" % self.dtype + \
608
            "Subtype: %d\n" % self.subtype + \
609
            "Typename: %s\n" % self.typename.strip('\x00').strip() + \
610
            "Pack Identifier: %s\n" % self.packname.strip('\x00').strip() + \
611
            "Number of bytes per sector: %d\n" % self.secsize + \
612
            "Number of data sectors per track: %d\n" % self.nsectors + \
613
            "Number of tracks per cylinder: %d\n" % self.ntracks + \
614
            "Number of data cylinders per unit: %d\n" % self.ncylinders + \
615
            "Number of data sectors per cylinder: %d\n" % self.secpercyl + \
616
            "Number of data sectors per unit: %d\n" % self.secperunit + \
617
            "DUID: %s\n" % "".join(x.encode('hex') for x in self.uid) + \
618
            "Alt. cylinders per unit: %d\n" % self.acylinders + \
619
            "Start of useable region (high part): %d\n" % self.bstarth + \
620
            "Size of useable region (high part): %d\n" % self.bendh + \
621
            "Start of useable region: %d\n" % self.bstart + \
622
            "End of usable region: %d\n" % self.bend + \
623
            "Generic Flags: %r\n" % self.flags + \
624
            "Drive data: %r\n" % self.drivedata + \
625
            "Number of data sectors (high part): %d\n" % self.secperunith + \
626
            "Version: %d\n" % self.version + \
627
            "Reserved for future use: %r\n" % self.spare + \
628
            "The magic number again: 0x%x\n" % self.magic2 + \
629
            "Checksum: %d\n" % self.checksum + \
630
            "Number of partitions: %d\n" % self.npartitions + \
631
            "Size of boot aread at sn0: %d\n" % self.bbsize + \
632
            "Max size of fs superblock: %d\n" % self.sbsize + \
580
            "Magic Number: 0x%(magic)x\n" \
581
            "Drive type: %(dtype)d\n" \
582
            "Subtype: %(subtype)d\n" % self.field + \
583
            "Typename: %s\n" % typename + \
584
            "Pack Identifier: %s\n" % packname + \
585
            "# of bytes per sector: %(secsize)d\n" \
586
            "# of data sectors per track: %(nsectors)d\n" \
587
            "# of tracks per cylinder: %(ntracks)d\n" \
588
            "# of data cylinders per unit: %(ncylinders)d\n" \
589
            "# of data sectors per cylinder: %(secpercyl)d\n" \
590
            "# of data sectors per unit: %(secperunit)d\n" % self.field + \
591
            "DUID: %s\n" % duid + \
592
            "Alt. cylinders per unit: %(acylinders)d\n" \
593
            "Start of useable region (high part): %(bstarth)d\n" \
594
            "Size of useable region (high part): %(bendh)d\n" \
595
            "Start of useable region: %(bstart)d\n" \
596
            "End of usable region: %(bend)d\n" \
597
            "Generic Flags: %(flags)r\n" \
598
            "Drive data: %(drivedata)r\n" \
599
            "Number of data sectors (high part): %(secperunith)d\n" \
600
            "Version: %(version)d\n" \
601
            "Reserved for future use: %(spare)r\n" \
602
            "The magic number again: 0x%(magic2)x\n" \
603
            "Checksum: %(checksum)d\n" \
604
            "Number of partitions: %(npartitions)d\n"  \
605
            "Size of boot aread at sn0: %(bbsize)d\n"  \
606
            "Max size of fs superblock: %(sbsize)d\n" % self.field + \
633 607
            "%s" % self.ptable
634 608

  
635 609

  
......
667 641
        sys.exit(0)
668 642

  
669 643
    if options.duid:
670
        print "%s" % "".join(x.encode('hex') for x in disk.uid)
644
        print "%s" % "".join(x.encode('hex') for x in disk.get_duid())
671 645
        sys.exit(0)
672 646

  
673 647
    if options.last_part:

Also available in: Unified diff