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