SickGear/lib/hachoir/parser/file_system/mbr.py

235 lines
7.7 KiB
Python

"""
Master Boot Record.
"""
# cfdisk uses the following algorithm to compute the geometry:
# 0. Use the values given by the user.
# 1. Try to guess the geometry from the partition table:
# if all the used partitions end at the same head H and the
# same sector S, then there are (H+1) heads and S sectors/cylinder.
# 2. Ask the system (ioctl/HDIO_GETGEO).
# 3. 255 heads and 63 sectors/cylinder.
from hachoir.parser import Parser
from hachoir.field import (FieldSet,
Enum, Bits, UInt8, UInt16, UInt32,
RawBytes)
from hachoir.core.endian import LITTLE_ENDIAN
from hachoir.core.tools import humanFilesize
from hachoir.core.text_handler import textHandler, hexadecimal
BLOCK_SIZE = 512 # bytes
class CylinderNumber(Bits):
def __init__(self, parent, name, description=None):
Bits.__init__(self, parent, name, 10, description)
def createValue(self):
i = self.parent.stream.readInteger(
self.absolute_address, False, self._size, self.parent.endian)
return i >> 2 | i % 4 << 8
class PartitionHeader(FieldSet):
static_size = 16 * 8
# taken from the source of cfdisk:
# sed -n 's/.*{\(.*\), N_(\(.*\))}.*/ \1: \2,/p' i386_sys_types.c
system_name = {
0x00: "Empty",
0x01: "FAT12",
0x02: "XENIX root",
0x03: "XENIX usr",
0x04: "FAT16 <32M",
0x05: "Extended",
0x06: "FAT16",
0x07: "HPFS/NTFS",
0x08: "AIX",
0x09: "AIX bootable",
0x0a: "OS/2 Boot Manager",
0x0b: "W95 FAT32",
0x0c: "W95 FAT32 (LBA)",
0x0e: "W95 FAT16 (LBA)",
0x0f: "W95 Ext'd (LBA)",
0x10: "OPUS",
0x11: "Hidden FAT12",
0x12: "Compaq diagnostics",
0x14: "Hidden FAT16 <32M",
0x16: "Hidden FAT16",
0x17: "Hidden HPFS/NTFS",
0x18: "AST SmartSleep",
0x1b: "Hidden W95 FAT32",
0x1c: "Hidden W95 FAT32 (LBA)",
0x1e: "Hidden W95 FAT16 (LBA)",
0x24: "NEC DOS",
0x39: "Plan 9",
0x3c: "PartitionMagic recovery",
0x40: "Venix 80286",
0x41: "PPC PReP Boot",
0x42: "SFS",
0x4d: "QNX4.x",
0x4e: "QNX4.x 2nd part",
0x4f: "QNX4.x 3rd part",
0x50: "OnTrack DM",
0x51: "OnTrack DM6 Aux1",
0x52: "CP/M",
0x53: "OnTrack DM6 Aux3",
0x54: "OnTrackDM6",
0x55: "EZ-Drive",
0x56: "Golden Bow",
0x5c: "Priam Edisk",
0x61: "SpeedStor",
0x63: "GNU HURD or SysV",
0x64: "Novell Netware 286",
0x65: "Novell Netware 386",
0x70: "DiskSecure Multi-Boot",
0x75: "PC/IX",
0x80: "Old Minix",
0x81: "Minix / old Linux",
0x82: "Linux swap / Solaris",
0x83: "Linux (ext2/ext3)",
0x84: "OS/2 hidden C: drive",
0x85: "Linux extended",
0x86: "NTFS volume set",
0x87: "NTFS volume set",
0x88: "Linux plaintext",
0x8e: "Linux LVM",
0x93: "Amoeba",
0x94: "Amoeba BBT",
0x9f: "BSD/OS",
0xa0: "IBM Thinkpad hibernation",
0xa5: "FreeBSD",
0xa6: "OpenBSD",
0xa7: "NeXTSTEP",
0xa8: "Darwin UFS",
0xa9: "NetBSD",
0xab: "Darwin boot",
0xb7: "BSDI fs",
0xb8: "BSDI swap",
0xbb: "Boot Wizard hidden",
0xbe: "Solaris boot",
0xbf: "Solaris",
0xc1: "DRDOS/sec (FAT-12)",
0xc4: "DRDOS/sec (FAT-16 < 32M)",
0xc6: "DRDOS/sec (FAT-16)",
0xc7: "Syrinx",
0xda: "Non-FS data",
0xdb: "CP/M / CTOS / ...",
0xde: "Dell Utility",
0xdf: "BootIt",
0xe1: "DOS access",
0xe3: "DOS R/O",
0xe4: "SpeedStor",
0xeb: "BeOS fs",
0xee: "EFI GPT",
0xef: "EFI (FAT-12/16/32)",
0xf0: "Linux/PA-RISC boot",
0xf1: "SpeedStor",
0xf4: "SpeedStor",
0xf2: "DOS secondary",
0xfd: "Linux raid autodetect",
0xfe: "LANstep",
0xff: "BBT"
}
def createFields(self):
yield UInt8(self, "bootable", "Bootable flag (true if equals to 0x80)")
if self["bootable"].value not in (0x00, 0x80):
self.warning(
"Stream doesn't look like master boot record (partition bootable error)!")
yield UInt8(self, "start_head", "Starting head number of the partition")
yield Bits(self, "start_sector", 6, "Starting sector number of the partition")
yield CylinderNumber(self, "start_cylinder", "Starting cylinder number of the partition")
yield Enum(UInt8(self, "system", "System indicator"), self.system_name)
yield UInt8(self, "end_head", "Ending head number of the partition")
yield Bits(self, "end_sector", 6, "Ending sector number of the partition")
yield CylinderNumber(self, "end_cylinder", "Ending cylinder number of the partition")
yield UInt32(self, "LBA", "LBA (number of sectors before this partition)")
yield UInt32(self, "size", "Size (block count)")
def isUsed(self):
return self["system"].value != 0
def createDescription(self):
desc = "Partition header: "
if self.isUsed():
system = self["system"].display
size = self["size"].value * BLOCK_SIZE
desc += "%s, %s" % (system, humanFilesize(size))
else:
desc += "(unused)"
return desc
class MasterBootRecord(FieldSet):
static_size = 512 * 8
def createFields(self):
yield RawBytes(self, "program", 446, "Boot program (Intel x86 machine code)")
yield PartitionHeader(self, "header[0]")
yield PartitionHeader(self, "header[1]")
yield PartitionHeader(self, "header[2]")
yield PartitionHeader(self, "header[3]")
yield textHandler(UInt16(self, "signature", "Signature (0xAA55)"), hexadecimal)
def _getPartitions(self):
return (self[index] for index in range(1, 5))
headers = property(_getPartitions)
class Partition(FieldSet):
def createFields(self):
mbr = MasterBootRecord(self, "mbr")
yield mbr
# No error if we only want to analyse a backup of a mbr
if self.eof:
return
for start, index, header in sorted((hdr["LBA"].value, index, hdr)
for index, hdr in enumerate(mbr.headers) if hdr.isUsed()):
# Seek to the beginning of the partition
padding = self.seekByte(start * BLOCK_SIZE, "padding[]")
if padding:
yield padding
# Content of the partition
name = "partition[%u]" % index
size = BLOCK_SIZE * header["size"].value
desc = header["system"].display
if header["system"].value == 5:
yield Partition(self, name, desc, size * 8)
else:
yield RawBytes(self, name, size, desc)
# Padding at the end
if self.current_size < self._size:
yield self.seekBit(self._size, "end")
class MSDos_HardDrive(Parser, Partition):
endian = LITTLE_ENDIAN
MAGIC = b"\x55\xAA"
PARSER_TAGS = {
"id": "msdos_harddrive",
"category": "file_system",
"description": "MS-DOS hard drive with Master Boot Record (MBR)",
"min_size": 512 * 8,
"file_ext": ("",),
# "magic": ((MAGIC, 510*8),),
}
def validate(self):
if self.stream.readBytes(510 * 8, 2) != self.MAGIC:
return "Invalid signature"
used = False
for hdr in self["mbr"].headers:
if hdr["bootable"].value not in (0x00, 0x80):
return "Wrong boot flag"
used |= hdr.isUsed()
return used or "No partition found"