Home | History | Annotate | Download | only in commands
      1 # Copyright 2016 - The Android Open Source Project
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 
     15 import shellescape
     16 import signal
     17 import time
     18 
     19 from acts.controllers.utils_lib.ssh import connection
     20 from acts.libs.proc import job
     21 
     22 
     23 class ShellCommand(object):
     24     """Wraps basic commands that tend to be tied very closely to a shell.
     25 
     26     This class is a wrapper for running basic shell commands through
     27     any object that has a run command. Basic shell functionality for managing
     28     the system, programs, and files in wrapped within this class.
     29 
     30     Note: At the moment this only works with the ssh runner.
     31     """
     32 
     33     def __init__(self, runner, working_dir=None):
     34         """Creates a new shell command invoker.
     35 
     36         Args:
     37             runner: The object that will run the shell commands.
     38             working_dir: The directory that all commands should work in,
     39                          if none then the runners enviroment default is used.
     40         """
     41         self._runner = runner
     42         self._working_dir = working_dir
     43 
     44     def run(self, command, timeout=3600):
     45         """Runs a generic command through the runner.
     46 
     47         Takes the command and prepares it to be run in the target shell using
     48         this objects settings.
     49 
     50         Args:
     51             command: The command to run.
     52             timeout: How long to wait for the command (in seconds).
     53 
     54         Returns:
     55             A CmdResult object containing the results of the shell command.
     56 
     57         Raises:
     58             job.Error: When the command executed but had an error.
     59         """
     60         if self._working_dir:
     61             command_str = 'cd %s; %s' % (self._working_dir, command)
     62         else:
     63             command_str = command
     64 
     65         return self._runner.run(command_str, timeout=timeout)
     66 
     67     def is_alive(self, identifier):
     68         """Checks to see if a program is alive.
     69 
     70         Checks to see if a program is alive on the shells enviroment. This can
     71         be used to check on generic programs, or a specific program using
     72         a pid.
     73 
     74         Args:
     75             identifier: string or int, Used to identify the program to check.
     76                         if given an int then it is assumed to be a pid. If
     77                         given a string then it will be used as a search key
     78                         to compare on the running processes.
     79         Returns:
     80             True if a process was found running, false otherwise.
     81         """
     82         try:
     83             if isinstance(identifier, str):
     84                 self.run('ps aux | grep -v grep | grep %s' % identifier)
     85             elif isinstance(identifier, int):
     86                 self.signal(identifier, 0)
     87             else:
     88                 raise ValueError('Bad type was given for identifier')
     89 
     90             return True
     91         except job.Error:
     92             return False
     93 
     94     def get_pids(self, identifier):
     95         """Gets the pids of a program.
     96 
     97         Searches for a program with a specific name and grabs the pids for all
     98         programs that match.
     99 
    100         Args:
    101             identifier: A search term that identifies the program.
    102 
    103         Returns: An array of all pids that matched the identifier, or None
    104                   if no pids were found.
    105         """
    106         try:
    107             result = self.run('ps aux | grep -v grep | grep %s' % identifier)
    108         except job.Error:
    109             raise StopIteration
    110 
    111         lines = result.stdout.splitlines()
    112 
    113         # The expected output of the above command is like so:
    114         # bob    14349  0.0  0.0  34788  5552 pts/2    Ss   Oct10   0:03 bash
    115         # bob    52967  0.0  0.0  34972  5152 pts/4    Ss   Oct10   0:00 bash
    116         # Where the format is:
    117         # USER    PID  ...
    118         for line in lines:
    119             pieces = line.split()
    120             yield int(pieces[1])
    121 
    122     def search_file(self, search_string, file_name):
    123         """Searches through a file for a string.
    124 
    125         Args:
    126             search_string: The string or pattern to look for.
    127             file_name: The name of the file to search.
    128 
    129         Returns:
    130             True if the string or pattern was found, False otherwise.
    131         """
    132         try:
    133             self.run('grep %s %s' % (shellescape.quote(search_string),
    134                                      file_name))
    135             return True
    136         except job.Error:
    137             return False
    138 
    139     def read_file(self, file_name):
    140         """Reads a file through the shell.
    141 
    142         Args:
    143             file_name: The name of the file to read.
    144 
    145         Returns:
    146             A string of the files contents.
    147         """
    148         return self.run('cat %s' % file_name).stdout
    149 
    150     def write_file(self, file_name, data):
    151         """Writes a block of data to a file through the shell.
    152 
    153         Args:
    154             file_name: The name of the file to write to.
    155             data: The string of data to write.
    156         """
    157         return self.run('echo %s > %s' % (shellescape.quote(data), file_name))
    158 
    159     def append_file(self, file_name, data):
    160         """Appends a block of data to a file through the shell.
    161 
    162         Args:
    163             file_name: The name of the file to write to.
    164             data: The string of data to write.
    165         """
    166         return self.run('echo %s >> %s' % (shellescape.quote(data), file_name))
    167 
    168     def touch_file(self, file_name):
    169         """Creates a file through the shell.
    170 
    171         Args:
    172             file_name: The name of the file to create.
    173         """
    174         self.write_file(file_name, '')
    175 
    176     def delete_file(self, file_name):
    177         """Deletes a file through the shell.
    178 
    179         Args:
    180             file_name: The name of the file to delete.
    181         """
    182         try:
    183             self.run('rm %s' % file_name)
    184         except job.Error as e:
    185             if 'No such file or directory' in e.result.stderr:
    186                 return
    187 
    188             raise
    189 
    190     def kill(self, identifier, timeout=10):
    191         """Kills a program or group of programs through the shell.
    192 
    193         Kills all programs that match an identifier through the shell. This
    194         will send an increasing queue of kill signals to all programs
    195         that match the identifier until either all are dead or the timeout
    196         finishes.
    197 
    198         Programs are guaranteed to be killed after running this command.
    199 
    200         Args:
    201             identifier: A string used to identify the program.
    202             timeout: The time to wait for all programs to die. Each signal will
    203                      take an equal portion of this time.
    204         """
    205         if isinstance(identifier, int):
    206             pids = [identifier]
    207         else:
    208             pids = list(self.get_pids(identifier))
    209 
    210         signal_queue = [signal.SIGINT, signal.SIGTERM, signal.SIGKILL]
    211 
    212         signal_duration = timeout / len(signal_queue)
    213         for sig in signal_queue:
    214             for pid in pids:
    215                 try:
    216                     self.signal(pid, sig)
    217                 except job.Error:
    218                     pass
    219 
    220             start_time = time.time()
    221             while pids and time.time() - start_time < signal_duration:
    222                 time.sleep(0.1)
    223                 pids = [pid for pid in pids if self.is_alive(pid)]
    224 
    225             if not pids:
    226                 break
    227 
    228     def signal(self, pid, sig):
    229         """Sends a specific signal to a program.
    230 
    231         Args:
    232             pid: The process id of the program to kill.
    233             sig: The signal to send.
    234 
    235         Raises:
    236             job.Error: Raised when the signal fail to reach
    237                        the specified program.
    238         """
    239         self.run('kill -%d %d' % (sig, pid))
    240