#
# Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved.
# This component and the accompanying materials are made available
# under the terms of "Eclipse Public License v1.0"
# which accompanies this distribution, and is available
# at the URL "http://www.eclipse.org/legal/epl-v10.html".
#
# Initial Contributors:
# Nokia Corporation - initial contribution.
#
# Contributors:
#
# Description:
# Generate checksums for different types of files
#
import sys
import os
import re
import struct
import subprocess
import platform
try:
from hashlib import md5
except ImportError:
import md5
import SymbianUtils
class Evalid(object):
'''
Provide some of the functionality found in epoc32/tools/EvalidCompare.pm
generateSignature() - use appropriate method to calculate checksum for a file
getUid() - extract Uid from file
'''
class ExternalError(SymbianUtils.SymbianUtilsError):
""" An external utility exited with an error """
ext = ".exe" if platform.system() == "Windows" else ""
binPath = os.path.normpath(os.path.join(os.path.dirname(__file__), "bin"))
NM = os.path.join(binPath, "nm" + ext)
ELFDUMP = os.path.join(binPath, "elfdump" + ext)
ELF2E32 = os.path.join(binPath, "elf2e32" + ext)
PE_DUMP = os.path.join(binPath, "pe_dump" + ext)
ELF_DLL_NAME = re.compile(r"#<DLL>(\S+\.\S+)#<\\DLL>")
ELF_DEBUG = re.compile(r"^\.(rel\.)?debug_")
ELF_P_HEAD = re.compile(r"^\tProgram header offset.*$")
ELF_S_HEAD = re.compile(r"^\tSection header offset.*$")
PRECOMP_IGNORE = re.compile(r'^# \d+ ".*"( \d)?$')
E32_EMPTY = re.compile("Time Stamp:|E32ImageFile|Header CRC:")
E32_LOWER = re.compile("imports from")
INTEL_OBJECTPATH_WIN = re.compile(r"\.\.\\[^(]*\\")
INTEL_OBJECTPATH_NIX = re.compile("\.\.\/[^(]*\/")
INTEL_DLLTOOL = re.compile("^(.+ (_head|_))\w+_(EPOC32_\w+(_LIB|_iname))$", re.I)
@classmethod
def typeLookup(cls, type):
'''
Return the internally used identifier string for the type
@todo: Warning
@param type: The type
@type type: String
@return: Internally used type identifier
@rtype: String
'''
if type in ("e32", "default", "elf", "preprocessed_text", "intel", "intel_pe"):
return type
elif type in ("file", "symbol"):
return "default"
elif type in ("staticlib", "dso"):
return "elf"
elif type in ("exe", "plugin", "dll"):
return "e32"
else:
#sys.stderr.write("warning - unknown hashtype %s.\n"%type)
return "default"
@classmethod
def generateSignature(cls, path, fileType):
'''
Generic dispatcher method for file types. Use the appropriate method for
I{type} to generate the signature for file at I{path}.
@param path: The path where the file is located
@type path: String
@return: checksum
@rtype: String
'''
if not isinstance(path, basestring):
raise TypeError, "path must be a string"
if not path:
raise ValueError, "path must not be zero length"
path = os.path.normpath(path)
fileType = cls.typeLookup(fileType)
methodName = "sig_" + fileType
if hasattr(cls, methodName):
method = getattr(cls, methodName)
return method(path)
else:
raise NotImplementedError("No signature generator for type %s" % fileType)
@staticmethod
def getUid(index, file):
'''Get UID of file
@param index: Which UID
@param file: Absolute path
@return: UID
@rtype: String
'''
if index not in (1, 2, 3):
raise ValueError("Index can only be one of 1, 2 or 3")
if os.path.getsize(file) < 12:
return None
start = (index-1) * 4
finish = start + 4
f = open(file, "rb")
head = f.read(12)
f.close()
return struct.unpack("<l", head[start:finish])[0]
@staticmethod
def getMd5():
'''A convenicence method to use appropriate library regardless of Python
version. Maintain compatibility while using hashlib whenever possible.
@return: md5 object
@rtype: md5
'''
if hasattr(md5, "new"):
return md5.new()
else:
return md5()
# Signatures for various formats
@classmethod
def sig_e32(cls, path):
'''
Return the checksum of significant parts using elf2e32
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
bin = cls.ELF2E32 + " --dump --e32input="
m = cls.getMd5()
fo = os.popen(bin+path, "r", -1)
for line in fo:
if cls.E32_EMPTY.search(line):
line = ""
if cls.E32_LOWER.search(line):
line = line.lower()
m.update(line)
if fo.close():
raise cls.ExternalError("elf2e32 failed at %s" % path)
return m.hexdigest()
@classmethod
def sig_default(cls, path):
'''
Calculate the checksum of the file without filtering.
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
m = cls.getMd5()
f = open(path, "rb")
while True:
buf = f.read(32*1024)
if not buf:
break
m.update(buf)
f.close()
return m.hexdigest()
@classmethod
def sig_elf(cls, path):
'''
Return the checksum of significant parts using elfdump
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
bin = cls.ELFDUMP + " -i "
def firstGroupToLc(match):
return ("#<DLL>" + match.group(1).lower() + "#<\\DLL>")
m = cls.getMd5()
fo = os.popen(bin+path, "r", -1)
for line in fo:
if cls.ELF_P_HEAD.match(line):
line = "Program header offset\n"
if cls.ELF_S_HEAD.match(line):
line = "Section header offset\n"
line = cls.ELF_DLL_NAME.sub(firstGroupToLc, line)
if cls.ELF_DEBUG.match(line):
line = ""
#sys.stderr.write(line)
m.update(line)
if fo.close():
raise cls.ExternalError("elfdump failed at %s" % path)
return m.hexdigest()
@classmethod
def sig_preprocessed_text(cls, path):
'''
Return the checksum of significant parts of preprocessed text
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
m = cls.getMd5()
f = open(path, "rb")
for line in f:
line = line.replace("\r\n", "\n")
if cls.PRECOMP_IGNORE.search(line):
line = "\n"
m.update(line)
f.close()
return m.hexdigest()
@classmethod
def sig_intel_pe(cls, path):
'''
Return the checksum of significant parts of pe_dump output
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
m = cls.getMd5()
fo = os.popen("%s %s" % (cls.PE_DUMP, path), "r", -1)
for line in fo:
m.update(line)
if fo.close():
raise cls.ExternalError("pe_dump failed at %s" % path)
return m.hexdigest()
@classmethod
def sig_intel(cls, path):
'''
Return the checksum of significant parts using nm
@param path: The absolute path
@type path: String
@return: checksum
@rtype: String
'''
m = cls.getMd5()
try:
s = subprocess.Popen([cls.NM, "--no-sort", path], env=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = s.communicate()
except OSError, e:
raise cls.ExternalError, "nm failed at %s: %s" % (path, str(e))
if s.returncode != 0:
raise cls.ExternalError, "nm failed at %s: %s" % (path, err)
for line in out.splitlines():
# no need for regexps here
if line.endswith(":\n") \
or line.startswith("BFD: ") \
or cls.INTEL_OBJECTPATH_WIN.search(line) \
or cls.INTEL_OBJECTPATH_NIX.search(line):
line = "\n"
match = cls.INTEL_DLLTOOL.search(line)
if match:
line = "%s_..._%s" % (match.groups()[0], match.groups()[2])
line = line.upper()
m.update(line)
if s.returncode != 0:
raise cls.ExternalError("nm failed at %s" % path)
return m.hexdigest()
def main():
path = sys.argv[1]
ftype = sys.argv[2]
print Evalid.generateSignature(path, ftype)
if __name__ == "__main__":
main()