1# Copyright (c) 2010 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 logging, os
6from autotest_lib.client.common_lib import error
7from autotest_lib.client.cros import cros_logging
8from autotest_lib.client.cros.crash_test import CrashTest as CrashTestDefs
9from autotest_lib.server import autotest, site_host_attributes, test
10
11_CONSENT_FILE = '/home/chronos/Consent To Send Stats'
12_STOWED_CONSENT_FILE = '/var/lib/kernel-crash-server.consent'
13
14
15class logging_KernelCrashServer(test.test):
16    """
17    Prepares a system for generating a kernel crash report, then crashes
18    the system and call logging_KernelCrash client autotest to validate
19    the resulting report.
20    """
21    version = 1
22
23    def _exact_copy(self, source, dest):
24        """Copy remote source to dest, where dest removed if src not present."""
25        self._host.run('rm -f "%s"; cp "%s" "%s" 2>/dev/null; true' %
26                       (dest, source, dest))
27
28    def _exists_on_client(self, f):
29        return self._host.run('ls "%s"' % f,
30                               ignore_status=True).exit_status == 0
31
32    # Taken from KernelErrorPaths, which duplicates it, but is up to date
33    def _enable_consent(self):
34        """ Enable consent so that crashes get stored in /var/spool/crash. """
35        self._consent_files = [
36            (CrashTestDefs._PAUSE_FILE, None, 'chronos'),
37            (CrashTestDefs._CONSENT_FILE, None, 'chronos'),
38            (CrashTestDefs._POLICY_FILE, 'mock_metrics_on.policy', 'root'),
39            (CrashTestDefs._OWNER_KEY_FILE, 'mock_metrics_owner.key', 'root'),
40            ]
41        for dst, src, owner in self._consent_files:
42            if self._exists_on_client(dst):
43                self._host.run('mv "%s" "%s.autotest_backup"' % (dst, dst))
44            if src:
45                full_src = os.path.join(self.autodir, 'client/cros', src)
46                self._host.send_file(full_src, dst)
47            else:
48                self._host.run('touch "%s"' % dst)
49            self._host.run('chown "%s" "%s"' % (owner, dst))
50
51    def _restore_consent_files(self):
52        """ Restore consent files to their previous values. """
53        for f, _, _ in self._consent_files:
54            self._host.run('rm -f "%s"' % f)
55            if self._exists_on_client('%s.autotest_backup' % f):
56                self._host.run('mv "%s.autotest_backup" "%s"' % (f, f))
57
58    def cleanup(self):
59        self._exact_copy(_STOWED_CONSENT_FILE, _CONSENT_FILE)
60        test.test.cleanup(self)
61
62
63    def _can_disable_consent(self):
64        """Returns whether or not host can have consent disabled.
65
66        Presence of /etc/send_metrics causes ui.conf job (which starts
67        after chromeos_startup) to regenerate a consent file if one
68        does not exist.  Therefore, we cannot guarantee that
69        crash-reporter.conf will start with the file gone if we
70        removed it before causing a crash.
71
72        Presence of /root/.leave_core causes crash_reporter to always
73        handle crashes, so consent cannot be disabled in this case too.
74        """
75        always_regen = self._host.run('[ -r /etc/send_metrics ]',
76                                      ignore_status=True).exit_status == 0
77        is_devimg = self._host.run('[ -r /root/.leave_core ]',
78                                   ignore_status=True).exit_status == 0
79        logging.info('always_regen: %d', always_regen)
80        logging.info('is_devimg: %d', is_devimg)
81        return not (always_regen or is_devimg)
82
83
84    def _crash_it(self, consent):
85        """Crash the host after setting the consent as given."""
86        if consent:
87            self._enable_consent()
88        else:
89            self._restore_consent_files()
90        logging.info('KernelCrashServer: crashing %s', self._host.hostname)
91        lkdtm = "/sys/kernel/debug/provoke-crash/DIRECT"
92        if self._exists_on_client(lkdtm):
93            cmd = "echo BUG > %s" % (lkdtm)
94        else:
95            cmd = "echo bug > /proc/breakme"
96            logging.info("Falling back to using /proc/breakme")
97        boot_id = self._host.get_boot_id()
98        self._host.run('sh -c "sync; sleep 1; %s" >/dev/null 2>&1 &' % (cmd))
99        self._host.wait_for_restart(old_boot_id=boot_id)
100
101
102    def _run_while_paused(self, host):
103        self._host = host
104        client_at = autotest.Autotest(host)
105        self._exact_copy(_CONSENT_FILE, _STOWED_CONSENT_FILE)
106
107        client_at.run_test('logging_KernelCrash',
108                           tag='before-crash',
109                           is_before=True,
110                           consent=True)
111
112        client_attributes = site_host_attributes.HostAttributes(host.hostname)
113        if not client_attributes.has_chromeos_firmware:
114            raise error.TestNAError(
115                'This device is unable to report kernel crashes')
116
117        self._crash_it(True)
118
119        # Check for crash handling with consent.
120        client_at.run_test('logging_KernelCrash',
121                           tag='after-crash-consent',
122                           is_before=False,
123                           consent=True)
124
125        if not self._can_disable_consent():
126            logging.info('This device always has metrics enabled, '
127                         'skipping test of metrics disabled mode.')
128        else:
129            self._crash_it(False)
130
131            # Check for crash handling without consent.
132            client_at.run_test('logging_KernelCrash',
133                               tag='after-crash-no-consent',
134                               is_before=False,
135                               consent=False)
136
137    def run_once(self, host=None):
138        # For the entire duration of this server test (across crashes
139        # and boots after crashes) we want to disable log rotation.
140        log_pauser = cros_logging.LogRotationPauser(host)
141        try:
142            log_pauser.begin()
143            self._run_while_paused(host)
144        finally:
145            log_pauser.end()
146