Remove submodule, just put Dependencies in ./libs

This commit is contained in:
Ruud
2011-02-28 15:25:42 +01:00
parent 2fb578d4de
commit 6888798ef6
541 changed files with 173394 additions and 4 deletions
@@ -0,0 +1,8 @@
from hachoir_parser.file_system.ext2 import EXT2_FS
from hachoir_parser.file_system.fat import FAT12, FAT16, FAT32
from hachoir_parser.file_system.mbr import MSDos_HardDrive
from hachoir_parser.file_system.ntfs import NTFS
from hachoir_parser.file_system.iso9660 import ISO9660
from hachoir_parser.file_system.reiser_fs import REISER_FS
from hachoir_parser.file_system.linux_swap import LinuxSwapFile
+464
View File
@@ -0,0 +1,464 @@
"""
EXT2 (Linux) file system parser.
Author: Victor Stinner
Sources:
- EXT2FS source code
http://ext2fsd.sourceforge.net/
- Analysis of the Ext2fs structure
http://www.nondot.org/sabre/os/files/FileSystems/ext2fs/
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, ParserError,
Bit, Bits, UInt8, UInt16, UInt32,
Enum, String, TimestampUnix32, RawBytes, NullBytes)
from hachoir_core.tools import (alignValue,
humanDuration, humanFilesize)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import textHandler
from itertools import izip
class DirectoryEntry(FieldSet):
file_type = {
1: "Regular",
2: "Directory",
3: "Char. dev.",
4: "Block dev.",
5: "Fifo",
6: "Socket",
7: "Symlink",
8: "Max"
}
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = self["rec_len"].value * 8
def createFields(self):
yield UInt32(self, "inode", "Inode")
yield UInt16(self, "rec_len", "Record length")
yield UInt8(self, "name_len", "Name length")
yield Enum(UInt8(self, "file_type", "File type"), self.file_type)
yield String(self, "name", self["name_len"].value, "File name")
size = (self._size - self.current_size)//8
if size:
yield NullBytes(self, "padding", size)
def createDescription(self):
name = self["name"].value.strip("\0")
if name:
return "Directory entry: %s" % name
else:
return "Directory entry (empty)"
class Inode(FieldSet):
inode_type_name = {
1: "list of bad blocks",
2: "Root directory",
3: "ACL inode",
4: "ACL inode",
5: "Boot loader",
6: "Undelete directory",
8: "EXT3 journal"
}
file_type = {
1: "Fifo",
2: "Character device",
4: "Directory",
6: "Block device",
8: "Regular",
10: "Symbolic link",
12: "Socket",
}
file_type_letter = {
1: "p",
4: "d",
2: "c",
6: "b",
10: "l",
12: "s",
}
static_size = (68 + 15*4)*8
def __init__(self, parent, name, index):
FieldSet.__init__(self, parent, name, None)
self.uniq_id = 1+index
def createDescription(self):
desc = "Inode %s: " % self.uniq_id
size = self["size"].value
if self["blocks"].value == 0:
desc += "(unused)"
elif 11 <= self.uniq_id:
size = humanFilesize(size)
desc += "file, size=%s, mode=%s" % (size, self.getMode())
else:
if self.uniq_id in self.inode_type_name:
desc += self.inode_type_name[self.uniq_id]
if self.uniq_id == 2:
desc += " (%s)" % self.getMode()
else:
desc += "special"
return desc
def getMode(self):
names = (
("owner_read", "owner_write", "owner_exec"),
("group_read", "group_write", "group_exec"),
("other_read", "other_write", "other_exec"))
letters = "rwx"
mode = [ "-" for index in xrange(10) ]
index = 1
for loop in xrange(3):
for name, letter in izip(names[loop], letters):
if self[name].value:
mode[index] = letter
index += 1
file_type = self["file_type"].value
if file_type in self.file_type_letter:
mode[0] = self.file_type_letter[file_type]
return "".join(mode)
def createFields(self):
# File mode
yield Bit(self, "other_exec")
yield Bit(self, "other_write")
yield Bit(self, "other_read")
yield Bit(self, "group_exec")
yield Bit(self, "group_write")
yield Bit(self, "group_read")
yield Bit(self, "owner_exec")
yield Bit(self, "owner_write")
yield Bit(self, "owner_read")
yield Bit(self, "sticky")
yield Bit(self, "setgid")
yield Bit(self, "setuid")
yield Enum(Bits(self, "file_type", 4), self.file_type)
yield UInt16(self, "uid", "User ID")
yield UInt32(self, "size", "File size (in bytes)")
yield TimestampUnix32(self, "atime", "Last access time")
yield TimestampUnix32(self, "ctime", "Creation time")
yield TimestampUnix32(self, "mtime", "Last modification time")
yield TimestampUnix32(self, "dtime", "Delete time")
yield UInt16(self, "gid", "Group ID")
yield UInt16(self, "links_count", "Links count")
yield UInt32(self, "blocks", "Number of blocks")
yield UInt32(self, "flags", "Flags")
yield NullBytes(self, "reserved[]", 4, "Reserved")
for index in xrange(15):
yield UInt32(self, "block[]")
yield UInt32(self, "version", "Version")
yield UInt32(self, "file_acl", "File ACL")
yield UInt32(self, "dir_acl", "Directory ACL")
yield UInt32(self, "faddr", "Block where the fragment of the file resides")
os = self["/superblock/creator_os"].value
if os == SuperBlock.OS_LINUX:
yield UInt8(self, "frag", "Number of fragments in the block")
yield UInt8(self, "fsize", "Fragment size")
yield UInt16(self, "padding", "Padding")
yield UInt16(self, "uid_high", "High 16 bits of user ID")
yield UInt16(self, "gid_high", "High 16 bits of group ID")
yield NullBytes(self, "reserved[]", 4, "Reserved")
elif os == SuperBlock.OS_HURD:
yield UInt8(self, "frag", "Number of fragments in the block")
yield UInt8(self, "fsize", "Fragment size")
yield UInt16(self, "mode_high", "High 16 bits of mode")
yield UInt16(self, "uid_high", "High 16 bits of user ID")
yield UInt16(self, "gid_high", "High 16 bits of group ID")
yield UInt32(self, "author", "Author ID (?)")
else:
yield RawBytes(self, "raw", 12, "Reserved")
class Bitmap(FieldSet):
def __init__(self, parent, name, start, size, description, **kw):
description = "%s: %s items" % (description, size)
FieldSet.__init__(self, parent, name, description, size=size, **kw)
self.start = 1+start
def createFields(self):
for index in xrange(self._size):
yield Bit(self, "item[]", "Item %s" % (self.start+index))
BlockBitmap = Bitmap
InodeBitmap = Bitmap
class GroupDescriptor(FieldSet):
static_size = 32*8
def __init__(self, parent, name, index):
FieldSet.__init__(self, parent, name)
self.uniq_id = index
def createDescription(self):
blocks_per_group = self["/superblock/blocks_per_group"].value
start = self.uniq_id * blocks_per_group
end = start + blocks_per_group
return "Group descriptor: blocks %s-%s" % (start, end)
def createFields(self):
yield UInt32(self, "block_bitmap", "Points to the blocks bitmap block")
yield UInt32(self, "inode_bitmap", "Points to the inodes bitmap block")
yield UInt32(self, "inode_table", "Points to the inodes table first block")
yield UInt16(self, "free_blocks_count", "Number of free blocks")
yield UInt16(self, "free_inodes_count", "Number of free inodes")
yield UInt16(self, "used_dirs_count", "Number of inodes allocated to directories")
yield UInt16(self, "padding", "Padding")
yield NullBytes(self, "reserved", 12, "Reserved")
class SuperBlock(FieldSet):
static_size = 433*8
OS_LINUX = 0
OS_HURD = 1
os_name = {
0: "Linux",
1: "Hurd",
2: "Masix",
3: "FreeBSD",
4: "Lites",
5: "WinNT"
}
state_desc = {
1: "Valid (Unmounted cleanly)",
2: "Error (Errors detected)",
4: "Orphan FS (Orphans being recovered)",
}
error_handling_desc = { 1: "Continue" }
def __init__(self, parent, name):
FieldSet.__init__(self, parent, name)
self._group_count = None
def createDescription(self):
if self["feature_compat"].value & 4:
fstype = "ext3"
else:
fstype = "ext2"
return "Superblock: %s file system" % fstype
def createFields(self):
yield UInt32(self, "inodes_count", "Inodes count")
yield UInt32(self, "blocks_count", "Blocks count")
yield UInt32(self, "r_blocks_count", "Reserved blocks count")
yield UInt32(self, "free_blocks_count", "Free blocks count")
yield UInt32(self, "free_inodes_count", "Free inodes count")
yield UInt32(self, "first_data_block", "First data block")
yield UInt32(self, "log_block_size", "Block size")
yield UInt32(self, "log_frag_size", "Fragment size")
yield UInt32(self, "blocks_per_group", "Blocks per group")
yield UInt32(self, "frags_per_group", "Fragments per group")
yield UInt32(self, "inodes_per_group", "Inodes per group")
yield TimestampUnix32(self, "mtime", "Mount time")
yield TimestampUnix32(self, "wtime", "Write time")
yield UInt16(self, "mnt_count", "Mount count")
yield UInt16(self, "max_mnt_count", "Max mount count")
yield String(self, "magic", 2, "Magic number (0x53EF)")
yield Enum(UInt16(self, "state", "File system state"), self.state_desc)
yield Enum(UInt16(self, "errors", "Behaviour when detecting errors"), self.error_handling_desc)
yield UInt16(self, "minor_rev_level", "Minor revision level")
yield TimestampUnix32(self, "last_check", "Time of last check")
yield textHandler(UInt32(self, "check_interval", "Maximum time between checks"), self.postMaxTime)
yield Enum(UInt32(self, "creator_os", "Creator OS"), self.os_name)
yield UInt32(self, "rev_level", "Revision level")
yield UInt16(self, "def_resuid", "Default uid for reserved blocks")
yield UInt16(self, "def_resgid", "Default gid for reserved blocks")
yield UInt32(self, "first_ino", "First non-reserved inode")
yield UInt16(self, "inode_size", "Size of inode structure")
yield UInt16(self, "block_group_nr", "Block group # of this superblock")
yield UInt32(self, "feature_compat", "Compatible feature set")
yield UInt32(self, "feature_incompat", "Incompatible feature set")
yield UInt32(self, "feature_ro_compat", "Read-only compatible feature set")
yield RawBytes(self, "uuid", 16, "128-bit uuid for volume")
yield String(self, "volume_name", 16, "Volume name", strip="\0")
yield String(self, "last_mounted", 64, "Directory where last mounted", strip="\0")
yield UInt32(self, "compression", "For compression (algorithm usage bitmap)")
yield UInt8(self, "prealloc_blocks", "Number of blocks to try to preallocate")
yield UInt8(self, "prealloc_dir_blocks", "Number to preallocate for directories")
yield UInt16(self, "padding", "Padding")
yield String(self, "journal_uuid", 16, "uuid of journal superblock")
yield UInt32(self, "journal_inum", "inode number of journal file")
yield UInt32(self, "journal_dev", "device number of journal file")
yield UInt32(self, "last_orphan", "start of list of inodes to delete")
yield RawBytes(self, "reserved", 197, "Reserved")
def _getGroupCount(self):
if self._group_count is None:
# Calculate number of groups
blocks_per_group = self["blocks_per_group"].value
self._group_count = (self["blocks_count"].value - self["first_data_block"].value + (blocks_per_group - 1)) / blocks_per_group
return self._group_count
group_count = property(_getGroupCount)
def postMaxTime(self, chunk):
return humanDuration(chunk.value * 1000)
class GroupDescriptors(FieldSet):
def __init__(self, parent, name, count):
FieldSet.__init__(self, parent, name)
self.count = count
def createDescription(self):
return "Group descriptors: %s items" % self.count
def createFields(self):
for index in range(0, self.count):
yield GroupDescriptor(self, "group[]", index)
class InodeTable(FieldSet):
def __init__(self, parent, name, start, count):
FieldSet.__init__(self, parent, name)
self.start = start
self.count = count
self._size = self.count * self["/superblock/inode_size"].value * 8
def createDescription(self):
return "Group descriptors: %s items" % self.count
def createFields(self):
for index in range(self.start, self.start+self.count):
yield Inode(self, "inode[]", index)
class Group(FieldSet):
def __init__(self, parent, name, index):
FieldSet.__init__(self, parent, name)
self.uniq_id = index
def createDescription(self):
desc = "Group %s: %s" % (self.uniq_id, humanFilesize(self.size/8))
if "superblock_copy" in self:
desc += " (with superblock copy)"
return desc
def createFields(self):
group = self["../group_desc/group[%u]" % self.uniq_id]
superblock = self["/superblock"]
block_size = self["/"].block_size
# Read block bitmap
addr = self.absolute_address + 56*8
self.superblock_copy = (self.stream.readBytes(addr, 2) == "\x53\xEF")
if self.superblock_copy:
yield SuperBlock(self, "superblock_copy")
# Compute number of block and inodes
block_count = superblock["blocks_per_group"].value
inode_count = superblock["inodes_per_group"].value
block_index = self.uniq_id * block_count
inode_index = self.uniq_id * inode_count
if (block_count % 8) != 0:
raise ParserError("Invalid block count")
if (inode_count % 8) != 0:
raise ParserError("Invalid inode count")
block_count = min(block_count, superblock["blocks_count"].value - block_index)
inode_count = min(inode_count, superblock["inodes_count"].value - inode_index)
# Read block bitmap
field = self.seekByte(group["block_bitmap"].value * block_size, relative=False, null=True)
if field:
yield field
yield BlockBitmap(self, "block_bitmap", block_index, block_count, "Block bitmap")
# Read inode bitmap
field = self.seekByte(group["inode_bitmap"].value * block_size, relative=False)
if field:
yield field
yield InodeBitmap(self, "inode_bitmap", inode_index, inode_count, "Inode bitmap")
# Read inode table
field = self.seekByte(alignValue(self.current_size//8, block_size))
if field:
yield field
yield InodeTable(self, "inode_table", inode_index, inode_count)
# Add padding if needed
addr = min(self.parent.size / 8,
(self.uniq_id+1) * superblock["blocks_per_group"].value * block_size)
yield self.seekByte(addr, "data", relative=False)
class EXT2_FS(Parser):
"""
Parse an EXT2 or EXT3 partition.
Attributes:
* block_size: Size of a block (in bytes)
Fields:
* superblock: Most important block, store most important informations
* ...
"""
PARSER_TAGS = {
"id": "ext2",
"category": "file_system",
"description": "EXT2/EXT3 file system",
"min_size": (1024*2)*8,
"magic": (
# (magic, state=valid)
("\x53\xEF\1\0", 1080*8),
# (magic, state=error)
("\x53\xEF\2\0", 1080*8),
# (magic, state=error)
("\x53\xEF\4\0", 1080*8),
),
}
endian = LITTLE_ENDIAN
def validate(self):
if self.stream.readBytes((1024+56)*8, 2) != "\x53\xEF":
return "Invalid magic number"
if not(0 <= self["superblock/log_block_size"].value <= 2):
return "Invalid (log) block size"
if self["superblock/inode_size"].value != (68 + 15*4):
return "Unsupported inode size"
return True
def createFields(self):
# Skip something (what is stored here? MBR?)
yield NullBytes(self, "padding[]", 1024)
# Read superblock
superblock = SuperBlock(self, "superblock")
yield superblock
if not(0 <= self["superblock/log_block_size"].value <= 2):
raise ParserError("EXT2: Invalid (log) block size")
self.block_size = 1024 << superblock["log_block_size"].value # in bytes
# Read groups' descriptor
field = self.seekByte(((1023 + superblock.size/8) / self.block_size + 1) * self.block_size, null=True)
if field:
yield field
groups = GroupDescriptors(self, "group_desc", superblock.group_count)
yield groups
# Read groups
address = groups["group[0]/block_bitmap"].value * self.block_size
field = self.seekByte(address, null=True)
if field:
yield field
for index in range(0, superblock.group_count):
yield Group(self, "group[]", index)
def getSuperblock(self):
# FIXME: Use superblock copy if main superblock is invalid
return self["superblock"]
def createDescription(self):
superblock = self.getSuperblock()
block_size = 1024 << superblock["log_block_size"].value
nb_block = superblock["blocks_count"].value
total = nb_block * block_size
used = (superblock["free_blocks_count"].value) * block_size
desc = "EXT2/EXT3"
if "group[0]/inode_table/inode[7]/blocks" in self:
if 0 < self["group[0]/inode_table/inode[7]/blocks"].value:
desc = "EXT3"
else:
desc = "EXT2"
return desc + " file system: total=%s, used=%s, block=%s" % (
humanFilesize(total), humanFilesize(used),
humanFilesize(block_size))
+433
View File
@@ -0,0 +1,433 @@
from hachoir_core.compatibility import sorted
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, StaticFieldSet,
RawBytes, PaddingBytes, createPaddingField, Link, Fragment,
Bit, Bits, UInt8, UInt16, UInt32,
String, Bytes, NullBytes)
from hachoir_core.field.integer import GenericInteger
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal
from hachoir_core.error import error
from hachoir_core.tools import humanFilesize, makePrintable
import datetime
import re
strip_index = re.compile(r'\[[^]]+]$')
class Boot(FieldSet):
static_size = 512*8
def createFields(self):
yield Bytes(self, "jmp", 3, "Jump instruction (to skip over header on boot)")
yield Bytes(self, "oem_name", 8, "OEM Name (padded with spaces)")
yield UInt16(self, "sector_size", "Bytes per sector")
yield UInt8 (self, "cluster_size", "Sectors per cluster")
yield UInt16(self, "reserved_sectors", "Reserved sector count (including boot sector)")
yield UInt8 (self, "fat_nb", "Number of file allocation tables")
yield UInt16(self, "max_root", "Maximum number of root directory entries")
yield UInt16(self, "sectors1", "Total sectors (if zero, use 'sectors2')")
yield UInt8 (self, "media_desc", "Media descriptor")
yield UInt16(self, "fat_size", "Sectors per FAT")
yield UInt16(self, "track_size", "Sectors per track")
yield UInt16(self, "head_nb", "Number of heads")
yield UInt32(self, "hidden", "Hidden sectors")
yield UInt32(self, "sectors2", "Total sectors (if greater than 65535)")
if self.parent.version == 32:
yield UInt32(self, "fat32_size", "Sectors per FAT")
yield UInt16(self, "fat_flags", "FAT Flags")
yield UInt16(self, "version", "Version")
yield UInt32(self, "root_start", "Cluster number of root directory start")
yield UInt16(self, "inf_sector", "Sector number of FS Information Sector")
yield UInt16(self, "boot_copy", "Sector number of a copy of this boot sector")
yield NullBytes(self, "reserved[]", 12, "Reserved")
yield UInt8(self, "phys_drv", "Physical drive number")
yield NullBytes(self, "reserved[]", 1, 'Reserved ("current head")')
yield UInt8(self, "sign", "Signature")
yield textHandler(UInt32(self, "serial", "ID (serial number)"), hexadecimal)
yield String(self, "label", 11, "Volume Label", strip=' ', charset="ASCII")
yield String(self, "fs_type", 8, "FAT file system type", strip=' ', charset="ASCII")
yield Bytes(self, "code", 510-self.current_size/8, "Operating system boot code")
yield Bytes(self, "trail_sig", 2, "Signature (0x55 0xAA)")
class FSInfo(StaticFieldSet):
format = (
(String, "lead_sig", 4, 'Signature ("RRaA")'),
(NullBytes, "reserved[]", 480),
(String, "struct_sig", 4, 'Signature ("rrAa")'),
(UInt32, "free_count", "Last known free cluster count on the volume"),
(UInt32, "nxt_free",),
(NullBytes, "reserved[]", 12),
(Bytes, "trail_sig", 4, "Signature (0x00 0x00 0x55 0xAA)")
)
class FAT(FieldSet):
class FAT(FieldSet):
def createFields(self):
parent = self.parent
version = parent.parent.version
text_handler = parent.text_handler
while self.current_size < self._size:
yield textHandler(GenericInteger(self, 'entry[]', False, version), text_handler)
def createFields(self):
version = self.parent.version
max_entry = 1 << min(28, version)
def FatEntry(chunk):
i = chunk.value
j = (1 - i) % max_entry
if j == 0:
return "reserved cluster"
elif j == 1:
return "free cluster"
elif j < 10:
return "end of a chain"
elif j == 10:
return "bad cluster"
elif j < 18:
return "reserved value"
else:
return str(i)
self.text_handler = FatEntry
while self.current_size < self._size:
yield FAT.FAT(self, 'group[]', size=min(1000*version,self._size-self.current_size))
class Date(FieldSet):
def __init__(self, parent, name):
FieldSet.__init__(self, parent, name, size={
"create": 5,
"access": 2,
"modify": 4,
}[name] * 8)
def createFields(self):
size = self.size / 8
if size > 2:
if size > 4:
yield UInt8(self, "cs", "10ms units, values from 0 to 199")
yield Bits(self, "2sec", 5, "seconds/2")
yield Bits(self, "min", 6, "minutes")
yield Bits(self, "hour", 5, "hours")
yield Bits(self, "day", 5, "(1-31)")
yield Bits(self, "month", 4, "(1-12)")
yield Bits(self, "year", 7, "(0 = 1980, 127 = 2107)")
def createDescription(self):
date = [ self["year"].value, self["month"].value, self["day"].value ]
size = self.size / 8
if size > 2:
mkdate = datetime.datetime
cs = 200 * self["2sec"].value
if size > 4:
cs += self["cs"].value
date += [ self["hour"].value, self["min"].value, cs / 100, cs % 100 * 10000 ]
else:
mkdate = datetime.date
if date == [ 0 for i in date ]:
date = None
else:
date[0] += 1980
try:
date = mkdate(*tuple(date))
except ValueError:
return "invalid"
return str(date)
class InodeLink(Link):
def __init__(self, parent, name, target=None):
Link.__init__(self, parent, name)
self.target = target
self.first = None
def _getTargetPath(self):
if not self.target:
parent = self.parent
self.target = strip_index.sub(r"\\", parent.parent._name) + parent.getFilename().rstrip("/")
return self.target
def createValue(self):
field = InodeGen(self["/"], self.parent, self._getTargetPath())(self)
if field:
self._display = field.path
return Link.createValue(self)
def createDisplay(self):
return "/%s[0]" % self._getTargetPath()
class FileEntry(FieldSet):
static_size = 32*8
process = False
LFN = False
def __init__(self, *args):
FieldSet.__init__(self, *args)
self.status = self.stream.readBits(self.absolute_address, 8, LITTLE_ENDIAN)
if self.status in (0, 0xE5):
return
magic = self.stream.readBits(self.absolute_address+11*8, 8, LITTLE_ENDIAN)
if magic & 0x3F == 0x0F:
self.LFN = True
elif self.getFilename() not in (".", ".."):
self.process = True
def getFilename(self):
name = self["name"].value
if isinstance(name, str):
name = makePrintable(name, "ASCII", to_unicode=True)
ext = self["ext"].value
if ext:
name += "." + ext
if name[0] == 5:
name = "\xE5" + name[1:]
if not self.LFN and self["directory"].value:
name += "/"
return name
def createDescription(self):
if self.status == 0:
return "Free entry"
elif self.status == 0xE5:
return "Deleted file"
elif self.LFN:
name = "".join( field.value for field in self.array("name") )
try:
name = name[:name.index('\0')]
except ValueError:
pass
seq_no = self["seq_no"].value
return "Long filename part: '%s' [%u]" % (name, seq_no)
else:
return "File: '%s'" % self.getFilename()
def getCluster(self):
cluster = self["cluster_lo"].value
if self.parent.parent.version > 16:
cluster += self["cluster_hi"].value << 16
return cluster
def createFields(self):
if not self.LFN:
yield String(self, "name", 8, "DOS file name (padded with spaces)",
strip=' ', charset="ASCII")
yield String(self, "ext", 3, "DOS file extension (padded with spaces)",
strip=' ', charset="ASCII")
yield Bit(self, "read_only")
yield Bit(self, "hidden")
yield Bit(self, "system")
yield Bit(self, "volume_label")
yield Bit(self, "directory")
yield Bit(self, "archive")
yield Bit(self, "device")
yield Bit(self, "unused")
yield RawBytes(self, "reserved", 1, "Something about the case")
yield Date(self, "create")
yield Date(self, "access")
if self.parent.parent.version > 16:
yield UInt16(self, "cluster_hi")
else:
yield UInt16(self, "ea_index")
yield Date(self, "modify")
yield UInt16(self, "cluster_lo")
size = UInt32(self, "size")
yield size
if self.process:
del self.process
target_size = size.value
if self["directory"].value:
if target_size:
size.error("(FAT) value must be zero")
target_size = 0
elif not target_size:
return
self.target_size = 8 * target_size
yield InodeLink(self, "data")
else:
yield UInt8(self, "seq_no", "Sequence Number")
yield String(self, "name[]", 10, "(5 UTF-16 characters)",
charset="UTF-16-LE")
yield UInt8(self, "magic", "Magic number (15)")
yield NullBytes(self, "reserved", 1, "(always 0)")
yield UInt8(self, "checksum", "Checksum of DOS file name")
yield String(self, "name[]", 12, "(6 UTF-16 characters)",
charset="UTF-16-LE")
yield UInt16(self, "first_cluster", "(always 0)")
yield String(self, "name[]", 4, "(2 UTF-16 characters)",
charset="UTF-16-LE")
class Directory(Fragment):
def createFields(self):
while self.current_size < self._size:
yield FileEntry(self, "entry[]")
class File(Fragment):
def _getData(self):
return self["data"]
def createFields(self):
yield Bytes(self, "data", self.datasize/8)
padding = self._size - self.current_size
if padding:
yield createPaddingField(self, padding)
class InodeGen:
def __init__(self, root, entry, path):
self.root = root
self.cluster = root.clusters(entry.getCluster)
self.path = path
self.filesize = entry.target_size
self.done = 0
def createInputStream(cis, **args):
args["size"] = self.filesize
args.setdefault("tags",[]).append(("filename", entry.getFilename()))
return cis(**args)
self.createInputStream = createInputStream
def __call__(self, prev):
name = self.path + "[]"
address, size, last = self.cluster.next()
if self.filesize:
if self.done >= self.filesize:
error("(FAT) bad metadata for " + self.path)
return
field = File(self.root, name, size=size)
if prev.first is None:
field._description = 'File size: %s' % humanFilesize(self.filesize//8)
field.setSubIStream(self.createInputStream)
field.datasize = min(self.filesize - self.done, size)
self.done += field.datasize
else:
field = Directory(self.root, name, size=size)
padding = self.root.getFieldByAddress(address, feed=False)
if not isinstance(padding, (PaddingBytes, RawBytes)):
error("(FAT) address %u doesn't point to a padding field" % address)
return
if last:
next = None
else:
next = lambda: self(field)
field.setLinks(prev.first, next)
self.root.writeFieldsIn(padding, address, (field,))
return field
class FAT_FS(Parser):
endian = LITTLE_ENDIAN
PARSER_TAGS = {
"category": "file_system",
"min_size": 512*8,
"file_ext": ("",),
}
def _validate(self, type_offset):
if self.stream.readBytes(type_offset*8, 8) != ("FAT%-5u" % self.version):
return "Invalid FAT%u signature" % self.version
if self.stream.readBytes(510*8, 2) != "\x55\xAA":
return "Invalid BIOS signature"
return True
def clusters(self, cluster_func):
max_entry = (1 << min(28, self.version)) - 16
cluster = cluster_func()
if 1 < cluster < max_entry:
clus_nb = 1
next = cluster
while True:
next = self.fat[next/1000][next%1000].value
if not 1 < next < max_entry:
break
if cluster + clus_nb == next:
clus_nb += 1
else:
yield self.data_start + cluster * self.cluster_size, clus_nb * self.cluster_size, False
cluster = next
clus_nb = 1
yield self.data_start + cluster * self.cluster_size, clus_nb * self.cluster_size, True
def createFields(self):
# Read boot seector
boot = Boot(self, "boot", "Boot sector")
yield boot
self.sector_size = boot["sector_size"].value
if self.version == 32:
for field in sorted((
(boot["inf_sector"].value, lambda: FSInfo(self, "fsinfo")),
(boot["boot_copy"].value, lambda: Boot(self, "bkboot", "Copy of the boot sector")),
)):
if field[0]:
padding = self.seekByte(field[0] * self.sector_size)
if padding:
yield padding
yield field[1]()
padding = self.seekByte(boot["reserved_sectors"].value * self.sector_size)
if padding:
yield padding
# Read the two FAT
fat_size = boot["fat_size"].value
if fat_size == 0:
fat_size = boot["fat32_size"].value
fat_size *= self.sector_size * 8
for i in xrange(boot["fat_nb"].value):
yield FAT(self, "fat[]", "File Allocation Table", size=fat_size)
# Read inode table (Directory)
self.cluster_size = boot["cluster_size"].value * self.sector_size * 8
self.fat = self["fat[0]"]
if "root_start" in boot:
self.target_size = 0
self.getCluster = lambda: boot["root_start"].value
yield InodeLink(self, "root", "root")
else:
yield Directory(self, "root[]", size=boot["max_root"].value * 32 * 8)
self.data_start = self.current_size - 2 * self.cluster_size
sectors = boot["sectors1"].value
if not sectors:
sectors = boot["sectors2"].value
# Create one big padding field for the end
size = sectors * self.sector_size
if self._size:
size = min(size, self.size//8)
padding = self.seekByte(size)
if padding:
yield padding
class FAT12(FAT_FS):
PARSER_TAGS = {
"id": "fat12",
"description": "FAT12 filesystem",
"magic": (("FAT12 ", 54*8),),
}
version = 12
def validate(self):
return FAT_FS._validate(self, 54)
class FAT16(FAT_FS):
PARSER_TAGS = {
"id": "fat16",
"description": "FAT16 filesystem",
"magic": (("FAT16 ", 54*8),),
}
version = 16
def validate(self):
return FAT_FS._validate(self, 54)
class FAT32(FAT_FS):
PARSER_TAGS = {
"id": "fat32",
"description": "FAT32 filesystem",
"magic": (("FAT32 ", 82*8),),
}
version = 32
def validate(self):
return FAT_FS._validate(self, 82)
+121
View File
@@ -0,0 +1,121 @@
"""
ISO 9660 (cdrom) file system parser.
Documents:
- Standard ECMA-119 (december 1987)
http://www.nondot.org/sabre/os/files/FileSystems/iso9660.pdf
Author: Victor Stinner
Creation: 11 july 2006
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, ParserError,
UInt8, UInt32, UInt64, Enum,
NullBytes, RawBytes, String)
from hachoir_core.endian import LITTLE_ENDIAN, BIG_ENDIAN
class PrimaryVolumeDescriptor(FieldSet):
static_size = 2041*8
def createFields(self):
yield NullBytes(self, "unused[]", 1)
yield String(self, "system_id", 32, "System identifier", strip=" ")
yield String(self, "volume_id", 32, "Volume identifier", strip=" ")
yield NullBytes(self, "unused[]", 8)
yield UInt64(self, "space_size", "Volume space size")
yield NullBytes(self, "unused[]", 32)
yield UInt32(self, "set_size", "Volume set size")
yield UInt32(self, "seq_num", "Sequence number")
yield UInt32(self, "block_size", "Block size")
yield UInt64(self, "path_table_size", "Path table size")
yield UInt32(self, "occu_lpath", "Location of Occurrence of Type L Path Table")
yield UInt32(self, "opt_lpath", "Location of Optional of Type L Path Table")
yield UInt32(self, "occu_mpath", "Location of Occurrence of Type M Path Table")
yield UInt32(self, "opt_mpath", "Location of Optional of Type M Path Table")
yield RawBytes(self, "root", 34, "Directory Record for Root Directory")
yield String(self, "vol_set_id", 128, "Volume set identifier", strip=" ")
yield String(self, "publisher", 128, "Publisher identifier", strip=" ")
yield String(self, "data_preparer", 128, "Data preparer identifier", strip=" ")
yield String(self, "application", 128, "Application identifier", strip=" ")
yield String(self, "copyright", 37, "Copyright file identifier", strip=" ")
yield String(self, "abstract", 37, "Abstract file identifier", strip=" ")
yield String(self, "biographic", 37, "Biographic file identifier", strip=" ")
yield String(self, "creation_ts", 17, "Creation date and time", strip=" ")
yield String(self, "modification_ts", 17, "Modification date and time", strip=" ")
yield String(self, "expiration_ts", 17, "Expiration date and time", strip=" ")
yield String(self, "effective_ts", 17, "Effective date and time", strip=" ")
yield UInt8(self, "struct_ver", "Structure version")
yield NullBytes(self, "unused[]", 1)
yield String(self, "app_use", 512, "Application use", strip=" \0")
yield NullBytes(self, "unused[]", 653)
class BootRecord(FieldSet):
static_size = 2041*8
def createFields(self):
yield String(self, "sys_id", 31, "Boot system identifier", strip="\0")
yield String(self, "boot_id", 31, "Boot identifier", strip="\0")
yield RawBytes(self, "system_use", 1979, "Boot system use")
class Terminator(FieldSet):
static_size = 2041*8
def createFields(self):
yield NullBytes(self, "null", 2041)
class Volume(FieldSet):
endian = BIG_ENDIAN
TERMINATOR = 255
type_name = {
0: "Boot Record",
1: "Primary Volume Descriptor",
2: "Supplementary Volume Descriptor",
3: "Volume Partition Descriptor",
TERMINATOR: "Volume Descriptor Set Terminator",
}
static_size = 2048 * 8
content_handler = {
0: BootRecord,
1: PrimaryVolumeDescriptor,
TERMINATOR: Terminator,
}
def createFields(self):
yield Enum(UInt8(self, "type", "Volume descriptor type"), self.type_name)
yield RawBytes(self, "signature", 5, "ISO 9960 signature (CD001)")
if self["signature"].value != "CD001":
raise ParserError("Invalid ISO 9960 volume signature")
yield UInt8(self, "version", "Volume descriptor version")
cls = self.content_handler.get(self["type"].value, None)
if cls:
yield cls(self, "content")
else:
yield RawBytes(self, "raw_content", 2048-7)
class ISO9660(Parser):
endian = LITTLE_ENDIAN
MAGIC = "\x01CD001"
NULL_BYTES = 0x8000
PARSER_TAGS = {
"id": "iso9660",
"category": "file_system",
"description": "ISO 9660 file system",
"min_size": (NULL_BYTES + 6)*8,
"magic": ((MAGIC, NULL_BYTES*8),),
}
def validate(self):
if self.stream.readBytes(self.NULL_BYTES*8, len(self.MAGIC)) != self.MAGIC:
return "Invalid signature"
return True
def createFields(self):
yield self.seekByte(self.NULL_BYTES, null=True)
while True:
volume = Volume(self, "volume[]")
yield volume
if volume["type"].value == Volume.TERMINATOR:
break
if self.current_size < self._size:
yield self.seekBit(self._size, "end")
@@ -0,0 +1,114 @@
"""
Linux swap file.
Documentation: Linux kernel source code, files:
- mm/swapfile.c
- include/linux/swap.h
Author: Victor Stinner
Creation date: 25 december 2006 (christmas ;-))
"""
from hachoir_parser import Parser
from hachoir_core.field import (ParserError, GenericVector,
UInt32, String,
Bytes, NullBytes, RawBytes)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.tools import humanFilesize
from hachoir_core.bits import str2hex
PAGE_SIZE = 4096
# Definition of MAX_SWAP_BADPAGES in Linux kernel:
# (__swapoffset(magic.magic) - __swapoffset(info.badpages)) / sizeof(int)
MAX_SWAP_BADPAGES = ((PAGE_SIZE - 10) - 1536) // 4
class Page(RawBytes):
static_size = PAGE_SIZE*8
def __init__(self, parent, name):
RawBytes.__init__(self, parent, name, PAGE_SIZE)
class UUID(Bytes):
static_size = 16*8
def __init__(self, parent, name):
Bytes.__init__(self, parent, name, 16)
def createDisplay(self):
text = str2hex(self.value, format=r"%02x")
return "%s-%s-%s-%s-%s" % (
text[:8], text[8:12], text[12:16], text[16:20], text[20:])
class LinuxSwapFile(Parser):
PARSER_TAGS = {
"id": "linux_swap",
"file_ext": ("",),
"category": "file_system",
"min_size": PAGE_SIZE*8,
"description": "Linux swap file",
"magic": (
("SWAP-SPACE", (PAGE_SIZE-10)*8),
("SWAPSPACE2", (PAGE_SIZE-10)*8),
("S1SUSPEND\0", (PAGE_SIZE-10)*8),
),
}
endian = LITTLE_ENDIAN
def validate(self):
magic = self.stream.readBytes((PAGE_SIZE-10)*8, 10)
if magic not in ("SWAP-SPACE", "SWAPSPACE2", "S1SUSPEND\0"):
return "Unknown magic string"
if MAX_SWAP_BADPAGES < self["nb_badpage"].value:
return "Invalid number of bad page (%u)" % self["nb_badpage"].value
return True
def getPageCount(self):
"""
Number of pages which can really be used for swapping:
number of page minus bad pages minus one page (used for the header)
"""
# -1 because first page is used for the header
return self["last_page"].value - self["nb_badpage"].value - 1
def createDescription(self):
if self["magic"].value == "S1SUSPEND\0":
text = "Suspend swap file version 1"
elif self["magic"].value == "SWAPSPACE2":
text = "Linux swap file version 2"
else:
text = "Linux swap file version 1"
nb_page = self.getPageCount()
return "%s, page size: %s, %s pages" % (
text, humanFilesize(PAGE_SIZE), nb_page)
def createFields(self):
# First kilobyte: boot sectors
yield RawBytes(self, "boot", 1024, "Space for disklabel etc.")
# Header
yield UInt32(self, "version")
yield UInt32(self, "last_page")
yield UInt32(self, "nb_badpage")
yield UUID(self, "sws_uuid")
yield UUID(self, "sws_volume")
yield NullBytes(self, "reserved", 117*4)
# Read bad pages (if any)
count = self["nb_badpage"].value
if count:
if MAX_SWAP_BADPAGES < count:
raise ParserError("Invalid number of bad page (%u)" % count)
yield GenericVector(self, "badpages", count, UInt32, "badpage")
# Read magic
padding = self.seekByte(PAGE_SIZE - 10, "padding", null=True)
if padding:
yield padding
yield String(self, "magic", 10, charset="ASCII")
# Read all pages
yield GenericVector(self, "pages", self["last_page"].value, Page, "page")
# Padding at the end
padding = self.seekBit(self.size, "end_padding", null=True)
if padding:
yield padding
+230
View File
@@ -0,0 +1,230 @@
"""
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_core.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 xrange(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 = "\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"
+285
View File
@@ -0,0 +1,285 @@
"""
New Technology File System (NTFS) file system parser.
Sources:
- The NTFS documentation
http://www.linux-ntfs.org/
- NTFS-3G driver
http://www.ntfs-3g.org/
Creation date: 3rd january 2007
Author: Victor Stinner
"""
SECTOR_SIZE = 512
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, Enum,
UInt8, UInt16, UInt32, UInt64, TimestampWin64,
String, Bytes, Bit,
NullBits, NullBytes, PaddingBytes, RawBytes)
from hachoir_core.endian import LITTLE_ENDIAN
from hachoir_core.text_handler import textHandler, hexadecimal, filesizeHandler
from hachoir_core.tools import humanFilesize, createDict
from hachoir_parser.common.msdos import MSDOSFileAttr32
class BiosParameterBlock(FieldSet):
"""
BIOS parameter block (bpb) structure
"""
static_size = 25 * 8
MEDIA_TYPE = {0xf8: "Hard disk"}
def createFields(self):
yield UInt16(self, "bytes_per_sector", "Size of a sector in bytes")
yield UInt8(self, "sectors_per_cluster", "Size of a cluster in sectors")
yield NullBytes(self, "reserved_sectors", 2)
yield NullBytes(self, "fats", 1)
yield NullBytes(self, "root_entries", 2)
yield NullBytes(self, "sectors", 2)
yield Enum(UInt8(self, "media_type"), self.MEDIA_TYPE)
yield NullBytes(self, "sectors_per_fat", 2)
yield UInt16(self, "sectors_per_track")
yield UInt16(self, "heads")
yield UInt32(self, "hidden_sectors")
yield NullBytes(self, "large_sectors", 4)
def validate(self):
if self["bytes_per_sector"].value not in (256, 512, 1024, 2048, 4096):
return "Invalid sector size (%u bytes)" % \
self["bytes_per_sector"].value
if self["sectors_per_cluster"].value not in (1, 2, 4, 8, 16, 32, 64, 128):
return "Invalid cluster size (%u sectors)" % \
self["sectors_per_cluster"].value
return ""
class MasterBootRecord(FieldSet):
static_size = 512*8
def createFields(self):
yield Bytes(self, "jump", 3, "Intel x86 jump instruction")
yield String(self, "name", 8)
yield BiosParameterBlock(self, "bios", "BIOS parameters")
yield textHandler(UInt8(self, "physical_drive", "(0x80)"), hexadecimal)
yield NullBytes(self, "current_head", 1)
yield textHandler(UInt8(self, "ext_boot_sig", "Extended boot signature (0x80)"), hexadecimal)
yield NullBytes(self, "unused", 1)
yield UInt64(self, "nb_sectors")
yield UInt64(self, "mft_cluster", "Cluster location of MFT data")
yield UInt64(self, "mftmirr_cluster", "Cluster location of copy of MFT")
yield UInt8(self, "cluster_per_mft", "MFT record size in clusters")
yield NullBytes(self, "reserved[]", 3)
yield UInt8(self, "cluster_per_index", "Index block size in clusters")
yield NullBytes(self, "reserved[]", 3)
yield textHandler(UInt64(self, "serial_number"), hexadecimal)
yield textHandler(UInt32(self, "checksum", "Boot sector checksum"), hexadecimal)
yield Bytes(self, "boot_code", 426)
yield Bytes(self, "mbr_magic", 2, r"Master boot record magic number (\x55\xAA)")
def createDescription(self):
size = self["nb_sectors"].value * self["bios/bytes_per_sector"].value
return "NTFS Master Boot Record (%s)" % humanFilesize(size)
class MFT_Flags(FieldSet):
static_size = 16
def createFields(self):
yield Bit(self, "in_use")
yield Bit(self, "is_directory")
yield NullBits(self, "padding", 14)
class Attribute(FieldSet):
# --- Common code ---
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = self["size"].value * 8
type = self["type"].value
if type in self.ATTR_INFO:
self._name = self.ATTR_INFO[type][0]
self._parser = self.ATTR_INFO[type][2]
def createFields(self):
yield Enum(textHandler(UInt32(self, "type"), hexadecimal), self.ATTR_NAME)
yield UInt32(self, "size")
yield UInt8(self, "non_resident", "Non-resident flag")
yield UInt8(self, "name_length", "Name length in bytes")
yield UInt16(self, "name_offset", "Name offset")
yield UInt16(self, "flags")
yield textHandler(UInt16(self, "attribute_id"), hexadecimal)
yield UInt32(self, "length_attr", "Length of the Attribute")
yield UInt16(self, "offset_attr", "Offset of the Attribute")
yield UInt8(self, "indexed_flag")
yield NullBytes(self, "padding", 1)
if self._parser:
for field in self._parser(self):
yield field
else:
size = self["length_attr"].value
if size:
yield RawBytes(self, "data", size)
size = (self.size - self.current_size) // 8
if size:
yield PaddingBytes(self, "end_padding", size)
def createDescription(self):
return "Attribute %s" % self["type"].display
FILENAME_NAMESPACE = {
0: "POSIX",
1: "Win32",
2: "DOS",
3: "Win32 & DOS",
}
# --- Parser specific to a type ---
def parseStandardInfo(self):
yield TimestampWin64(self, "ctime", "File Creation")
yield TimestampWin64(self, "atime", "File Altered")
yield TimestampWin64(self, "mtime", "MFT Changed")
yield TimestampWin64(self, "rtime", "File Read")
yield MSDOSFileAttr32(self, "file_attr", "DOS File Permissions")
yield UInt32(self, "max_version", "Maximum Number of Versions")
yield UInt32(self, "version", "Version Number")
yield UInt32(self, "class_id")
yield UInt32(self, "owner_id")
yield UInt32(self, "security_id")
yield filesizeHandler(UInt64(self, "quota_charged", "Quota Charged"))
yield UInt64(self, "usn", "Update Sequence Number (USN)")
def parseFilename(self):
yield UInt64(self, "ref", "File reference to the parent directory")
yield TimestampWin64(self, "ctime", "File Creation")
yield TimestampWin64(self, "atime", "File Altered")
yield TimestampWin64(self, "mtime", "MFT Changed")
yield TimestampWin64(self, "rtime", "File Read")
yield filesizeHandler(UInt64(self, "alloc_size", "Allocated size of the file"))
yield filesizeHandler(UInt64(self, "real_size", "Real size of the file"))
yield UInt32(self, "file_flags")
yield UInt32(self, "file_flags2", "Used by EAs and Reparse")
yield UInt8(self, "filename_length", "Filename length in characters")
yield Enum(UInt8(self, "filename_namespace"), self.FILENAME_NAMESPACE)
size = self["filename_length"].value * 2
if size:
yield String(self, "filename", size, charset="UTF-16-LE")
def parseData(self):
size = (self.size - self.current_size) // 8
if size:
yield Bytes(self, "data", size)
def parseBitmap(self):
size = (self.size - self.current_size)
for index in xrange(size):
yield Bit(self, "bit[]")
# --- Type information ---
ATTR_INFO = {
0x10: ('standard_info', 'STANDARD_INFORMATION ', parseStandardInfo),
0x20: ('attr_list', 'ATTRIBUTE_LIST ', None),
0x30: ('filename', 'FILE_NAME ', parseFilename),
0x40: ('vol_ver', 'VOLUME_VERSION', None),
0x40: ('obj_id', 'OBJECT_ID ', None),
0x50: ('security', 'SECURITY_DESCRIPTOR ', None),
0x60: ('vol_name', 'VOLUME_NAME ', None),
0x70: ('vol_info', 'VOLUME_INFORMATION ', None),
0x80: ('data', 'DATA ', parseData),
0x90: ('index_root', 'INDEX_ROOT ', None),
0xA0: ('index_alloc', 'INDEX_ALLOCATION ', None),
0xB0: ('bitmap', 'BITMAP ', parseBitmap),
0xC0: ('sym_link', 'SYMBOLIC_LINK', None),
0xC0: ('reparse', 'REPARSE_POINT ', None),
0xD0: ('ea_info', 'EA_INFORMATION ', None),
0xE0: ('ea', 'EA ', None),
0xF0: ('prop_set', 'PROPERTY_SET', None),
0x100: ('log_util', 'LOGGED_UTILITY_STREAM', None),
}
ATTR_NAME = createDict(ATTR_INFO, 1)
class File(FieldSet):
# static_size = 48*8
def __init__(self, *args):
FieldSet.__init__(self, *args)
self._size = self["bytes_allocated"].value * 8
def createFields(self):
yield Bytes(self, "signature", 4, "Usually the magic is 'FILE'")
yield UInt16(self, "usa_ofs", "Update Sequence Array offset")
yield UInt16(self, "usa_count", "Update Sequence Array count")
yield UInt64(self, "lsn", "$LogFile sequence number for this record")
yield UInt16(self, "sequence_number", "Number of times this mft record has been reused")
yield UInt16(self, "link_count", "Number of hard links")
yield UInt16(self, "attrs_offset", "Byte offset to the first attribute")
yield MFT_Flags(self, "flags")
yield UInt32(self, "bytes_in_use", "Number of bytes used in this record")
yield UInt32(self, "bytes_allocated", "Number of bytes allocated for this record")
yield UInt64(self, "base_mft_record")
yield UInt16(self, "next_attr_instance")
# The below fields are specific to NTFS 3.1+ (Windows XP and above)
yield NullBytes(self, "reserved", 2)
yield UInt32(self, "mft_record_number", "Number of this mft record")
padding = self.seekByte(self["attrs_offset"].value, relative=True)
if padding:
yield padding
while not self.eof:
addr = self.absolute_address + self.current_size
if self.stream.readBytes(addr, 4) == "\xFF\xFF\xFF\xFF":
yield Bytes(self, "attr_end_marker", 8)
break
yield Attribute(self, "attr[]")
size = self["bytes_in_use"].value - self.current_size//8
if size:
yield RawBytes(self, "end_rawdata", size)
size = (self.size - self.current_size) // 8
if size:
yield RawBytes(self, "end_padding", size, "Unused but allocated bytes")
def createDescription(self):
text = "File"
if "filename/filename" in self:
text += ' "%s"' % self["filename/filename"].value
if "filename/real_size" in self:
text += ' (%s)' % self["filename/real_size"].display
if "standard_info/file_attr" in self:
text += ', %s' % self["standard_info/file_attr"].display
return text
class NTFS(Parser):
MAGIC = "\xEB\x52\x90NTFS "
PARSER_TAGS = {
"id": "ntfs",
"category": "file_system",
"description": "NTFS file system",
"min_size": 1024*8,
"magic": ((MAGIC, 0),),
}
endian = LITTLE_ENDIAN
_cluster_size = None
def validate(self):
if self.stream.readBytes(0, len(self.MAGIC)) != self.MAGIC:
return "Invalid magic string"
err = self["mbr/bios"].validate()
if err:
return err
return True
def createFields(self):
yield MasterBootRecord(self, "mbr")
bios = self["mbr/bios"]
cluster_size = bios["sectors_per_cluster"].value * bios["bytes_per_sector"].value
offset = self["mbr/mft_cluster"].value * cluster_size
padding = self.seekByte(offset, relative=False)
if padding:
yield padding
for index in xrange(1000):
yield File(self, "file[]")
size = (self.size - self.current_size) // 8
if size:
yield RawBytes(self, "end", size)
@@ -0,0 +1,120 @@
"""
ReiserFS file system version 3 parser (version 1, 2 and 4 are not supported).
Author: Frederic Weisbecker
Creation date: 8 december 2006
Sources:
- http://p-nand-q.com/download/rfstool/reiserfs_docs.html
- http://homes.cerias.purdue.edu/~florian/reiser/reiserfs.php
- file://usr/src/linux-2.6.16.19/include/linux/reiserfs_fs.h
NOTES:
The most part of the description of the structures, their fields and their
comments decribed here comes from the file include/linux/reiserfs_fs.h
- written by Hans reiser - located in the Linux kernel 2.6.16.19 and from
the Reiserfs explanations in
http://p-nand-q.com/download/rfstool/reiserfs_docs.html written by Gerson
Kurz.
"""
from hachoir_parser import Parser
from hachoir_core.field import (FieldSet, Enum,
UInt16, UInt32, String, RawBytes, NullBytes)
from hachoir_core.endian import LITTLE_ENDIAN
class Journal_params(FieldSet):
static_size = 32*8
def createFields(self):
yield UInt32(self, "1st_block", "Journal 1st block number")
yield UInt32(self, "dev", "Journal device number")
yield UInt32(self, "size", "Size of the journal")
yield UInt32(self, "trans_max", "Max number of blocks in a transaction")
#TODO: Must be explained: it was sb_journal_block_count
yield UInt32(self, "magic", "Random value made on fs creation.")
yield UInt32(self, "max_batch", "Max number of blocks to batch into a trans")
yield UInt32(self, "max_commit_age", "In seconds, how old can an async commit be")
yield UInt32(self, "max_trans_age", "In seconds, how old can a transaction be")
def createDescription(self):
return "Parameters of the journal"
class SuperBlock(FieldSet):
static_size = 204*8
UMOUNT_STATE = { 1: "unmounted", 2: "not unmounted" }
HASH_FUNCTIONS = {
0: "UNSET_HASH",
1: "TEA_HASH",
2: "YURA_HASH",
3: "R5_HASH"
}
def createFields(self):
#TODO: This structure is normally divided in two parts:
# _reiserfs_super_block_v1
# _reiserfs_super_block
# It will be divided later to easily support older version of the first part
yield UInt32(self, "block_count", "Number of blocks")
yield UInt32(self, "free_blocks", "Number of free blocks")
yield UInt32(self, "root_block", "Root block number")
yield Journal_params(self, "Journal parameters")
yield UInt16(self, "blocksize", "Size of a block")
yield UInt16(self, "oid_maxsize", "Max size of object id array")
yield UInt16(self, "oid_cursize", "Current size of object id array")
yield Enum(UInt16(self, "umount_state", "Filesystem umounted or not"), self.UMOUNT_STATE)
yield String(self, "magic", 10, "Magic string", strip="\0")
#TODO: change the type of s_fs_state in Enum to have more details about this fsck state
yield UInt16(self, "fs_state", "Rebuilding phase of fsck ")
yield Enum(UInt32(self, "hash_function", "Hash function to sort names in a directory"), self.HASH_FUNCTIONS)
yield UInt16(self, "tree_height", "Height of disk tree")
yield UInt16(self, "bmap_nr", "Amount of bitmap blocks needed to address each block of file system")
#TODO: find a good description for this field
yield UInt16(self, "version", "Field only reliable on filesystem with non-standard journal")
yield UInt16(self, "reserved_for_journal", "Size in blocks of journal area on main device")
#TODO: same as above
yield UInt32(self, "inode_generation", "No description")
#TODO: same as above and should be an enum field
yield UInt32(self, "flags", "No description")
#TODO: Create a special Type to format this id
yield RawBytes(self, "uuid", 16, "Filesystem unique identifier")
yield String(self, "label", 16, "Filesystem volume label", strip="\0")
yield NullBytes(self, "unused", 88)
def createDescription(self):
return "Superblock: ReiserFs Filesystem"
class REISER_FS(Parser):
PARSER_TAGS = {
"id": "reiserfs",
"category": "file_system",
# 130 blocks before the journal +
# Minimal size of journal (513 blocks) +
# 1 block for the rest
# And The Minimal size of a block is 512 bytes
"min_size": (130+513+1) * (512*8),
"description": "ReiserFS file system"
}
endian = LITTLE_ENDIAN
# Offsets (in bytes) of important information
SUPERBLOCK_OFFSET = 64*1024
MAGIC_OFFSET = SUPERBLOCK_OFFSET + 52
def validate(self):
# Let's look at the magic field in the superblock
magic = self.stream.readBytes(self.MAGIC_OFFSET*8, 9).rstrip("\0")
if magic == "ReIsEr3Fs":
return True
if magic in ("ReIsEr2Fs", "ReIsErFs"):
return "Unsupported version of ReiserFs"
return "Invalid magic string"
def createFields(self):
yield NullBytes(self, "padding[]", self.SUPERBLOCK_OFFSET)
yield SuperBlock(self, "superblock")