1# Copyright 2014 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import gzip, logging, os, re
6from autotest_lib.client.bin import utils
7from autotest_lib.client.common_lib import error
8
9class KernelConfig():
10    """
11    Parse the kernel config and enable us to query it.
12    Used to verify the kernel config (see kernel_ConfigVerify).
13    """
14
15    def _passed(self, msg):
16        logging.info('ok: %s', msg)
17
18    def _failed(self, msg):
19        logging.error('FAIL: %s', msg)
20        self._failures.append(msg)
21
22    def failures(self):
23        """Return the list of failures that occured during the test.
24
25        @return a list of string describing errors that occured since
26                initialization.
27        """
28        return self._failures
29
30    def _fatal(self, msg):
31        logging.error('FATAL: %s', msg)
32        raise error.TestError(msg)
33
34    def get(self, key, default):
35        """Get the value associated to key or default if it does not exist
36
37        @param key: key to look for.
38        @param default: value returned if key is not set in self._config
39        """
40        return self._config.get(key, default)
41
42    def _config_required(self, name, wanted):
43        value = self._config.get(name, None)
44        if value in wanted:
45            self._passed('"%s" was "%s" in kernel config' % (name, value))
46        else:
47            states = []
48            for state in wanted:
49                if state == None:
50                    states.append("unset")
51                else:
52                    states.append(state)
53            self._failed('"%s" was "%s" (wanted one of "%s") in kernel config' %
54                         (name, value, '|'.join(states)))
55
56    def has_value(self, name, value):
57        """Determine if the name config item has a specific value.
58
59        @param name: name of config item to test
60        @param value: value expected for the given config name
61        """
62        self._config_required('CONFIG_%s' % (name), value)
63
64    def has_builtin(self, name):
65        """Check if the specific config item is built-in (present but not
66        built as a module).
67
68        @param name: name of config item to test
69        """
70        self.has_value(name, ['y'])
71
72    def has_module(self, name):
73        """Check if the specific config item is a module (present but not
74        built-in).
75
76        @param name: name of config item to test
77        """
78        self.has_value(name, ['m'])
79
80    def is_enabled(self, name):
81        """Check if the specific config item is present (either built-in or
82        a module).
83
84        @param name: name of config item to test
85        """
86        self.has_value(name, ['y', 'm'])
87
88    def is_missing(self, name):
89        """Check if the specific config item is not present (neither built-in
90        nor a module).
91
92        @param name: name of config item to test
93        """
94        self.has_value(name, [None])
95
96    def is_exclusive(self, exclusive):
97        """Given a config item regex, make sure only the expected items
98        are present in the kernel configs.
99
100        @param exclusive: hash containing "missing", "builtin", "module",
101                          each to be checked with the corresponding has_*
102                          function based on config items matching the
103                          "regex" value.
104        """
105        expected = set()
106        for name in exclusive['missing']:
107            self.is_missing(name)
108        for name in exclusive['builtin']:
109            self.has_builtin(name)
110            expected.add('CONFIG_%s' % (name))
111        for name in exclusive['module']:
112            self.has_module(name)
113            expected.add('CONFIG_%s' % (name))
114
115        # Now make sure nothing else with the specified regex exists.
116        regex = r'CONFIG_%s' % (exclusive['regex'])
117        for name in self._config:
118            if not re.match(regex, name):
119                continue
120            if not name in expected:
121                self._failed('"%s" found for "%s" when only "%s" allowed' %
122                             (name, regex, "|".join(expected)))
123
124    def _open_config(self):
125        """Open the kernel's build config file. Attempt to use the built-in
126        symbols from /proc first, then fall back to looking for a text file
127        in /boot.
128
129        @return fileobj for open config file
130        """
131        filename = '/proc/config.gz'
132        if not os.path.exists(filename):
133            utils.system("modprobe configs", ignore_status=True)
134        if os.path.exists(filename):
135            return gzip.open(filename, "r")
136
137        filename = '/boot/config-%s' % utils.system_output('uname -r')
138        if os.path.exists(filename):
139            logging.info('Falling back to reading %s', filename)
140            return file(filename, "r")
141
142        self._fatal("Cannot locate suitable kernel config file")
143
144    def initialize(self):
145        """Load the kernel configuration and parse it.
146        """
147        fileobj = self._open_config()
148        # Import kernel config variables into a dictionary for each searching.
149        config = dict()
150        for item in fileobj.readlines():
151            item = item.strip()
152            if not '=' in item:
153                continue
154            key, value = item.split('=', 1)
155            config[key] = value
156
157        # Make sure we actually loaded something sensible.
158        if len(config) == 0:
159            self._fatal('No CONFIG variables found!')
160
161        self._config = config
162        self._failures = []
163
164