Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user