1# Copyright (c) 2012 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"""A wrapper for subprocess to make calling shell commands easier.""" 6 7import logging 8import pipes 9import signal 10import subprocess 11import tempfile 12 13from utils import timeout_retry 14 15 16def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 17 return subprocess.Popen( 18 args=args, cwd=cwd, stdout=stdout, stderr=stderr, 19 shell=shell, close_fds=True, env=env, 20 preexec_fn=lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)) 21 22 23def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None): 24 pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd, 25 env=env) 26 pipe.communicate() 27 return pipe.wait() 28 29 30def RunCmd(args, cwd=None): 31 """Opens a subprocess to execute a program and returns its return value. 32 33 Args: 34 args: A string or a sequence of program arguments. The program to execute is 35 the string or the first item in the args sequence. 36 cwd: If not None, the subprocess's current directory will be changed to 37 |cwd| before it's executed. 38 39 Returns: 40 Return code from the command execution. 41 """ 42 logging.info(str(args) + ' ' + (cwd or '')) 43 return Call(args, cwd=cwd) 44 45 46def GetCmdOutput(args, cwd=None, shell=False): 47 """Open a subprocess to execute a program and returns its output. 48 49 Args: 50 args: A string or a sequence of program arguments. The program to execute is 51 the string or the first item in the args sequence. 52 cwd: If not None, the subprocess's current directory will be changed to 53 |cwd| before it's executed. 54 shell: Whether to execute args as a shell command. 55 56 Returns: 57 Captures and returns the command's stdout. 58 Prints the command's stderr to logger (which defaults to stdout). 59 """ 60 (_, output) = GetCmdStatusAndOutput(args, cwd, shell) 61 return output 62 63 64def GetCmdStatusAndOutput(args, cwd=None, shell=False): 65 """Executes a subprocess and returns its exit code and output. 66 67 Args: 68 args: A string or a sequence of program arguments. The program to execute is 69 the string or the first item in the args sequence. 70 cwd: If not None, the subprocess's current directory will be changed to 71 |cwd| before it's executed. 72 shell: Whether to execute args as a shell command. 73 74 Returns: 75 The 2-tuple (exit code, output). 76 """ 77 if isinstance(args, basestring): 78 args_repr = args 79 if not shell: 80 raise Exception('string args must be run with shell=True') 81 elif shell: 82 raise Exception('array args must be run with shell=False') 83 else: 84 args_repr = ' '.join(map(pipes.quote, args)) 85 86 s = '[host]' 87 if cwd: 88 s += ':' + cwd 89 s += '> ' + args_repr 90 logging.info(s) 91 tmpout = tempfile.TemporaryFile(bufsize=0) 92 tmperr = tempfile.TemporaryFile(bufsize=0) 93 exit_code = Call(args, cwd=cwd, stdout=tmpout, stderr=tmperr, shell=shell) 94 tmperr.seek(0) 95 stderr = tmperr.read() 96 tmperr.close() 97 if stderr: 98 logging.critical(stderr) 99 tmpout.seek(0) 100 stdout = tmpout.read() 101 tmpout.close() 102 if len(stdout) > 4096: 103 logging.debug('Truncated output:') 104 logging.debug(stdout[:4096]) 105 return (exit_code, stdout) 106 107 108def GetCmdStatusAndOutputWithTimeoutAndRetries(args, timeout, retries): 109 """Executes a subprocess with a timeout and retries. 110 111 Args: 112 args: List of arguments to the program, the program to execute is the first 113 element. 114 timeout: the timeout in seconds. 115 retries: the number of retries. 116 117 Returns: 118 The 2-tuple (exit code, output). 119 """ 120 return timeout_retry.Run(GetCmdStatusAndOutput, timeout, retries, [args]) 121