blob: 5e86d6f17edffa351fef36fa54000bafa5e827a3 [file] [log] [blame]
Rahul Chaudhry8ac4ec02014-11-01 09:31:15 -07001# Copyright 2011 The Chromium OS 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.
Ahmad Shariffd356fb2012-05-07 12:02:16 -07004
Luis Lozanobd447042015-10-01 14:24:27 -07005"""Utilities to run commands in outside/inside chroot and on the board."""
6
Ahmad Sharif70de27b2011-06-15 17:51:24 -07007import getpass
Ahmad Sharif70de27b2011-06-15 17:51:24 -07008import os
Ahmad Sharif70de27b2011-06-15 17:51:24 -07009import re
10import select
Luis Lozano45b53c52015-09-30 11:36:27 -070011import signal
Ahmad Sharif70de27b2011-06-15 17:51:24 -070012import subprocess
Luis Lozanobd447042015-10-01 14:24:27 -070013import sys
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080014import tempfile
Ahmad Sharif70de27b2011-06-15 17:51:24 -070015import time
Ahmad Shariffd356fb2012-05-07 12:02:16 -070016
17import logger
18import misc
Ahmad Sharif70de27b2011-06-15 17:51:24 -070019
20mock_default = False
21
Luis Lozanobd447042015-10-01 14:24:27 -070022LOG_LEVEL = ("none", "quiet", "average", "verbose")
Ahmad Sharif70de27b2011-06-15 17:51:24 -070023
24def InitCommandExecuter(mock=False):
25 global mock_default
26 # Whether to default to a mock command executer or not
27 mock_default = mock
28
29
cmtice13909242014-03-11 13:38:07 -070030def GetCommandExecuter(logger_to_set=None, mock=False, log_level="verbose"):
Ahmad Sharif70de27b2011-06-15 17:51:24 -070031 # If the default is a mock executer, always return one.
32 if mock_default or mock:
Luis Lozanobd447042015-10-01 14:24:27 -070033 return MockCommandExecuter(log_level, logger_to_set)
Ahmad Sharif70de27b2011-06-15 17:51:24 -070034 else:
cmtice13909242014-03-11 13:38:07 -070035 return CommandExecuter(log_level, logger_to_set)
Ahmad Sharif70de27b2011-06-15 17:51:24 -070036
37
Luis Lozanobd447042015-10-01 14:24:27 -070038class CommandExecuter(object):
39 """Provides several methods to execute commands on several environments."""
40
cmtice13909242014-03-11 13:38:07 -070041 def __init__(self, log_level, logger_to_set=None):
42 self.log_level = log_level
cmtice37828572015-02-04 17:37:43 -080043 if log_level == "none":
44 self.logger = None
Ahmad Sharif70de27b2011-06-15 17:51:24 -070045 else:
cmtice37828572015-02-04 17:37:43 -080046 if logger_to_set is not None:
47 self.logger = logger_to_set
48 else:
49 self.logger = logger.GetLogger()
Ahmad Sharif70de27b2011-06-15 17:51:24 -070050
cmtice13909242014-03-11 13:38:07 -070051 def GetLogLevel(self):
52 return self.log_level
53
54 def SetLogLevel(self, log_level):
55 self.log_level = log_level
56
Ahmad Sharif70de27b2011-06-15 17:51:24 -070057 def RunCommand(self, cmd, return_output=False, machine=None,
58 username=None, command_terminator=None,
59 command_timeout=None,
Ahmad Shariff395c262012-10-09 17:48:09 -070060 terminated_timeout=10,
61 print_to_console=True):
Luis Lozano45b53c52015-09-30 11:36:27 -070062 """Run a command."""
Ahmad Sharif70de27b2011-06-15 17:51:24 -070063
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080064 cmd = str(cmd)
65
cmtice13909242014-03-11 13:38:07 -070066 if self.log_level == "quiet":
Luis Lozanobd447042015-10-01 14:24:27 -070067 print_to_console = False
cmtice13909242014-03-11 13:38:07 -070068
69 if self.log_level == "verbose":
70 self.logger.LogCmd(cmd, machine, username, print_to_console)
cmtice37828572015-02-04 17:37:43 -080071 elif self.logger:
cmtice05379562014-04-07 13:19:01 -070072 self.logger.LogCmdToFileOnly(cmd, machine, username)
Ahmad Sharif70de27b2011-06-15 17:51:24 -070073 if command_terminator and command_terminator.IsTerminated():
cmtice37828572015-02-04 17:37:43 -080074 if self.logger:
75 self.logger.LogError("Command was terminated!", print_to_console)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -080076 if return_output:
77 return [1, "", ""]
78 else:
79 return 1
Ahmad Sharif70de27b2011-06-15 17:51:24 -070080
81 if machine is not None:
82 user = ""
83 if username is not None:
84 user = username + "@"
85 cmd = "ssh -t -t %s%s -- '%s'" % (user, machine, cmd)
86
Luis Lozano45b53c52015-09-30 11:36:27 -070087 # We use setsid so that the child will have a different session id
88 # and we can easily kill the process group. This is also important
89 # because the child will be disassociated from the parent terminal.
90 # In this way the child cannot mess the parent's terminal.
91 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
92 stderr=subprocess.PIPE, shell=True,
93 preexec_fn=os.setsid)
Ahmad Sharif70de27b2011-06-15 17:51:24 -070094
95 full_stdout = ""
96 full_stderr = ""
97
98 # Pull output from pipes, send it to file/stdout/string
99 out = err = None
100 pipes = [p.stdout, p.stderr]
101
Luis Lozanof81680c2013-03-15 14:44:13 -0700102 my_poll = select.poll()
103 my_poll.register(p.stdout, select.POLLIN)
104 my_poll.register(p.stderr, select.POLLIN)
105
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700106 terminated_time = None
107 started_time = time.time()
108
109 while len(pipes):
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700110 if command_terminator and command_terminator.IsTerminated():
Luis Lozano45b53c52015-09-30 11:36:27 -0700111 os.killpg(os.getpgid(p.pid), signal.SIGTERM)
cmtice37828572015-02-04 17:37:43 -0800112 if self.logger:
Luis Lozano45b53c52015-09-30 11:36:27 -0700113 self.logger.LogError("Command received termination request. "
114 "Killed child process group.",
115 print_to_console)
116 break
Luis Lozanof81680c2013-03-15 14:44:13 -0700117
Luis Lozanobd447042015-10-01 14:24:27 -0700118 l = my_poll.poll(100)
119 for (fd, _) in l:
Luis Lozanof81680c2013-03-15 14:44:13 -0700120 if fd == p.stdout.fileno():
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700121 out = os.read(p.stdout.fileno(), 16384)
122 if return_output:
123 full_stdout += out
cmtice37828572015-02-04 17:37:43 -0800124 if self.logger:
125 self.logger.LogCommandOutput(out, print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700126 if out == "":
127 pipes.remove(p.stdout)
Luis Lozanof81680c2013-03-15 14:44:13 -0700128 my_poll.unregister(p.stdout)
129 if fd == p.stderr.fileno():
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700130 err = os.read(p.stderr.fileno(), 16384)
131 if return_output:
132 full_stderr += err
cmtice37828572015-02-04 17:37:43 -0800133 if self.logger:
134 self.logger.LogCommandError(err, print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700135 if err == "":
136 pipes.remove(p.stderr)
Luis Lozanof81680c2013-03-15 14:44:13 -0700137 my_poll.unregister(p.stderr)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700138
139 if p.poll() is not None:
140 if terminated_time is None:
141 terminated_time = time.time()
142 elif (terminated_timeout is not None and
143 time.time() - terminated_time > terminated_timeout):
cmtice37828572015-02-04 17:37:43 -0800144 if self.logger:
Luis Lozano45b53c52015-09-30 11:36:27 -0700145 self.logger.LogWarning("Timeout of %s seconds reached since "
146 "process termination."
147 % terminated_timeout, print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700148 break
149
150 if (command_timeout is not None and
151 time.time() - started_time > command_timeout):
Luis Lozano45b53c52015-09-30 11:36:27 -0700152 os.killpg(os.getpgid(p.pid), signal.SIGTERM)
cmtice37828572015-02-04 17:37:43 -0800153 if self.logger:
Luis Lozano45b53c52015-09-30 11:36:27 -0700154 self.logger.LogWarning("Timeout of %s seconds reached since process"
155 "started. Killed child process group."
156 % command_timeout, print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700157 break
158
159 if out == err == "":
160 break
161
162 p.wait()
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700163 if return_output:
164 return (p.returncode, full_stdout, full_stderr)
165 return p.returncode
166
167 def RemoteAccessInitCommand(self, chromeos_root, machine):
168 command = ""
169 command += "\nset -- --remote=" + machine
170 command += "\n. " + chromeos_root + "/src/scripts/common.sh"
171 command += "\n. " + chromeos_root + "/src/scripts/remote_access.sh"
172 command += "\nTMP=$(mktemp -d)"
173 command += "\nFLAGS \"$@\" || exit 1"
174 command += "\nremote_access_init"
175 return command
176
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800177 def WriteToTempShFile(self, contents):
178 handle, command_file = tempfile.mkstemp(prefix=os.uname()[1],
179 suffix=".sh")
180 os.write(handle, "#!/bin/bash\n")
181 os.write(handle, contents)
182 os.close(handle)
183 return command_file
184
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700185
186 def CrosLearnBoard(self, chromeos_root, machine):
187 command = self.RemoteAccessInitCommand(chromeos_root, machine)
188 command += "\nlearn_board"
189 command += "\necho ${FLAGS_board}"
Luis Lozanobd447042015-10-01 14:24:27 -0700190 retval, output, _ = self.RunCommand(command, True)
cmtice37828572015-02-04 17:37:43 -0800191 if self.logger:
192 self.logger.LogFatalIf(retval, "learn_board command failed")
cmtice513d8522015-02-06 09:17:25 -0800193 elif retval:
cmtice37828572015-02-04 17:37:43 -0800194 sys.exit(1)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700195 return output.split()[-1]
196
197 def CrosRunCommand(self, cmd, return_output=False, machine=None,
Luis Lozanobd447042015-10-01 14:24:27 -0700198 command_terminator=None,
199 chromeos_root=None, command_timeout=None,
200 terminated_timeout=10, print_to_console=True):
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700201 """Run a command on a chromeos box"""
cmtice13909242014-03-11 13:38:07 -0700202
203 if self.log_level != "verbose":
Luis Lozanobd447042015-10-01 14:24:27 -0700204 print_to_console = False
cmtice13909242014-03-11 13:38:07 -0700205
cmtice37828572015-02-04 17:37:43 -0800206 if self.logger:
207 self.logger.LogCmd(cmd, print_to_console=print_to_console)
208 self.logger.LogFatalIf(not machine, "No machine provided!")
209 self.logger.LogFatalIf(not chromeos_root, "chromeos_root not given!")
210 else:
211 if not chromeos_root or not machine:
212 sys.exit(1)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700213 chromeos_root = os.path.expanduser(chromeos_root)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800214
215 # Write all commands to a file.
216 command_file = self.WriteToTempShFile(cmd)
Ahmad Sharif4467f002012-12-20 12:09:49 -0800217 retval = self.CopyFiles(command_file, command_file,
218 dest_machine=machine,
219 command_terminator=command_terminator,
220 chromeos_root=chromeos_root,
221 dest_cros=True,
222 recursive=False,
223 print_to_console=print_to_console)
224 if retval:
cmtice37828572015-02-04 17:37:43 -0800225 if self.logger:
226 self.logger.LogError("Could not run remote command on machine."
Luis Lozanobd447042015-10-01 14:24:27 -0700227 " Is the machine up?")
Ahmad Sharif4467f002012-12-20 12:09:49 -0800228 return retval
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800229
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700230 command = self.RemoteAccessInitCommand(chromeos_root, machine)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800231 command += "\nremote_sh bash %s" % command_file
Luis Lozanodd75bad2014-04-21 13:58:16 -0700232 command += "\nl_retval=$?; echo \"$REMOTE_OUT\"; exit $l_retval"
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800233 retval = self.RunCommand(command, return_output,
234 command_terminator=command_terminator,
Ahmad Shariffd356fb2012-05-07 12:02:16 -0700235 command_timeout=command_timeout,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800236 terminated_timeout=terminated_timeout,
237 print_to_console=print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700238 if return_output:
239 connect_signature = ("Initiating first contact with remote host\n" +
240 "Connection OK\n")
241 connect_signature_re = re.compile(connect_signature)
Luis Lozano54db5382015-05-20 15:57:19 -0700242 modded_retval = list(retval)
243 modded_retval[1] = connect_signature_re.sub("", retval[1])
244 return modded_retval
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700245 return retval
246
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800247 def ChrootRunCommand(self, chromeos_root, command, return_output=False,
Ahmad Shariffd356fb2012-05-07 12:02:16 -0700248 command_terminator=None, command_timeout=None,
Ahmad Shariff395c262012-10-09 17:48:09 -0700249 terminated_timeout=10,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800250 print_to_console=True,
251 cros_sdk_options=""):
cmtice13909242014-03-11 13:38:07 -0700252
253 if self.log_level != "verbose":
254 print_to_console = False
255
cmtice37828572015-02-04 17:37:43 -0800256 if self.logger:
257 self.logger.LogCmd(command, print_to_console=print_to_console)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800258
259 handle, command_file = tempfile.mkstemp(dir=os.path.join(chromeos_root,
Luis Lozanobd447042015-10-01 14:24:27 -0700260 "src/scripts"),
261 suffix=".sh",
262 prefix="in_chroot_cmd")
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800263 os.write(handle, "#!/bin/bash\n")
264 os.write(handle, command)
Rahul Chaudhry8ac4ec02014-11-01 09:31:15 -0700265 os.write(handle, "\n")
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800266 os.close(handle)
267
268 os.chmod(command_file, 0777)
269
Rahul Chaudhry8d774272014-11-05 12:50:45 -0800270 # if return_output is set, run a dummy command first to make sure that
271 # the chroot already exists. We want the final returned output to skip
272 # the output from chroot creation steps.
273 if return_output:
274 ret = self.RunCommand("cd %s; cros_sdk %s -- true" %
275 (chromeos_root, cros_sdk_options))
276 if ret:
277 return (ret, "", "")
278
Rahul Chaudhry8ac4ec02014-11-01 09:31:15 -0700279 # Run command_file inside the chroot, making sure that any "~" is expanded
280 # by the shell inside the chroot, not outside.
281 command = ("cd %s; cros_sdk %s -- bash -c '%s/%s'" %
282 (chromeos_root,
283 cros_sdk_options,
284 misc.CHROMEOS_SCRIPTS_DIR,
285 os.path.basename(command_file)))
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800286 ret = self.RunCommand(command, return_output,
Ahmad Shariffd356fb2012-05-07 12:02:16 -0700287 command_terminator=command_terminator,
288 command_timeout=command_timeout,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800289 terminated_timeout=terminated_timeout,
290 print_to_console=print_to_console)
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800291 os.remove(command_file)
292 return ret
293
294
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700295 def RunCommands(self, cmdlist, return_output=False, machine=None,
296 username=None, command_terminator=None):
297 cmd = " ;\n" .join(cmdlist)
298 return self.RunCommand(cmd, return_output, machine, username,
299 command_terminator)
300
301 def CopyFiles(self, src, dest, src_machine=None, dest_machine=None,
302 src_user=None, dest_user=None, recursive=True,
303 command_terminator=None,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800304 chromeos_root=None, src_cros=False, dest_cros=False,
305 print_to_console=True):
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700306 src = os.path.expanduser(src)
307 dest = os.path.expanduser(dest)
308
309 if recursive:
310 src = src + "/"
311 dest = dest + "/"
312
313 if src_cros == True or dest_cros == True:
cmtice37828572015-02-04 17:37:43 -0800314 if self.logger:
Luis Lozanobd447042015-10-01 14:24:27 -0700315 self.logger.LogFatalIf(src_cros == dest_cros,
316 "Only one of src_cros and desc_cros can "
317 "be True.")
cmtice37828572015-02-04 17:37:43 -0800318 self.logger.LogFatalIf(not chromeos_root, "chromeos_root not given!")
Luis Lozanobd447042015-10-01 14:24:27 -0700319 elif src_cros == dest_cros or not chromeos_root:
cmtice37828572015-02-04 17:37:43 -0800320 sys.exit(1)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700321 if src_cros == True:
322 cros_machine = src_machine
323 else:
324 cros_machine = dest_machine
325
326 command = self.RemoteAccessInitCommand(chromeos_root, cros_machine)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700327 ssh_command = ("ssh -p ${FLAGS_ssh_port}" +
328 " -o StrictHostKeyChecking=no" +
329 " -o UserKnownHostsFile=$(mktemp)" +
330 " -i $TMP_PRIVATE_KEY")
331 rsync_prefix = "\nrsync -r -e \"%s\" " % ssh_command
332 if dest_cros == True:
333 command += rsync_prefix + "%s root@%s:%s" % (src, dest_machine, dest)
334 return self.RunCommand(command,
335 machine=src_machine,
336 username=src_user,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800337 command_terminator=command_terminator,
338 print_to_console=print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700339 else:
340 command += rsync_prefix + "root@%s:%s %s" % (src_machine, src, dest)
341 return self.RunCommand(command,
342 machine=dest_machine,
343 username=dest_user,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800344 command_terminator=command_terminator,
345 print_to_console=print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700346
347
348 if dest_machine == src_machine:
Luis Lozanobd447042015-10-01 14:24:27 -0700349 command = "rsync -a %s %s" % (src, dest)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700350 else:
351 if src_machine is None:
352 src_machine = os.uname()[1]
353 src_user = getpass.getuser()
Luis Lozanobd447042015-10-01 14:24:27 -0700354 command = "rsync -a %s@%s:%s %s" % (src_user, src_machine, src, dest)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700355 return self.RunCommand(command,
356 machine=dest_machine,
357 username=dest_user,
Ahmad Sharif4467f002012-12-20 12:09:49 -0800358 command_terminator=command_terminator,
359 print_to_console=print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700360
361
Han Shen9b552382015-03-26 11:45:52 -0700362 def RunCommand2(self, cmd, cwd=None, line_consumer=None,
363 timeout=None, shell=True, join_stderr=True, env=None):
364 """Run the command with an extra feature line_consumer.
365
366 This version allow developers to provide a line_consumer which will be
367 fed execution output lines.
368
369 A line_consumer is a callback, which is given a chance to run for each
370 line the execution outputs (either to stdout or stderr). The
371 line_consumer must accept one and exactly one dict argument, the dict
372 argument has these items -
373 'line' - The line output by the binary. Notice, this string includes
374 the trailing '\n'.
375 'output' - Whether this is a stdout or stderr output, values are either
376 'stdout' or 'stderr'. When join_stderr is True, this value
377 will always be 'output'.
378 'pobject' - The object used to control execution, for example, call
379 pobject.kill().
380
Luis Lozano60a68a52015-09-28 15:13:57 -0700381 Note: As this is written, the stdin for the process executed is
382 not associated with the stdin of the caller of this routine.
383
Han Shen9b552382015-03-26 11:45:52 -0700384 Args:
Luis Lozanobd447042015-10-01 14:24:27 -0700385 cmd: Command in a single string.
386 cwd: Working directory for execution.
Han Shen9b552382015-03-26 11:45:52 -0700387 line_consumer: A function that will ba called by this function. See above
Luis Lozanobd447042015-10-01 14:24:27 -0700388 for details.
389 timeout: terminate command after this timeout.
390 shell: Whether to use a shell for execution.
391 join_stderr: Whether join stderr to stdout stream.
392 env: Execution environment.
Han Shen9b552382015-03-26 11:45:52 -0700393
394 Returns:
395 Execution return code.
396
397 Raises:
398 child_exception: if fails to start the command process (missing
399 permission, no such file, etc)
400 """
401
402 class StreamHandler(object):
Luis Lozanobd447042015-10-01 14:24:27 -0700403 """Internal utility class."""
Han Shen9b552382015-03-26 11:45:52 -0700404
Luis Lozanobd447042015-10-01 14:24:27 -0700405 def __init__(self, pobject, fd, name, line_consumer):
406 self._pobject = pobject
407 self._fd = fd
408 self._name = name
409 self._buf = ''
410 self._line_consumer = line_consumer
Han Shen9b552382015-03-26 11:45:52 -0700411
Luis Lozanobd447042015-10-01 14:24:27 -0700412 def read_and_notify_line(self):
413 t = os.read(fd, 1024)
414 self._buf = self._buf + t
415 self.notify_line()
Han Shen9b552382015-03-26 11:45:52 -0700416
Luis Lozanobd447042015-10-01 14:24:27 -0700417 def notify_line(self):
418 p = self._buf.find('\n')
419 while p >= 0:
420 self._line_consumer(line=self._buf[:p+1], output=self._name,
421 pobject=self._pobject)
422 if p < len(self._buf) - 1:
423 self._buf = self._buf[p+1:]
Han Shen9b552382015-03-26 11:45:52 -0700424 p = self._buf.find('\n')
Luis Lozanobd447042015-10-01 14:24:27 -0700425 else:
426 self._buf = ''
427 p = -1
428 break
Han Shen9b552382015-03-26 11:45:52 -0700429
Luis Lozanobd447042015-10-01 14:24:27 -0700430 def notify_eos(self):
431 # Notify end of stream. The last line may not end with a '\n'.
432 if self._buf != '':
433 self._line_consumer(line=self._buf, output=self._name,
434 pobject=self._pobject)
435 self._buf = ''
Han Shen9b552382015-03-26 11:45:52 -0700436
437 if self.log_level == "verbose":
438 self.logger.LogCmd(cmd)
439 elif self.logger:
440 self.logger.LogCmdToFileOnly(cmd)
Luis Lozano45b53c52015-09-30 11:36:27 -0700441
442 # We use setsid so that the child will have a different session id
443 # and we can easily kill the process group. This is also important
444 # because the child will be disassociated from the parent terminal.
445 # In this way the child cannot mess the parent's terminal.
Han Shen9b552382015-03-26 11:45:52 -0700446 pobject = subprocess.Popen(
447 cmd, cwd=cwd, bufsize=1024, env=env, shell=shell,
Luis Lozano45b53c52015-09-30 11:36:27 -0700448 universal_newlines=True, stdout=subprocess.PIPE,
449 stderr=subprocess.STDOUT if join_stderr else subprocess.PIPE,
450 preexec_fn=os.setsid)
Luis Lozano60a68a52015-09-28 15:13:57 -0700451
Han Shen9b552382015-03-26 11:45:52 -0700452 # We provide a default line_consumer
453 if line_consumer is None:
Luis Lozanobd447042015-10-01 14:24:27 -0700454 line_consumer = lambda **d: None
Han Shen9b552382015-03-26 11:45:52 -0700455 start_time = time.time()
456 poll = select.poll()
457 outfd = pobject.stdout.fileno()
458 poll.register(outfd, select.POLLIN | select.POLLPRI)
459 handlermap = {outfd: StreamHandler(pobject, outfd, 'stdout', line_consumer)}
460 if not join_stderr:
Luis Lozanobd447042015-10-01 14:24:27 -0700461 errfd = pobject.stderr.fileno()
462 poll.register(errfd, select.POLLIN | select.POLLPRI)
463 handlermap[errfd] = StreamHandler(
464 pobject, errfd, 'stderr', line_consumer)
Han Shen9b552382015-03-26 11:45:52 -0700465 while len(handlermap):
Luis Lozanobd447042015-10-01 14:24:27 -0700466 readables = poll.poll(300)
467 for (fd, evt) in readables:
468 handler = handlermap[fd]
469 if evt & (select.POLLPRI | select.POLLIN):
470 handler.read_and_notify_line()
471 elif evt & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
472 handler.notify_eos()
473 poll.unregister(fd)
474 del handlermap[fd]
Han Shen9b552382015-03-26 11:45:52 -0700475
Luis Lozanobd447042015-10-01 14:24:27 -0700476 if timeout is not None and (time.time() - start_time > timeout):
477 os.killpg(os.getpgid(pobject.pid), signal.SIGTERM)
Han Shen9b552382015-03-26 11:45:52 -0700478
479 return pobject.wait()
480
481
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700482class MockCommandExecuter(CommandExecuter):
Luis Lozanobd447042015-10-01 14:24:27 -0700483 """Mock class for class CommandExecuter."""
484 def __init__(self, log_level, logger_to_set=None):
485 super(MockCommandExecuter, self).__init__(log_level, logger_to_set)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700486
Luis Lozanobd447042015-10-01 14:24:27 -0700487 def RunCommand(self, cmd, return_output=False, machine=None,
488 username=None, command_terminator=None,
489 command_timeout=None, terminated_timeout=10,
490 print_to_console=True):
491 assert not command_timeout
Ahmad Sharif0dcbc4b2012-02-02 16:37:18 -0800492 cmd = str(cmd)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700493 if machine is None:
494 machine = "localhost"
495 if username is None:
496 username = "current"
Luis Lozanobd447042015-10-01 14:24:27 -0700497 logger.GetLogger().LogCmd("(Mock) " + cmd, machine,
498 username, print_to_console)
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700499 return 0
500
501
Luis Lozanobd447042015-10-01 14:24:27 -0700502class CommandTerminator(object):
503 """Object to request termination of a command in execution."""
Ahmad Sharif70de27b2011-06-15 17:51:24 -0700504 def __init__(self):
505 self.terminated = False
506
507 def Terminate(self):
508 self.terminated = True
509
510 def IsTerminated(self):
511 return self.terminated