releasing/blocks/framework/src/SymbianUtils/Evalid.py
author kelvzhu
Thu, 02 Sep 2010 15:02:14 +0800
changeset 632 934f9131337b
permissions -rw-r--r--
Delivery Blocks to SF

#
# 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()