| #!/usr/bin/env python |
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Helper script to perform actions as a super-user on ChromeOS. |
| |
| Needs to be run with superuser privileges, typically using the |
| suid_python binary. |
| |
| Usage: |
| sudo python suid_actions.py --action=CleanFlimflamDirs |
| """ |
| |
| import optparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| import time |
| |
| sys.path.append('/usr/local') # to import autotest libs. |
| from autotest.cros import constants |
| from autotest.cros import cryptohome |
| |
| TEMP_BACKCHANNEL_FILE = '/tmp/pyauto_network_backchannel_file' |
| |
| |
| class SuidAction(object): |
| """Helper to perform some super-user actions on ChromeOS.""" |
| |
| def _ParseArgs(self): |
| parser = optparse.OptionParser() |
| parser.add_option( |
| '-a', '--action', help='Action to perform.') |
| self._options = parser.parse_args()[0] |
| if not self._options.action: |
| raise RuntimeError('No action specified.') |
| |
| def Run(self): |
| self._ParseArgs() |
| assert os.geteuid() == 0, 'Needs superuser privileges.' |
| handler = getattr(self, self._options.action) |
| assert handler and callable(handler), \ |
| 'No handler for %s' % self._options.action |
| handler() |
| return 0 |
| |
| ## Actions ## |
| def CleanFlimflamDirs(self): |
| """Clean the contents of all connection manager (shill/flimflam) profiles. |
| """ |
| flimflam_dirs = ['/home/chronos/user/flimflam', |
| '/home/chronos/user/shill', |
| '/var/cache/flimflam', |
| '/var/cache/shill'] |
| |
| # The stop/start flimflam command should stop/start shill respectivly if |
| # enabled. |
| os.system('stop flimflam') |
| try: |
| for flimflam_dir in flimflam_dirs: |
| if not os.path.exists(flimflam_dir): |
| continue |
| for item in os.listdir(flimflam_dir): |
| path = os.path.join(flimflam_dir, item) |
| if os.path.isdir(path): |
| shutil.rmtree(path) |
| else: |
| os.remove(path) |
| finally: |
| os.system('start flimflam') |
| # TODO(stanleyw): crosbug.com/29421 This method should wait until |
| # flimflam/shill is fully initialized and accessible via DBus again. |
| # Otherwise, there is a race conditions and subsequent accesses to |
| # flimflam/shill may fail. Until this is fixed, waiting for the |
| # resolv.conf file to be created is better than nothing. |
| begin = time.time() |
| while not os.path.exists(constants.RESOLV_CONF_FILE): |
| if time.time() - begin > 10: |
| raise RuntimeError('Timeout while waiting for flimflam/shill start.') |
| time.sleep(.25) |
| |
| def RemoveAllCryptohomeVaults(self): |
| """Remove any existing cryptohome vaults.""" |
| cryptohome.remove_all_vaults() |
| |
| def _GetEthInterfaces(self): |
| """Returns a list of the eth* interfaces detected by the device.""" |
| # Assumes ethernet interfaces all have "eth" in the name. |
| import pyudev |
| return sorted([iface.sys_name for iface in |
| pyudev.Context().list_devices(subsystem='net') |
| if 'eth' in iface.sys_name]) |
| |
| def _Renameif(self, old_iface, new_iface, mac_address): |
| """Renames the interface with mac_address from old_iface to new_iface. |
| |
| Args: |
| old_iface: The name of the interface you want to change. |
| new_iface: The name of the interface you want to change to. |
| mac_address: The mac address of the interface being changed. |
| """ |
| subprocess.call(['stop', 'flimflam']) |
| subprocess.call(['ifconfig', old_iface, 'down']) |
| subprocess.call(['nameif', new_iface, mac_address]) |
| subprocess.call(['ifconfig', new_iface, 'up']) |
| subprocess.call(['start', 'flimflam']) |
| |
| # Check and make sure interfaces have been renamed |
| eth_ifaces = self._GetEthInterfaces() |
| if new_iface not in eth_ifaces: |
| raise RuntimeError('Interface %s was not renamed to %s' % |
| (old_iface, new_iface)) |
| elif old_iface in eth_ifaces: |
| raise RuntimeError('Old iface %s is still present' % old_iface) |
| |
| def SetupBackchannel(self): |
| """Renames the connected ethernet interface to eth_test for offline mode |
| testing. Does nothing if no connected interface is found. |
| """ |
| # Return the interface with ethernet connected or returns if none found. |
| for iface in self._GetEthInterfaces(): |
| with open('/sys/class/net/%s/operstate' % iface, 'r') as fp: |
| if 'up' in fp.read(): |
| eth_iface = iface |
| break |
| else: |
| return |
| |
| # Write backup file to be used by TeardownBackchannel to restore the |
| # interface names. |
| with open(TEMP_BACKCHANNEL_FILE, 'w') as fpw: |
| with open('/sys/class/net/%s/address' % eth_iface) as fp: |
| mac_address = fp.read().strip() |
| fpw.write('%s, %s' % (eth_iface, mac_address)) |
| |
| self._Renameif(eth_iface, 'eth_test', mac_address) |
| |
| def TeardownBackchannel(self): |
| """Restores the eth interface names if SetupBackchannel was called.""" |
| if not os.path.isfile(TEMP_BACKCHANNEL_FILE): |
| return |
| |
| with open(TEMP_BACKCHANNEL_FILE, 'r') as fp: |
| eth_iface, mac_address = fp.read().split(',') |
| |
| self._Renameif('eth_test', eth_iface, mac_address) |
| os.remove(TEMP_BACKCHANNEL_FILE) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(SuidAction().Run()) |