Home | History | Annotate | Download | only in platform
      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 
      5 import distutils.spawn
      6 import logging
      7 import os
      8 import re
      9 import stat
     10 import subprocess
     11 
     12 from telemetry.core.platform import desktop_platform_backend
     13 from telemetry.core.platform import ps_util
     14 
     15 
     16 class 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