1# Copyright 2013 The Chromium 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 distutils.spawn 6import logging 7import os 8import re 9import stat 10import subprocess 11 12from telemetry.core.platform import desktop_platform_backend 13from telemetry.core.platform import ps_util 14 15 16class PosixPlatformBackend(desktop_platform_backend.DesktopPlatformBackend): 17 18 # This is an abstract class. It is OK to have abstract methods. 19 # pylint: disable=W0223 20 21 def RunCommand(self, args): 22 return subprocess.Popen(args, stdout=subprocess.PIPE).communicate()[0] 23 24 def GetFileContents(self, path): 25 with open(path, 'r') as f: 26 return f.read() 27 28 def GetPsOutput(self, columns, pid=None): 29 """Returns output of the 'ps' command as a list of lines. 30 Subclass should override this function. 31 32 Args: 33 columns: A list of require columns, e.g., ['pid', 'pss']. 34 pid: If not None, returns only the information of the process 35 with the pid. 36 """ 37 args = ['ps'] 38 args.extend(['-p', str(pid)] if pid != None else ['-e']) 39 for c in columns: 40 args.extend(['-o', c + '=']) 41 return self.RunCommand(args).splitlines() 42 43 def _GetTopOutput(self, pid, columns): 44 """Returns output of the 'top' command as a list of lines. 45 46 Args: 47 pid: pid of process to examine. 48 columns: A list of require columns, e.g., ['idlew', 'vsize']. 49 """ 50 args = ['top'] 51 args.extend(['-pid', str(pid), '-l', '1', '-s', '0', '-stats', 52 ','.join(columns)]) 53 return self.RunCommand(args).splitlines() 54 55 def GetChildPids(self, pid): 56 """Returns a list of child pids of |pid|.""" 57 ps_output = self.GetPsOutput(['pid', 'ppid', 'state']) 58 ps_line_re = re.compile( 59 '\s*(?P<pid>\d+)\s*(?P<ppid>\d+)\s*(?P<state>\S*)\s*') 60 processes = [] 61 for pid_ppid_state in ps_output: 62 m = ps_line_re.match(pid_ppid_state) 63 assert m, 'Did not understand ps output: %s' % pid_ppid_state 64 processes.append((m.group('pid'), m.group('ppid'), m.group('state'))) 65 return ps_util.GetChildPids(processes, pid) 66 67 def GetCommandLine(self, pid): 68 command = self.GetPsOutput(['command'], pid) 69 return command[0] if command else None 70 71 def CanLaunchApplication(self, application): 72 return bool(distutils.spawn.find_executable(application)) 73 74 def IsApplicationRunning(self, application): 75 ps_output = self.GetPsOutput(['command']) 76 application_re = re.compile( 77 '(.*%s|^)%s(\s|$)' % (os.path.sep, application)) 78 return any(application_re.match(cmd) for cmd in ps_output) 79 80 def LaunchApplication( 81 self, application, parameters=None, elevate_privilege=False): 82 assert application, 'Must specify application to launch' 83 84 if os.path.sep not in application: 85 application = distutils.spawn.find_executable(application) 86 assert application, 'Failed to find application in path' 87 88 args = [application] 89 90 if parameters: 91 assert isinstance(parameters, list), 'parameters must be a list' 92 args += parameters 93 94 def IsSetUID(path): 95 return (os.stat(path).st_mode & stat.S_ISUID) == stat.S_ISUID 96 97 def IsElevated(): 98 p = subprocess.Popen( 99 ['sudo', '-nv'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, 100 stderr=subprocess.STDOUT) 101 stdout = p.communicate()[0] 102 # Some versions of sudo set the returncode based on whether sudo requires 103 # a password currently. Other versions return output when password is 104 # required and no output when the user is already authenticated. 105 return not p.returncode and not stdout 106 107 if elevate_privilege and not IsSetUID(application): 108 args = ['sudo'] + args 109 if not IsElevated(): 110 print ('Telemetry needs to run %s under sudo. Please authenticate.' % 111 application) 112 subprocess.check_call(['sudo', '-v']) # Synchronously authenticate. 113 114 prompt = ('Would you like to always allow %s to be run as the current ' 115 'user without sudo? If so, Telemetry will ' 116 '`sudo chmod +s %s`. (y/N)' % (application, application)) 117 if raw_input(prompt).lower() == 'y': 118 subprocess.check_call(['sudo', 'chmod', '+s', application]) 119 120 stderror_destination = subprocess.PIPE 121 if logging.getLogger().isEnabledFor(logging.DEBUG): 122 stderror_destination = None 123 124 return subprocess.Popen( 125 args, stdout=subprocess.PIPE, stderr=stderror_destination) 126