1*9c5db199SXin Li# Copyright 2014 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport logging 6*9c5db199SXin Liimport os 7*9c5db199SXin Li 8*9c5db199SXin Liimport common 9*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 10*9c5db199SXin Li 11*9c5db199SXin Li""" 12*9c5db199SXin LiFunctions to query and control debugd dev tools. 13*9c5db199SXin Li 14*9c5db199SXin LiThis file provides a set of functions to check the general state of the 15*9c5db199SXin Lidebugd dev tools, and a set of classes to interface to the individual 16*9c5db199SXin Litools. 17*9c5db199SXin Li 18*9c5db199SXin LiCurrent tool classes are: 19*9c5db199SXin Li RootfsVerificationTool 20*9c5db199SXin Li BootFromUsbTool 21*9c5db199SXin Li SshServerTool 22*9c5db199SXin Li SystemPasswordTool 23*9c5db199SXin LiThese classes have functions to check the state and enable/disable the 24*9c5db199SXin Litool. Some tools may not be able to disable themselves, in which case 25*9c5db199SXin Lian exception will be thrown (for example, RootfsVerificationTool cannot 26*9c5db199SXin Libe disabled). 27*9c5db199SXin Li 28*9c5db199SXin LiGeneral usage will look something like this: 29*9c5db199SXin Li 30*9c5db199SXin Li# Make sure tools are accessible on the system. 31*9c5db199SXin Liif debugd_dev_tools.are_dev_tools_available(host): 32*9c5db199SXin Li # Create the tool(s) you want to interact with. 33*9c5db199SXin Li tools = [debugd_dev_tools.SshServerTool(), ...] 34*9c5db199SXin Li for tool in tools: 35*9c5db199SXin Li # Initialize tools and save current state. 36*9c5db199SXin Li tool.initialize(host, save_initial_state=True) 37*9c5db199SXin Li # Perform required action with tools. 38*9c5db199SXin Li tool.enable() 39*9c5db199SXin Li # Restore initial tool state. 40*9c5db199SXin Li tool.restore_state() 41*9c5db199SXin Li # Clean up temporary files. 42*9c5db199SXin Li debugd_dev_tools.remove_temp_files() 43*9c5db199SXin Li""" 44*9c5db199SXin Li 45*9c5db199SXin Li 46*9c5db199SXin Li# Defined in system_api/dbus/service_constants.h. 47*9c5db199SXin LiDEV_FEATURES_DISABLED = 1 << 0 48*9c5db199SXin LiDEV_FEATURE_ROOTFS_VERIFICATION_REMOVED = 1 << 1 49*9c5db199SXin LiDEV_FEATURE_BOOT_FROM_USB_ENABLED = 1 << 2 50*9c5db199SXin LiDEV_FEATURE_SSH_SERVER_CONFIGURED = 1 << 3 51*9c5db199SXin LiDEV_FEATURE_DEV_MODE_ROOT_PASSWORD_SET = 1 << 4 52*9c5db199SXin LiDEV_FEATURE_SYSTEM_ROOT_PASSWORD_SET = 1 << 5 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Li# Location to save temporary files to store and load state. This folder should 56*9c5db199SXin Li# be persistent through a power cycle so we can't use /tmp. 57*9c5db199SXin Li_TEMP_DIR = '/usr/local/autotest/tmp/debugd_dev_tools' 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Liclass AccessError(error.CmdError): 61*9c5db199SXin Li """Raised when debugd D-Bus access fails.""" 62*9c5db199SXin Li pass 63*9c5db199SXin Li 64*9c5db199SXin Li 65*9c5db199SXin Liclass FeatureUnavailableError(error.TestNAError): 66*9c5db199SXin Li """Raised when a feature cannot be enabled or disabled.""" 67*9c5db199SXin Li pass 68*9c5db199SXin Li 69*9c5db199SXin Li 70*9c5db199SXin Lidef query_dev_tools_state(host): 71*9c5db199SXin Li """ 72*9c5db199SXin Li Queries debugd for the current dev features state. 73*9c5db199SXin Li 74*9c5db199SXin Li @param host: Host device. 75*9c5db199SXin Li 76*9c5db199SXin Li @return: Integer debugd query return value. 77*9c5db199SXin Li 78*9c5db199SXin Li @raise AccessError: Can't talk to debugd on the host. 79*9c5db199SXin Li """ 80*9c5db199SXin Li result = _send_debugd_command(host, 'QueryDevFeatures') 81*9c5db199SXin Li state = int(result.stdout) 82*9c5db199SXin Li logging.debug('query_dev_tools_state = %d (0x%04X)', state, state) 83*9c5db199SXin Li return state 84*9c5db199SXin Li 85*9c5db199SXin Li 86*9c5db199SXin Lidef are_dev_tools_available(host): 87*9c5db199SXin Li """ 88*9c5db199SXin Li Check if dev tools are available on the host. 89*9c5db199SXin Li 90*9c5db199SXin Li @param host: Host device. 91*9c5db199SXin Li 92*9c5db199SXin Li @return: True if tools are available, False otherwise. 93*9c5db199SXin Li """ 94*9c5db199SXin Li try: 95*9c5db199SXin Li return query_dev_tools_state(host) != DEV_FEATURES_DISABLED 96*9c5db199SXin Li except AccessError: 97*9c5db199SXin Li return False 98*9c5db199SXin Li 99*9c5db199SXin Li 100*9c5db199SXin Lidef remove_temp_files(host): 101*9c5db199SXin Li """ 102*9c5db199SXin Li Removes all DevTools temporary files and directories. 103*9c5db199SXin Li 104*9c5db199SXin Li Any test using dev tools should try to call this just before 105*9c5db199SXin Li exiting to erase any temporary files that may have been saved. 106*9c5db199SXin Li 107*9c5db199SXin Li @param host: Host device. 108*9c5db199SXin Li """ 109*9c5db199SXin Li host.run('rm -rf "%s"' % _TEMP_DIR) 110*9c5db199SXin Li 111*9c5db199SXin Li 112*9c5db199SXin Lidef expect_access_failure(host, tools): 113*9c5db199SXin Li """ 114*9c5db199SXin Li Verifies that access is denied to all provided tools. 115*9c5db199SXin Li 116*9c5db199SXin Li Will check are_dev_tools_available() first to try to avoid changing 117*9c5db199SXin Li device state in case access is allowed. Otherwise, the function 118*9c5db199SXin Li will try to enable each tool in the list and throw an exception if 119*9c5db199SXin Li any succeeds. 120*9c5db199SXin Li 121*9c5db199SXin Li @param host: Host device. 122*9c5db199SXin Li @param tools: List of tools to checks. 123*9c5db199SXin Li 124*9c5db199SXin Li @raise TestFail: are_dev_tools_available() returned True or 125*9c5db199SXin Li a tool successfully enabled. 126*9c5db199SXin Li """ 127*9c5db199SXin Li if are_dev_tools_available(host): 128*9c5db199SXin Li raise error.TestFail('Unexpected dev tool access success') 129*9c5db199SXin Li for tool in tools: 130*9c5db199SXin Li try: 131*9c5db199SXin Li tool.enable() 132*9c5db199SXin Li except AccessError: 133*9c5db199SXin Li # We want an exception, otherwise the tool succeeded. 134*9c5db199SXin Li pass 135*9c5db199SXin Li else: 136*9c5db199SXin Li raise error.TestFail('Unexpected %s enable success.' % tool) 137*9c5db199SXin Li 138*9c5db199SXin Li 139*9c5db199SXin Lidef _send_debugd_command(host, name, args=()): 140*9c5db199SXin Li """ 141*9c5db199SXin Li Sends a debugd command. 142*9c5db199SXin Li 143*9c5db199SXin Li @param host: Host to run the command on. 144*9c5db199SXin Li @param name: String debugd D-Bus function name. 145*9c5db199SXin Li @param args: List of string arguments to pass to dbus-send. 146*9c5db199SXin Li 147*9c5db199SXin Li @return: The dbus-send CmdResult object. 148*9c5db199SXin Li 149*9c5db199SXin Li @raise AccessError: debugd call returned an error. 150*9c5db199SXin Li """ 151*9c5db199SXin Li command = ('dbus-send --system --fixed --print-reply ' 152*9c5db199SXin Li '--dest=org.chromium.debugd /org/chromium/debugd ' 153*9c5db199SXin Li '"org.chromium.debugd.%s"' % name) 154*9c5db199SXin Li for arg in args: 155*9c5db199SXin Li command += ' %s' % arg 156*9c5db199SXin Li try: 157*9c5db199SXin Li return host.run(command) 158*9c5db199SXin Li except error.CmdError as e: 159*9c5db199SXin Li raise AccessError(e.command, e.result_obj, e.additional_text) 160*9c5db199SXin Li 161*9c5db199SXin Li 162*9c5db199SXin Liclass DevTool(object): 163*9c5db199SXin Li """ 164*9c5db199SXin Li Parent tool class. 165*9c5db199SXin Li 166*9c5db199SXin Li Each dev tool has its own child class that handles the details 167*9c5db199SXin Li of disabling, enabling, and querying the functionality. This class 168*9c5db199SXin Li provides some common functionality needed by multiple tools. 169*9c5db199SXin Li 170*9c5db199SXin Li Child classes should implement the following: 171*9c5db199SXin Li - is_enabled(): use debugd to query whether the tool is enabled. 172*9c5db199SXin Li - enable(): use debugd to enable the tool. 173*9c5db199SXin Li - disable(): manually disable the tool. 174*9c5db199SXin Li - save_state(): record the current tool state on the host. 175*9c5db199SXin Li - restore_state(): restore the saved tool state. 176*9c5db199SXin Li 177*9c5db199SXin Li If a child class cannot perform the required action (for 178*9c5db199SXin Li example the rootfs tool can't currently restore its initial 179*9c5db199SXin Li state), leave the function unimplemented so it will throw an 180*9c5db199SXin Li exception if a test attempts to use it. 181*9c5db199SXin Li """ 182*9c5db199SXin Li 183*9c5db199SXin Li 184*9c5db199SXin Li def initialize(self, host, save_initial_state=False): 185*9c5db199SXin Li """ 186*9c5db199SXin Li Sets up the initial tool state. This must be called on 187*9c5db199SXin Li every tool before use. 188*9c5db199SXin Li 189*9c5db199SXin Li @param host: Device host the test is running on. 190*9c5db199SXin Li @param save_initial_state: True to save the device state. 191*9c5db199SXin Li """ 192*9c5db199SXin Li self._host = host 193*9c5db199SXin Li if save_initial_state: 194*9c5db199SXin Li self.save_state() 195*9c5db199SXin Li 196*9c5db199SXin Li 197*9c5db199SXin Li def is_enabled(self): 198*9c5db199SXin Li """ 199*9c5db199SXin Li Each tool should override this to query itself using debugd. 200*9c5db199SXin Li Normally this can be done by using the provided 201*9c5db199SXin Li _check_enabled() function. 202*9c5db199SXin Li """ 203*9c5db199SXin Li self._unimplemented_function_error('is_enabled') 204*9c5db199SXin Li 205*9c5db199SXin Li 206*9c5db199SXin Li def enable(self): 207*9c5db199SXin Li """ 208*9c5db199SXin Li Each tool should override this to enable itself using debugd. 209*9c5db199SXin Li """ 210*9c5db199SXin Li self._unimplemented_function_error('enable') 211*9c5db199SXin Li 212*9c5db199SXin Li 213*9c5db199SXin Li def disable(self): 214*9c5db199SXin Li """ 215*9c5db199SXin Li Each tool should override this to disable itself. 216*9c5db199SXin Li """ 217*9c5db199SXin Li self._unimplemented_function_error('disable') 218*9c5db199SXin Li 219*9c5db199SXin Li 220*9c5db199SXin Li def save_state(self): 221*9c5db199SXin Li """ 222*9c5db199SXin Li Save the initial tool state. Should be overridden by child 223*9c5db199SXin Li tool classes. 224*9c5db199SXin Li """ 225*9c5db199SXin Li self._unimplemented_function_error('_save_state') 226*9c5db199SXin Li 227*9c5db199SXin Li 228*9c5db199SXin Li def restore_state(self): 229*9c5db199SXin Li """ 230*9c5db199SXin Li Restore the initial tool state. Should be overridden by child 231*9c5db199SXin Li tool classes. 232*9c5db199SXin Li """ 233*9c5db199SXin Li self._unimplemented_function_error('_restore_state') 234*9c5db199SXin Li 235*9c5db199SXin Li 236*9c5db199SXin Li def _check_enabled(self, bits): 237*9c5db199SXin Li """ 238*9c5db199SXin Li Checks if the given feature is currently enabled according to 239*9c5db199SXin Li the debugd status query function. 240*9c5db199SXin Li 241*9c5db199SXin Li @param bits: Integer status bits corresponding to the features. 242*9c5db199SXin Li 243*9c5db199SXin Li @return: True if the status query is enabled and the 244*9c5db199SXin Li indicated bits are all set, False otherwise. 245*9c5db199SXin Li """ 246*9c5db199SXin Li state = query_dev_tools_state(self._host) 247*9c5db199SXin Li enabled = bool((state != DEV_FEATURES_DISABLED) and 248*9c5db199SXin Li (state & bits == bits)) 249*9c5db199SXin Li logging.debug('%s _check_enabled = %s (0x%04X / 0x%04X)', 250*9c5db199SXin Li self, enabled, state, bits) 251*9c5db199SXin Li return enabled 252*9c5db199SXin Li 253*9c5db199SXin Li 254*9c5db199SXin Li def _get_temp_path(self, source_path): 255*9c5db199SXin Li """ 256*9c5db199SXin Li Get temporary storage path for a file or directory. 257*9c5db199SXin Li 258*9c5db199SXin Li Temporary path is based on the tool class name and the 259*9c5db199SXin Li source directory to keep tool files isolated and prevent 260*9c5db199SXin Li name conflicts within tools. 261*9c5db199SXin Li 262*9c5db199SXin Li The function returns a full temporary path corresponding to 263*9c5db199SXin Li |source_path|. 264*9c5db199SXin Li 265*9c5db199SXin Li For example, _get_temp_path('/foo/bar.txt') would return 266*9c5db199SXin Li '/path/to/temp/folder/debugd_dev_tools/FooTool/foo/bar.txt'. 267*9c5db199SXin Li 268*9c5db199SXin Li @param source_path: String path to the file or directory. 269*9c5db199SXin Li 270*9c5db199SXin Li @return: Temp path string. 271*9c5db199SXin Li """ 272*9c5db199SXin Li return '%s/%s/%s' % (_TEMP_DIR, self, source_path) 273*9c5db199SXin Li 274*9c5db199SXin Li 275*9c5db199SXin Li def _save_files(self, paths): 276*9c5db199SXin Li """ 277*9c5db199SXin Li Saves a set of files to a temporary location. 278*9c5db199SXin Li 279*9c5db199SXin Li This can be used to save specific files so that a tool can 280*9c5db199SXin Li save its current state before starting a test. 281*9c5db199SXin Li 282*9c5db199SXin Li See _restore_files() for restoring the saved files. 283*9c5db199SXin Li 284*9c5db199SXin Li @param paths: List of string paths to save. 285*9c5db199SXin Li """ 286*9c5db199SXin Li for path in paths: 287*9c5db199SXin Li temp_path = self._get_temp_path(path) 288*9c5db199SXin Li self._host.run('mkdir -p "%s"' % os.path.dirname(temp_path)) 289*9c5db199SXin Li self._host.run('cp -r "%s" "%s"' % (path, temp_path), 290*9c5db199SXin Li ignore_status=True) 291*9c5db199SXin Li 292*9c5db199SXin Li 293*9c5db199SXin Li def _restore_files(self, paths): 294*9c5db199SXin Li """ 295*9c5db199SXin Li Restores saved files to their original location. 296*9c5db199SXin Li 297*9c5db199SXin Li Used to restore files that have previously been saved by 298*9c5db199SXin Li _save_files(), usually to return the device to its initial 299*9c5db199SXin Li state. 300*9c5db199SXin Li 301*9c5db199SXin Li This function does not erase the saved files, so it can 302*9c5db199SXin Li be used multiple times if needed. 303*9c5db199SXin Li 304*9c5db199SXin Li @param paths: List of string paths to restore. 305*9c5db199SXin Li """ 306*9c5db199SXin Li for path in paths: 307*9c5db199SXin Li self._host.run('rm -rf "%s"' % path) 308*9c5db199SXin Li self._host.run('cp -r "%s" "%s"' % (self._get_temp_path(path), 309*9c5db199SXin Li path), 310*9c5db199SXin Li ignore_status=True) 311*9c5db199SXin Li 312*9c5db199SXin Li 313*9c5db199SXin Li def _unimplemented_function_error(self, function_name): 314*9c5db199SXin Li """ 315*9c5db199SXin Li Throws an exception if a required tool function hasn't been 316*9c5db199SXin Li implemented. 317*9c5db199SXin Li """ 318*9c5db199SXin Li raise FeatureUnavailableError('%s has not implemented %s()' % 319*9c5db199SXin Li (self, function_name)) 320*9c5db199SXin Li 321*9c5db199SXin Li 322*9c5db199SXin Li def __str__(self): 323*9c5db199SXin Li """ 324*9c5db199SXin Li Tool name accessor for temporary files and logging. 325*9c5db199SXin Li 326*9c5db199SXin Li Based on class rather than unique instance naming since all 327*9c5db199SXin Li instances of the same tool have identical functionality. 328*9c5db199SXin Li """ 329*9c5db199SXin Li return type(self).__name__ 330*9c5db199SXin Li 331*9c5db199SXin Li 332*9c5db199SXin Liclass RootfsVerificationTool(DevTool): 333*9c5db199SXin Li """ 334*9c5db199SXin Li Rootfs verification removal tool. 335*9c5db199SXin Li 336*9c5db199SXin Li This tool is currently unable to transition from non-verified back 337*9c5db199SXin Li to verified rootfs; it may potentially require re-flashing an OS. 338*9c5db199SXin Li Since devices in the test lab run in verified mode, this tool is 339*9c5db199SXin Li unsuitable for automated testing until this capability is 340*9c5db199SXin Li implemented. 341*9c5db199SXin Li """ 342*9c5db199SXin Li 343*9c5db199SXin Li 344*9c5db199SXin Li def is_enabled(self): 345*9c5db199SXin Li return self._check_enabled(DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED) 346*9c5db199SXin Li 347*9c5db199SXin Li 348*9c5db199SXin Li def enable(self): 349*9c5db199SXin Li _send_debugd_command(self._host, 'RemoveRootfsVerification') 350*9c5db199SXin Li self._host.reboot() 351*9c5db199SXin Li 352*9c5db199SXin Li 353*9c5db199SXin Li def disable(self): 354*9c5db199SXin Li raise FeatureUnavailableError('Cannot re-enable rootfs verification') 355*9c5db199SXin Li 356*9c5db199SXin Li 357*9c5db199SXin Liclass BootFromUsbTool(DevTool): 358*9c5db199SXin Li """USB boot configuration tool.""" 359*9c5db199SXin Li 360*9c5db199SXin Li 361*9c5db199SXin Li def is_enabled(self): 362*9c5db199SXin Li return self._check_enabled(DEV_FEATURE_BOOT_FROM_USB_ENABLED) 363*9c5db199SXin Li 364*9c5db199SXin Li 365*9c5db199SXin Li def enable(self): 366*9c5db199SXin Li _send_debugd_command(self._host, 'EnableBootFromUsb') 367*9c5db199SXin Li 368*9c5db199SXin Li 369*9c5db199SXin Li def disable(self): 370*9c5db199SXin Li self._host.run('crossystem dev_boot_usb=0') 371*9c5db199SXin Li 372*9c5db199SXin Li 373*9c5db199SXin Li def save_state(self): 374*9c5db199SXin Li self.initial_state = self.is_enabled() 375*9c5db199SXin Li 376*9c5db199SXin Li 377*9c5db199SXin Li def restore_state(self): 378*9c5db199SXin Li if self.initial_state: 379*9c5db199SXin Li self.enable() 380*9c5db199SXin Li else: 381*9c5db199SXin Li self.disable() 382*9c5db199SXin Li 383*9c5db199SXin Li 384*9c5db199SXin Liclass SshServerTool(DevTool): 385*9c5db199SXin Li """ 386*9c5db199SXin Li SSH server tool. 387*9c5db199SXin Li 388*9c5db199SXin Li SSH configuration has two components, the init file and the test 389*9c5db199SXin Li keys. Since a system could potentially have none, just the init 390*9c5db199SXin Li file, or all files, we want to be sure to restore just the files 391*9c5db199SXin Li that existed before the test started. 392*9c5db199SXin Li """ 393*9c5db199SXin Li 394*9c5db199SXin Li 395*9c5db199SXin Li PATHS = ('/etc/init/openssh-server.conf', 396*9c5db199SXin Li '/root/.ssh/authorized_keys', 397*9c5db199SXin Li '/root/.ssh/id_rsa', 398*9c5db199SXin Li '/root/.ssh/id_rsa.pub') 399*9c5db199SXin Li 400*9c5db199SXin Li 401*9c5db199SXin Li def is_enabled(self): 402*9c5db199SXin Li return self._check_enabled(DEV_FEATURE_SSH_SERVER_CONFIGURED) 403*9c5db199SXin Li 404*9c5db199SXin Li 405*9c5db199SXin Li def enable(self): 406*9c5db199SXin Li _send_debugd_command(self._host, 'ConfigureSshServer') 407*9c5db199SXin Li 408*9c5db199SXin Li 409*9c5db199SXin Li def disable(self): 410*9c5db199SXin Li for path in self.PATHS: 411*9c5db199SXin Li self._host.run('rm -f %s' % path) 412*9c5db199SXin Li 413*9c5db199SXin Li 414*9c5db199SXin Li def save_state(self): 415*9c5db199SXin Li self._save_files(self.PATHS) 416*9c5db199SXin Li 417*9c5db199SXin Li 418*9c5db199SXin Li def restore_state(self): 419*9c5db199SXin Li self._restore_files(self.PATHS) 420*9c5db199SXin Li 421*9c5db199SXin Li 422*9c5db199SXin Liclass SystemPasswordTool(DevTool): 423*9c5db199SXin Li """ 424*9c5db199SXin Li System password configuration tool. 425*9c5db199SXin Li 426*9c5db199SXin Li This tool just affects the system password (/etc/shadow). We could 427*9c5db199SXin Li add a devmode password tool if we want to explicitly test that as 428*9c5db199SXin Li well. 429*9c5db199SXin Li """ 430*9c5db199SXin Li 431*9c5db199SXin Li 432*9c5db199SXin Li SYSTEM_PATHS = ('/etc/shadow',) 433*9c5db199SXin Li DEV_PATHS = ('/mnt/stateful_partition/etc/devmode.passwd',) 434*9c5db199SXin Li 435*9c5db199SXin Li 436*9c5db199SXin Li def is_enabled(self): 437*9c5db199SXin Li return self._check_enabled(DEV_FEATURE_SYSTEM_ROOT_PASSWORD_SET) 438*9c5db199SXin Li 439*9c5db199SXin Li 440*9c5db199SXin Li def enable(self): 441*9c5db199SXin Li # Save the devmode.passwd file to avoid affecting it. 442*9c5db199SXin Li self._save_files(self.DEV_PATHS) 443*9c5db199SXin Li try: 444*9c5db199SXin Li _send_debugd_command(self._host, 'SetUserPassword', 445*9c5db199SXin Li ('string:root', 'string:test0000')) 446*9c5db199SXin Li finally: 447*9c5db199SXin Li # Restore devmode.passwd 448*9c5db199SXin Li self._restore_files(self.DEV_PATHS) 449*9c5db199SXin Li 450*9c5db199SXin Li 451*9c5db199SXin Li def disable(self): 452*9c5db199SXin Li self._host.run('passwd -d root') 453*9c5db199SXin Li 454*9c5db199SXin Li 455*9c5db199SXin Li def save_state(self): 456*9c5db199SXin Li self._save_files(self.SYSTEM_PATHS) 457*9c5db199SXin Li 458*9c5db199SXin Li 459*9c5db199SXin Li def restore_state(self): 460*9c5db199SXin Li self._restore_files(self.SYSTEM_PATHS) 461