Home | History | Annotate | Download | only in tools
      1 # this file contains definitions related to the Linux kernel itself
      2 #
      3 
      4 # list here the macros that you know are always defined/undefined when including
      5 # the kernel headers
      6 #
      7 import sys, cpp, re, os.path, string, time
      8 from defaults import *
      9 
     10 verboseSearch = 0
     11 verboseFind   = 0
     12 
     13 ########################################################################
     14 ########################################################################
     15 #####                                                              #####
     16 #####           H E A D E R   S C A N N E R                        #####
     17 #####                                                              #####
     18 ########################################################################
     19 ########################################################################
     20 
     21 
     22 class HeaderScanner:
     23     """a class used to non-recursively detect which Linux kernel headers are
     24        used by a given set of input source files"""
     25 
     26     # to use the HeaderScanner, do the following:
     27     #
     28     #    scanner = HeaderScanner()
     29     #    for path in <your list of files>:
     30     #        scanner.parseFile(path)
     31     #
     32     #    # get the set of Linux headers included by your files
     33     #    headers = scanner.getHeaders()
     34     #
     35     #    # get the set of of input files that do include Linux headers
     36     #    files   = scanner.getFiles()
     37     #
     38     #    note that the result of getHeaders() is a set of strings, each one
     39     #    corresponding to a non-bracketed path name, e.g.:
     40     #
     41     #        set("linux/types","asm/types.h")
     42     #
     43 
     44     # the default algorithm is pretty smart and will analyze the input
     45     # files with a custom C pre-processor in order to optimize out macros,
     46     # get rid of comments, empty lines, etc..
     47     #
     48     # this avoids many annoying false positives... !!
     49     #
     50 
     51     # this regular expression is used to detect include paths that relate to
     52     # the kernel, by default, it selects one of:
     53     #    <linux/*>
     54     #    <asm/*>
     55     #    <asm-generic/*>
     56     #    <mtd/*>
     57     #
     58     re_combined =\
     59        re.compile(r"^.*<((%s)/[\d\w_\+\.\-/]*)>.*$" % string.join(kernel_dirs,"|") )
     60     # some kernel files choose to include files with relative paths (x86 32/64
     61     # dispatch for instance)
     62     re_rel_dir = re.compile(r'^.*"([\d\w_\+\.\-/]+)".*$')
     63 
     64     def __init__(self,config={}):
     65         """initialize a HeaderScanner"""
     66         self.reset()
     67         self.config = config
     68 
     69     def reset(self,config={}):
     70         self.files    = set()  # set of files being parsed for headers
     71         self.headers  = {}     # maps headers to set of users
     72         self.config   = config
     73 
     74     def checkInclude(self, line, from_file, kernel_root=None):
     75         relative = False
     76         m = HeaderScanner.re_combined.match(line)
     77         if kernel_root and not m:
     78             m = HeaderScanner.re_rel_dir.match(line)
     79             relative = True
     80         if not m: return
     81 
     82         header = m.group(1)
     83         if from_file:
     84             self.files.add(from_file)
     85             if kernel_root and relative:
     86                 hdr_dir = os.path.realpath(os.path.dirname(from_file))
     87                 hdr_dir = hdr_dir.replace("%s/" % os.path.realpath(kernel_root),
     88                                           "")
     89                 if hdr_dir:
     90                     _prefix = "%s/" % hdr_dir
     91                 else:
     92                     _prefix = ""
     93                 header = "%s%s" % (_prefix, header)
     94 
     95         if not header in self.headers:
     96             self.headers[header] = set()
     97 
     98         if from_file:
     99             if verboseFind:
    100                 print "=== %s uses %s" % (from_file, header)
    101             self.headers[header].add(from_file)
    102 
    103     def parseFile(self, path, arch=None, kernel_root=None):
    104         """parse a given file for Linux headers"""
    105         if not os.path.exists(path):
    106             return
    107 
    108         # since tokenizing the file is very slow, we first try a quick grep
    109         # to see if this returns any meaningful results. only if this is true
    110         # do we do the tokenization"""
    111         try:
    112             f = open(path, "rt")
    113         except:
    114             print "!!! can't read '%s'" % path
    115             return
    116 
    117         hasIncludes = False
    118         for line in f:
    119             if (HeaderScanner.re_combined.match(line) or
    120                 (kernel_root and HeaderScanner.re_rel_dir.match(line))):
    121                 hasIncludes = True
    122                 break
    123 
    124         if not hasIncludes:
    125             if verboseSearch: print "::: " + path
    126             return
    127 
    128         if verboseSearch: print "*** " + path
    129 
    130         list = cpp.BlockParser().parseFile(path)
    131         if list:
    132             #list.removePrefixed("CONFIG_",self.config)
    133             macros = kernel_known_macros.copy()
    134             if kernel_root:
    135                 macros.update(self.config)
    136                 if arch and arch in kernel_default_arch_macros:
    137                     macros.update(kernel_default_arch_macros[arch])
    138             list.optimizeMacros(macros)
    139             list.optimizeIf01()
    140             includes = list.findIncludes()
    141             for inc in includes:
    142                 self.checkInclude(inc, path, kernel_root)
    143 
    144     def getHeaders(self):
    145         """return the set of all needed kernel headers"""
    146         return set(self.headers.keys())
    147 
    148     def getHeaderUsers(self,header):
    149         """return the set of all users for a given header"""
    150         return set(self.headers.get(header))
    151 
    152     def getAllUsers(self):
    153         """return a dictionary mapping heaaders to their user set"""
    154         return self.headers.copy()
    155 
    156     def getFiles(self):
    157         """returns the set of files that do include kernel headers"""
    158         return self.files.copy()
    159 
    160 
    161 ##########################################################################
    162 ##########################################################################
    163 #####                                                                #####
    164 #####           H E A D E R   F I N D E R                            #####
    165 #####                                                                #####
    166 ##########################################################################
    167 ##########################################################################
    168 
    169 
    170 class KernelHeaderFinder:
    171     """a class used to scan the kernel headers themselves."""
    172 
    173     # this is different
    174     #  from a HeaderScanner because we need to translate the path returned by
    175     #  HeaderScanner.getHeaders() into possibly architecture-specific ones.
    176     #
    177     # for example, <asm/XXXX.h> needs to be translated in <asm-ARCH/XXXX.h>
    178     # where ARCH is appropriately chosen
    179 
    180     # here's how to use this:
    181     #
    182     #    scanner = HeaderScanner()
    183     #    for path in <your list of user sources>:
    184     #        scanner.parseFile(path)
    185     #
    186     #    used_headers = scanner.getHeaders()
    187     #    finder       = KernelHeaderFinder(used_headers, [ "arm", "x86" ],
    188     #                                      "<kernel_include_path>")
    189     #    all_headers  = finder.scanForAllArchs()
    190     #
    191     #   not that the result of scanForAllArchs() is a list of relative
    192     #   header paths that are not bracketed
    193     #
    194 
    195     def __init__(self,headers,archs,kernel_root,kernel_config):
    196         """init a KernelHeaderScanner,
    197 
    198             'headers' is a list or set of headers,
    199             'archs' is a list of architectures
    200             'kernel_root' is the path to the 'include' directory
    201              of your original kernel sources
    202         """
    203 
    204         if len(kernel_root) > 0 and kernel_root[-1] != "/":
    205             kernel_root += "/"
    206         #print "using kernel_root %s" % kernel_root
    207         self.archs         = archs
    208         self.searched      = set(headers)
    209         self.kernel_root   = kernel_root
    210         self.kernel_config = kernel_config
    211         self.needed        = {}
    212         self.setArch(arch=None)
    213 
    214     def setArch(self,arch=None):
    215         self.curr_arch = arch
    216         self.arch_headers = set()
    217         if arch:
    218             self.prefix = "asm-%s/" % arch
    219         else:
    220             self.prefix = None
    221 
    222     def pathFromHeader(self,header):
    223         path = header
    224         if self.prefix and path.startswith("asm/"):
    225             path = "%s%s" % (self.prefix, path[4:])
    226         return path
    227 
    228     def pathToHeader(self,path):
    229         if self.prefix and path.startswith(self.prefix):
    230             path = "asm/%s" % path[len(self.prefix):]
    231         return "%s" % path
    232 
    233     def setSearchedHeaders(self,headers):
    234         self.searched = set(headers)
    235 
    236     def scanForArch(self):
    237         fparser   = HeaderScanner(config=self.kernel_config)
    238         workqueue = []
    239         needed    = {}
    240         for h in self.searched:
    241             path = self.pathFromHeader(h)
    242             if not path in needed:
    243                 needed[path] = set()
    244             workqueue.append(path)
    245 
    246         i = 0
    247         while i < len(workqueue):
    248             path = workqueue[i]
    249             i   += 1
    250             fparser.parseFile(self.kernel_root + path,
    251                               arch=self.curr_arch, kernel_root=self.kernel_root)
    252             for used in fparser.getHeaders():
    253                 path  = self.pathFromHeader(used)
    254                 if not path in needed:
    255                     needed[path] = set()
    256                     workqueue.append(path)
    257                 for user in fparser.getHeaderUsers(used):
    258                     needed[path].add(user)
    259 
    260         # now copy the arch-specific headers into the global list
    261         for header in needed.keys():
    262             users = needed[header]
    263             if not header in self.needed:
    264                 self.needed[header] = set()
    265 
    266             for user in users:
    267                 self.needed[header].add(user)
    268 
    269     def scanForAllArchs(self):
    270         """scan for all architectures and return the set of all needed kernel headers"""
    271         for arch in self.archs:
    272             self.setArch(arch)
    273             self.scanForArch()
    274 
    275         return set(self.needed.keys())
    276 
    277     def getHeaderUsers(self,header):
    278         """return the set of all users for a given header"""
    279         return set(self.needed[header])
    280 
    281     def getArchHeaders(self,arch):
    282         """return the set of all <asm/...> headers required by a given architecture"""
    283         return set()  # XXX: TODO
    284 
    285 #####################################################################################
    286 #####################################################################################
    287 #####                                                                           #####
    288 #####           C O N F I G   P A R S E R                                       #####
    289 #####                                                                           #####
    290 #####################################################################################
    291 #####################################################################################
    292 
    293 class ConfigParser:
    294     """a class used to parse the Linux kernel .config file"""
    295     re_CONFIG_ = re.compile(r"^(CONFIG_\w+)=(.*)$")
    296 
    297     def __init__(self):
    298         self.items = {}
    299         self.duplicates = False
    300 
    301     def parseLine(self,line):
    302         line = string.strip(line)
    303 
    304         # skip empty and comment lines
    305         if len(line) == 0 or line[0] == "#":
    306             return
    307 
    308         m = ConfigParser.re_CONFIG_.match(line)
    309         if not m: return
    310 
    311         name  = m.group(1)
    312         value = m.group(2)
    313 
    314         if name in self.items:  # aarg, duplicate value
    315             self.duplicates = True
    316 
    317         self.items[name] = value
    318 
    319     def parseFile(self,path):
    320         f = file(path, "r")
    321         for line in f:
    322             if len(line) > 0:
    323                 if line[-1] == "\n":
    324                     line = line[:-1]
    325                     if len(line) > 0 and line[-1] == "\r":
    326                         line = line[:-1]
    327                 self.parseLine(line)
    328         f.close()
    329 
    330     def getDefinitions(self):
    331         """retrieve a dictionary containing definitions for CONFIG_XXX"""
    332         return self.items.copy()
    333 
    334     def __repr__(self):
    335         return repr(self.items)
    336 
    337     def __str__(self):
    338         return str(self.items)
    339