1# -*- coding: utf-8 -*- 2# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Common python commands used by various build scripts.""" 7 8from __future__ import print_function 9 10import base64 11import contextlib 12from datetime import datetime 13import email.utils 14import errno 15import functools 16import getpass 17import inspect 18import operator 19import os 20import re 21import signal 22import socket 23import subprocess 24import sys 25import tempfile 26import time 27 28import six 29 30from autotest_lib.utils.frozen_chromite.lib import constants 31from autotest_lib.utils.frozen_chromite.lib import cros_collections 32from autotest_lib.utils.frozen_chromite.lib import cros_logging as logging 33from autotest_lib.utils.frozen_chromite.lib import signals 34 35 36STRICT_SUDO = False 37 38# For use by ShellQuote. Match all characters that the shell might treat 39# specially. This means a number of things: 40# - Reserved characters. 41# - Characters used in expansions (brace, variable, path, globs, etc...). 42# - Characters that an interactive shell might use (like !). 43# - Whitespace so that one arg turns into multiple. 44# See the bash man page as well as the POSIX shell documentation for more info: 45# http://www.gnu.org/software/bash/manual/bashref.html 46# http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 47_SHELL_QUOTABLE_CHARS = frozenset('[|&;()<> \t!{}[]=*?~$"\'\\#^') 48# The chars that, when used inside of double quotes, need escaping. 49# Order here matters as we need to escape backslashes first. 50_SHELL_ESCAPE_CHARS = r'\"`$' 51 52# The number of files is larger than this, we will use -T option 53# and files to be added may not show up to the command line. 54_THRESHOLD_TO_USE_T_FOR_TAR = 50 55 56 57def ShellQuote(s): 58 """Quote |s| in a way that is safe for use in a shell. 59 60 We aim to be safe, but also to produce "nice" output. That means we don't 61 use quotes when we don't need to, and we prefer to use less quotes (like 62 putting it all in single quotes) than more (using double quotes and escaping 63 a bunch of stuff, or mixing the quotes). 64 65 While python does provide a number of alternatives like: 66 - pipes.quote 67 - shlex.quote 68 They suffer from various problems like: 69 - Not widely available in different python versions. 70 - Do not produce pretty output in many cases. 71 - Are in modules that rarely otherwise get used. 72 73 Note: We don't handle reserved shell words like "for" or "case". This is 74 because those only matter when they're the first element in a command, and 75 there is no use case for that. When we want to run commands, we tend to 76 run real programs and not shell ones. 77 78 Args: 79 s: The string to quote. 80 81 Returns: 82 A safely (possibly quoted) string. 83 """ 84 if sys.version_info.major < 3: 85 # This is a bit of a hack. Python 2 will display strings with u prefixes 86 # when logging which makes things harder to work with. Writing bytes to 87 # stdout will be interpreted as UTF-8 content implicitly. 88 if isinstance(s, six.string_types): 89 try: 90 s = s.encode('utf-8') 91 except UnicodeDecodeError: 92 # We tried our best. Let Python's automatic mixed encoding kick in. 93 pass 94 else: 95 return repr(s) 96 else: 97 # If callers pass down bad types, don't blow up. 98 if isinstance(s, six.binary_type): 99 s = s.decode('utf-8', 'backslashreplace') 100 elif not isinstance(s, six.string_types): 101 return repr(s) 102 103 # See if no quoting is needed so we can return the string as-is. 104 for c in s: 105 if c in _SHELL_QUOTABLE_CHARS: 106 break 107 else: 108 if not s: 109 return "''" 110 else: 111 return s 112 113 # See if we can use single quotes first. Output is nicer. 114 if "'" not in s: 115 return "'%s'" % s 116 117 # Have to use double quotes. Escape the few chars that still expand when 118 # used inside of double quotes. 119 for c in _SHELL_ESCAPE_CHARS: 120 if c in s: 121 s = s.replace(c, r'\%s' % c) 122 return '"%s"' % s 123 124 125def TruncateStringToLine(s, maxlen=80): 126 """Truncate |s| to a maximum length of |maxlen| including elipsis (...) 127 128 Args: 129 s: A string. 130 maxlen: Maximum length of desired returned string. Must be at least 3. 131 132 Returns: 133 s if len(s) <= maxlen already and s has no newline in it. 134 Otherwise, a single line truncation that ends with '...' and is of 135 length |maxlen|. 136 """ 137 assert maxlen >= 3 138 line = s.splitlines()[0] 139 if len(line) <= maxlen: 140 return line 141 else: 142 return line[:maxlen-3] + '...' 143 144 145def ShellUnquote(s): 146 """Do the opposite of ShellQuote. 147 148 This function assumes that the input is a valid escaped string. The behaviour 149 is undefined on malformed strings. 150 151 Args: 152 s: An escaped string. 153 154 Returns: 155 The unescaped version of the string. 156 """ 157 if not s: 158 return '' 159 160 if s[0] == "'": 161 return s[1:-1] 162 163 if s[0] != '"': 164 return s 165 166 s = s[1:-1] 167 output = '' 168 i = 0 169 while i < len(s) - 1: 170 # Skip the backslash when it makes sense. 171 if s[i] == '\\' and s[i + 1] in _SHELL_ESCAPE_CHARS: 172 i += 1 173 output += s[i] 174 i += 1 175 return output + s[i] if i < len(s) else output 176 177 178def CmdToStr(cmd): 179 """Translate a command list into a space-separated string. 180 181 The resulting string should be suitable for logging messages and for 182 pasting into a terminal to run. Command arguments are surrounded by 183 quotes to keep them grouped, even if an argument has spaces in it. 184 185 Examples: 186 ['a', 'b'] ==> "'a' 'b'" 187 ['a b', 'c'] ==> "'a b' 'c'" 188 ['a', 'b\'c'] ==> '\'a\' "b\'c"' 189 [u'a', "/'$b"] ==> '\'a\' "/\'$b"' 190 [] ==> '' 191 See unittest for additional (tested) examples. 192 193 Args: 194 cmd: List of command arguments. 195 196 Returns: 197 String representing full command. 198 """ 199 # If callers pass down bad types, triage it a bit. 200 if isinstance(cmd, (list, tuple)): 201 return ' '.join(ShellQuote(arg) for arg in cmd) 202 else: 203 raise ValueError('cmd must be list or tuple, not %s: %r' % 204 (type(cmd), repr(cmd))) 205 206 207class CompletedProcess(getattr(subprocess, 'CompletedProcess', object)): 208 """An object to store various attributes of a child process. 209 210 This is akin to subprocess.CompletedProcess. 211 """ 212 213 # The linter is confused by the getattr usage above. 214 # TODO(vapier): Drop this once we're Python 3-only and we drop getattr. 215 # pylint: disable=bad-option-value,super-on-old-class 216 def __init__(self, args=None, returncode=None, stdout=None, stderr=None): 217 if sys.version_info.major < 3: 218 self.args = args 219 self.stdout = stdout 220 self.stderr = stderr 221 self.returncode = returncode 222 else: 223 super(CompletedProcess, self).__init__( 224 args=args, returncode=returncode, stdout=stdout, stderr=stderr) 225 226 @property 227 def cmd(self): 228 """Alias to self.args to better match other subprocess APIs.""" 229 return self.args 230 231 @property 232 def cmdstr(self): 233 """Return self.cmd as a well shell-quoted string useful for log messages.""" 234 if self.args is None: 235 return '' 236 else: 237 return CmdToStr(self.args) 238 239 def check_returncode(self): 240 """Raise CalledProcessError if the exit code is non-zero.""" 241 if self.returncode: 242 raise CalledProcessError( 243 returncode=self.returncode, cmd=self.args, stdout=self.stdout, 244 stderr=self.stderr, msg='check_returncode failed') 245 246 247# TODO(crbug.com/1006587): Migrate users to CompletedProcess and drop this. 248class CommandResult(CompletedProcess): 249 """An object to store various attributes of a child process. 250 251 This is akin to subprocess.CompletedProcess. 252 """ 253 254 # The linter is confused by the getattr usage above. 255 # TODO(vapier): Drop this once we're Python 3-only and we drop getattr. 256 # pylint: disable=bad-option-value,super-on-old-class 257 def __init__(self, cmd=None, error=None, output=None, returncode=None, 258 args=None, stdout=None, stderr=None): 259 if args is None: 260 args = cmd 261 elif cmd is not None: 262 raise TypeError('Only specify |args|, not |cmd|') 263 if stdout is None: 264 stdout = output 265 elif output is not None: 266 raise TypeError('Only specify |stdout|, not |output|') 267 if stderr is None: 268 stderr = error 269 elif error is not None: 270 raise TypeError('Only specify |stderr|, not |error|') 271 272 super(CommandResult, self).__init__(args=args, stdout=stdout, stderr=stderr, 273 returncode=returncode) 274 275 @property 276 def output(self): 277 """Backwards compat API.""" 278 return self.stdout 279 280 @property 281 def error(self): 282 """Backwards compat API.""" 283 return self.stderr 284 285 286class CalledProcessError(subprocess.CalledProcessError): 287 """Error caught in run() function. 288 289 This is akin to subprocess.CalledProcessError. We do not support |output|, 290 only |stdout|. 291 292 Attributes: 293 returncode: The exit code of the process. 294 cmd: The command that triggered this exception. 295 msg: Short explanation of the error. 296 exception: The underlying Exception if available. 297 """ 298 299 def __init__(self, returncode, cmd, stdout=None, stderr=None, msg=None, 300 exception=None): 301 if exception is not None and not isinstance(exception, Exception): 302 raise TypeError('exception must be an exception instance; got %r' 303 % (exception,)) 304 305 super(CalledProcessError, self).__init__(returncode, cmd, stdout) 306 # The parent class will set |output|, so delete it. 307 del self.output 308 # TODO(vapier): When we're Python 3-only, delete this assignment as the 309 # parent handles it for us. 310 self.stdout = stdout 311 # TODO(vapier): When we're Python 3-only, move stderr to the init above. 312 self.stderr = stderr 313 self.msg = msg 314 self.exception = exception 315 316 @property 317 def cmdstr(self): 318 """Return self.cmd as a well shell-quoted string useful for log messages.""" 319 if self.cmd is None: 320 return '' 321 else: 322 return CmdToStr(self.cmd) 323 324 def Stringify(self, stdout=True, stderr=True): 325 """Custom method for controlling what is included in stringifying this. 326 327 Args: 328 stdout: Whether to include captured stdout in the return value. 329 stderr: Whether to include captured stderr in the return value. 330 331 Returns: 332 A summary string for this result. 333 """ 334 items = [ 335 u'return code: %s; command: %s' % ( 336 self.returncode, self.cmdstr), 337 ] 338 if stderr and self.stderr: 339 stderr = self.stderr 340 if isinstance(stderr, six.binary_type): 341 stderr = stderr.decode('utf-8', 'replace') 342 items.append(stderr) 343 if stdout and self.stdout: 344 stdout = self.stdout 345 if isinstance(stdout, six.binary_type): 346 stdout = stdout.decode('utf-8', 'replace') 347 items.append(stdout) 348 if self.msg: 349 msg = self.msg 350 if isinstance(msg, six.binary_type): 351 msg = msg.decode('utf-8', 'replace') 352 items.append(msg) 353 return u'\n'.join(items) 354 355 def __str__(self): 356 if sys.version_info.major < 3: 357 # __str__ needs to return ascii, thus force a conversion to be safe. 358 return self.Stringify().encode('ascii', 'xmlcharrefreplace') 359 else: 360 return self.Stringify() 361 362 def __eq__(self, other): 363 return (isinstance(other, type(self)) and 364 self.returncode == other.returncode and 365 self.cmd == other.cmd and 366 self.stdout == other.stdout and 367 self.stderr == other.stderr and 368 self.msg == other.msg and 369 self.exception == other.exception) 370 371 def __ne__(self, other): 372 return not self.__eq__(other) 373 374 375# TODO(crbug.com/1006587): Migrate users to CompletedProcess and drop this. 376class RunCommandError(CalledProcessError): 377 """Error caught in run() method. 378 379 Attributes: 380 args: Tuple of the attributes below. 381 msg: Short explanation of the error. 382 result: The CommandResult that triggered this error, if available. 383 exception: The underlying Exception if available. 384 """ 385 386 def __init__(self, msg, result=None, exception=None): 387 # This makes mocking tests easier. 388 if result is None: 389 result = CommandResult() 390 elif not isinstance(result, CommandResult): 391 raise TypeError('result must be a CommandResult instance; got %r' 392 % (result,)) 393 394 self.args = (msg, result, exception) 395 self.result = result 396 super(RunCommandError, self).__init__( 397 returncode=result.returncode, cmd=result.args, stdout=result.stdout, 398 stderr=result.stderr, msg=msg, exception=exception) 399 400 401class TerminateRunCommandError(RunCommandError): 402 """We were signaled to shutdown while running a command. 403 404 Client code shouldn't generally know, nor care about this class. It's 405 used internally to suppress retry attempts when we're signaled to die. 406 """ 407 408 409def sudo_run(cmd, user='root', preserve_env=False, **kwargs): 410 """Run a command via sudo. 411 412 Client code must use this rather than coming up with their own run 413 invocation that jams sudo in- this function is used to enforce certain 414 rules in our code about sudo usage, and as a potential auditing point. 415 416 Args: 417 cmd: The command to run. See run for rules of this argument: sudo_run 418 purely prefixes it with sudo. 419 user: The user to run the command as. 420 preserve_env (bool): Whether to preserve the environment. 421 kwargs: See run() options, it's a direct pass thru to it. 422 Note that this supports a 'strict' keyword that defaults to True. 423 If set to False, it'll suppress strict sudo behavior. 424 425 Returns: 426 See run documentation. 427 428 Raises: 429 This function may immediately raise RunCommandError if we're operating 430 in a strict sudo context and the API is being misused. 431 Barring that, see run's documentation: it can raise the same things run 432 does. 433 """ 434 sudo_cmd = ['sudo'] 435 436 strict = kwargs.pop('strict', True) 437 438 if user == 'root' and os.geteuid() == 0: 439 return run(cmd, **kwargs) 440 441 if strict and STRICT_SUDO: 442 if 'CROS_SUDO_KEEP_ALIVE' not in os.environ: 443 raise RunCommandError( 444 'We were invoked in a strict sudo non - interactive context, but no ' 445 'sudo keep alive daemon is running. This is a bug in the code.', 446 CommandResult(args=cmd, returncode=126)) 447 sudo_cmd += ['-n'] 448 449 if user != 'root': 450 sudo_cmd += ['-u', user] 451 452 if preserve_env: 453 sudo_cmd += ['--preserve-env'] 454 455 # Pass these values down into the sudo environment, since sudo will 456 # just strip them normally. 457 extra_env = kwargs.pop('extra_env', None) 458 extra_env = {} if extra_env is None else extra_env.copy() 459 460 for var in constants.ENV_PASSTHRU: 461 if var not in extra_env and var in os.environ: 462 extra_env[var] = os.environ[var] 463 464 sudo_cmd.extend('%s=%s' % (k, v) for k, v in extra_env.items()) 465 466 # Finally, block people from passing options to sudo. 467 sudo_cmd.append('--') 468 469 if isinstance(cmd, six.string_types): 470 # We need to handle shell ourselves so the order is correct: 471 # $ sudo [sudo args] -- bash -c '[shell command]' 472 # If we let run take care of it, we'd end up with: 473 # $ bash -c 'sudo [sudo args] -- [shell command]' 474 shell = kwargs.pop('shell', False) 475 if not shell: 476 raise Exception('Cannot run a string command without a shell') 477 sudo_cmd.extend(['/bin/bash', '-c', cmd]) 478 else: 479 sudo_cmd.extend(cmd) 480 481 return run(sudo_cmd, **kwargs) 482 483 484def _KillChildProcess(proc, int_timeout, kill_timeout, cmd, original_handler, 485 signum, frame): 486 """Used as a signal handler by run. 487 488 This is internal to run. No other code should use this. 489 """ 490 if signum: 491 # If we've been invoked because of a signal, ignore delivery of that signal 492 # from this point forward. The invoking context of _KillChildProcess 493 # restores signal delivery to what it was prior; we suppress future delivery 494 # till then since this code handles SIGINT/SIGTERM fully including 495 # delivering the signal to the original handler on the way out. 496 signal.signal(signum, signal.SIG_IGN) 497 498 # Do not trust Popen's returncode alone; we can be invoked from contexts where 499 # the Popen instance was created, but no process was generated. 500 if proc.returncode is None and proc.pid is not None: 501 try: 502 while proc.poll_lock_breaker() is None and int_timeout >= 0: 503 time.sleep(0.1) 504 int_timeout -= 0.1 505 506 proc.terminate() 507 while proc.poll_lock_breaker() is None and kill_timeout >= 0: 508 time.sleep(0.1) 509 kill_timeout -= 0.1 510 511 if proc.poll_lock_breaker() is None: 512 # Still doesn't want to die. Too bad, so sad, time to die. 513 proc.kill() 514 except EnvironmentError as e: 515 logging.warning('Ignoring unhandled exception in _KillChildProcess: %s', 516 e) 517 518 # Ensure our child process has been reaped. 519 kwargs = {} 520 if sys.version_info.major >= 3: 521 # ... but don't wait forever. 522 kwargs['timeout'] = 60 523 proc.wait_lock_breaker(**kwargs) 524 525 if not signals.RelaySignal(original_handler, signum, frame): 526 # Mock up our own, matching exit code for signaling. 527 cmd_result = CommandResult(args=cmd, returncode=signum << 8) 528 raise TerminateRunCommandError('Received signal %i' % signum, cmd_result) 529 530 531class _Popen(subprocess.Popen): 532 """subprocess.Popen derivative customized for our usage. 533 534 Specifically, we fix terminate/send_signal/kill to work if the child process 535 was a setuid binary; on vanilla kernels, the parent can wax the child 536 regardless, on goobuntu this apparently isn't allowed, thus we fall back 537 to the sudo machinery we have. 538 539 While we're overriding send_signal, we also suppress ESRCH being raised 540 if the process has exited, and suppress signaling all together if the process 541 has knowingly been waitpid'd already. 542 """ 543 544 # Pylint seems to be buggy with the send_signal signature detection. 545 # pylint: disable=arguments-differ 546 def send_signal(self, sig): 547 if self.returncode is not None: 548 # The original implementation in Popen would allow signaling whatever 549 # process now occupies this pid, even if the Popen object had waitpid'd. 550 # Since we can escalate to sudo kill, we do not want to allow that. 551 # Fixing this addresses that angle, and makes the API less sucky in the 552 # process. 553 return 554 555 try: 556 os.kill(self.pid, sig) 557 except EnvironmentError as e: 558 if e.errno == errno.EPERM: 559 # Kill returns either 0 (signal delivered), or 1 (signal wasn't 560 # delivered). This isn't particularly informative, but we still 561 # need that info to decide what to do, thus the check=False. 562 ret = sudo_run(['kill', '-%i' % sig, str(self.pid)], 563 print_cmd=False, stdout=True, 564 stderr=True, check=False) 565 if ret.returncode == 1: 566 # The kill binary doesn't distinguish between permission denied, 567 # and the pid is missing. Denied can only occur under weird 568 # grsec/selinux policies. We ignore that potential and just 569 # assume the pid was already dead and try to reap it. 570 self.poll() 571 elif e.errno == errno.ESRCH: 572 # Since we know the process is dead, reap it now. 573 # Normally Popen would throw this error- we suppress it since frankly 574 # that's a misfeature and we're already overriding this method. 575 self.poll() 576 else: 577 raise 578 579 def _lock_breaker(self, func, *args, **kwargs): 580 """Helper to manage the waitpid lock. 581 582 Workaround https://bugs.python.org/issue25960. 583 """ 584 # If the lock doesn't exist, or is not locked, call the func directly. 585 lock = getattr(self, '_waitpid_lock', None) 586 if lock is not None and lock.locked(): 587 try: 588 lock.release() 589 return func(*args, **kwargs) 590 finally: 591 if not lock.locked(): 592 lock.acquire() 593 else: 594 return func(*args, **kwargs) 595 596 def poll_lock_breaker(self, *args, **kwargs): 597 """Wrapper around poll() to break locks if needed.""" 598 return self._lock_breaker(self.poll, *args, **kwargs) 599 600 def wait_lock_breaker(self, *args, **kwargs): 601 """Wrapper around wait() to break locks if needed.""" 602 return self._lock_breaker(self.wait, *args, **kwargs) 603 604 605# pylint: disable=redefined-builtin 606def run(cmd, print_cmd=True, stdout=None, stderr=None, 607 cwd=None, input=None, enter_chroot=False, 608 shell=False, env=None, extra_env=None, ignore_sigint=False, 609 chroot_args=None, debug_level=logging.INFO, 610 check=True, int_timeout=1, kill_timeout=1, 611 log_output=False, capture_output=False, 612 quiet=False, encoding=None, errors=None, dryrun=False, 613 **kwargs): 614 """Runs a command. 615 616 Args: 617 cmd: cmd to run. Should be input to subprocess.Popen. If a string, shell 618 must be true. Otherwise the command must be an array of arguments, and 619 shell must be false. 620 print_cmd: prints the command before running it. 621 stdout: Where to send stdout. This may be many things to control 622 redirection: 623 * None is the default; the existing stdout is used. 624 * An existing file object (must be opened with mode 'w' or 'wb'). 625 * A string to a file (will be truncated & opened automatically). 626 * subprocess.PIPE to capture & return the output. 627 * A boolean to indicate whether to capture the output. 628 True will capture the output via a tempfile (good for large output). 629 * An open file descriptor (as a positive integer). 630 stderr: Where to send stderr. See |stdout| for possible values. This also 631 may be subprocess.STDOUT to indicate stderr & stdout should be combined. 632 cwd: the working directory to run this cmd. 633 input: The data to pipe into this command through stdin. If a file object 634 or file descriptor, stdin will be connected directly to that. 635 enter_chroot: this command should be run from within the chroot. If set, 636 cwd must point to the scripts directory. If we are already inside the 637 chroot, this command will be run as if |enter_chroot| is False. 638 shell: Controls whether we add a shell as a command interpreter. See cmd 639 since it has to agree as to the type. 640 env: If non-None, this is the environment for the new process. If 641 enter_chroot is true then this is the environment of the enter_chroot, 642 most of which gets removed from the cmd run. 643 extra_env: If set, this is added to the environment for the new process. 644 In enter_chroot=True case, these are specified on the post-entry 645 side, and so are often more useful. This dictionary is not used to 646 clear any entries though. 647 ignore_sigint: If True, we'll ignore signal.SIGINT before calling the 648 child. This is the desired behavior if we know our child will handle 649 Ctrl-C. If we don't do this, I think we and the child will both get 650 Ctrl-C at the same time, which means we'll forcefully kill the child. 651 chroot_args: An array of arguments for the chroot environment wrapper. 652 debug_level: The debug level of run's output. 653 check: Whether to raise an exception when command returns a non-zero exit 654 code, or return the CommandResult object containing the exit code. 655 Note: will still raise an exception if the cmd file does not exist. 656 int_timeout: If we're interrupted, how long (in seconds) should we give the 657 invoked process to clean up before we send a SIGTERM. 658 kill_timeout: If we're interrupted, how long (in seconds) should we give the 659 invoked process to shutdown from a SIGTERM before we SIGKILL it. 660 log_output: Log the command and its output automatically. 661 capture_output: Set |stdout| and |stderr| to True. 662 quiet: Set |print_cmd| to False, and |capture_output| to True. 663 encoding: Encoding for stdin/stdout/stderr, otherwise bytes are used. Most 664 users want 'utf-8' here for string data. 665 errors: How to handle errors when |encoding| is used. Defaults to 'strict', 666 but 'ignore' and 'replace' are common settings. 667 dryrun: Only log the command,and return a stub result. 668 669 Returns: 670 A CommandResult object. 671 672 Raises: 673 RunCommandError: Raised on error. 674 """ 675 # Hide this function in pytest tracebacks when a RunCommandError is raised, 676 # as seeing the contents of this function when a command fails is not helpful. 677 # https://docs.pytest.org/en/latest/example/simple.html#writing-well-integrated-assertion-helpers 678 __tracebackhide__ = operator.methodcaller('errisinstance', RunCommandError) 679 680 # Handle backwards compatible settings. 681 if 'log_stdout_to_file' in kwargs: 682 logging.warning('run: log_stdout_to_file=X is now stdout=X') 683 log_stdout_to_file = kwargs.pop('log_stdout_to_file') 684 if log_stdout_to_file is not None: 685 stdout = log_stdout_to_file 686 stdout_file_mode = 'w+b' 687 if 'append_to_file' in kwargs: 688 # TODO(vapier): Enable this warning once chromite & users migrate. 689 # logging.warning('run: append_to_file is now part of stdout') 690 if kwargs.pop('append_to_file'): 691 stdout_file_mode = 'a+b' 692 assert not kwargs, 'Unknown arguments to run: %s' % (list(kwargs),) 693 694 if quiet: 695 print_cmd = False 696 capture_output = True 697 698 if capture_output: 699 # TODO(vapier): Enable this once we migrate all the legacy arguments above. 700 # if stdout is not None or stderr is not None: 701 # raise ValueError('capture_output may not be used with stdout & stderr') 702 # TODO(vapier): Drop this specialization once we're Python 3-only as we can 703 # pass this argument down to Popen directly. 704 if stdout is None: 705 stdout = True 706 if stderr is None: 707 stderr = True 708 709 if encoding is not None and errors is None: 710 errors = 'strict' 711 712 # Set default for variables. 713 popen_stdout = None 714 popen_stderr = None 715 stdin = None 716 cmd_result = CommandResult() 717 718 # Force the timeout to float; in the process, if it's not convertible, 719 # a self-explanatory exception will be thrown. 720 kill_timeout = float(kill_timeout) 721 722 def _get_tempfile(): 723 try: 724 return UnbufferedTemporaryFile() 725 except EnvironmentError as e: 726 if e.errno != errno.ENOENT: 727 raise 728 # This can occur if we were pointed at a specific location for our 729 # TMP, but that location has since been deleted. Suppress that issue 730 # in this particular case since our usage gurantees deletion, 731 # and since this is primarily triggered during hard cgroups shutdown. 732 return UnbufferedTemporaryFile(dir='/tmp') 733 734 # Modify defaults based on parameters. 735 # Note that tempfiles must be unbuffered else attempts to read 736 # what a separate process did to that file can result in a bad 737 # view of the file. 738 log_stdout_to_file = False 739 if isinstance(stdout, six.string_types): 740 popen_stdout = open(stdout, stdout_file_mode) 741 log_stdout_to_file = True 742 elif hasattr(stdout, 'fileno'): 743 popen_stdout = stdout 744 log_stdout_to_file = True 745 elif isinstance(stdout, bool): 746 # This check must come before isinstance(int) because bool subclasses int. 747 if stdout: 748 popen_stdout = _get_tempfile() 749 elif isinstance(stdout, int): 750 popen_stdout = stdout 751 elif log_output: 752 popen_stdout = _get_tempfile() 753 754 log_stderr_to_file = False 755 if hasattr(stderr, 'fileno'): 756 popen_stderr = stderr 757 log_stderr_to_file = True 758 elif isinstance(stderr, bool): 759 # This check must come before isinstance(int) because bool subclasses int. 760 if stderr: 761 popen_stderr = _get_tempfile() 762 elif isinstance(stderr, int): 763 popen_stderr = stderr 764 elif log_output: 765 popen_stderr = _get_tempfile() 766 767 # If subprocesses have direct access to stdout or stderr, they can bypass 768 # our buffers, so we need to flush to ensure that output is not interleaved. 769 if popen_stdout is None or popen_stderr is None: 770 sys.stdout.flush() 771 sys.stderr.flush() 772 773 # If input is a string, we'll create a pipe and send it through that. 774 # Otherwise we assume it's a file object that can be read from directly. 775 if isinstance(input, (six.string_types, six.binary_type)): 776 stdin = subprocess.PIPE 777 # Allow people to always pass in bytes or strings regardless of encoding. 778 # Our Popen usage takes care of converting everything to bytes first. 779 # 780 # Linter can't see that we're using |input| as a var, not a builtin. 781 # pylint: disable=input-builtin 782 if encoding and isinstance(input, six.text_type): 783 input = input.encode(encoding, errors) 784 elif not encoding and isinstance(input, six.text_type): 785 input = input.encode('utf-8') 786 elif input is not None: 787 stdin = input 788 input = None 789 790 # Sanity check the command. This helps when RunCommand is deep in the call 791 # chain, but the command itself was constructed along the way. 792 if isinstance(cmd, (six.string_types, six.binary_type)): 793 if not shell: 794 raise ValueError('Cannot run a string command without a shell') 795 cmd = ['/bin/bash', '-c', cmd] 796 shell = False 797 elif shell: 798 raise ValueError('Cannot run an array command with a shell') 799 elif not cmd: 800 raise ValueError('Missing command to run') 801 elif not isinstance(cmd, (list, tuple)): 802 raise TypeError('cmd must be list or tuple, not %s: %r' % 803 (type(cmd), repr(cmd))) 804 elif not all(isinstance(x, (six.binary_type, six.string_types)) for x in cmd): 805 raise TypeError('All command elements must be bytes/strings: %r' % (cmd,)) 806 807 # If we are using enter_chroot we need to use enterchroot pass env through 808 # to the final command. 809 env = env.copy() if env is not None else os.environ.copy() 810 # Looking at localized error messages may be unexpectedly dangerous, so we 811 # set LC_MESSAGES=C to make sure the output of commands is safe to inspect. 812 env['LC_MESSAGES'] = 'C' 813 env.update(extra_env if extra_env else {}) 814 815 if enter_chroot and not IsInsideChroot(): 816 wrapper = ['cros_sdk'] 817 if cwd: 818 # If the current working directory is set, try to find cros_sdk relative 819 # to cwd. Generally cwd will be the buildroot therefore we want to use 820 # {cwd}/chromite/bin/cros_sdk. For more info PTAL at crbug.com/432620 821 path = os.path.join(cwd, constants.CHROMITE_BIN_SUBDIR, 'cros_sdk') 822 if os.path.exists(path): 823 wrapper = [path] 824 825 if chroot_args: 826 wrapper += chroot_args 827 828 if extra_env: 829 wrapper.extend('%s=%s' % (k, v) for k, v in extra_env.items()) 830 831 cmd = wrapper + ['--'] + cmd 832 833 for var in constants.ENV_PASSTHRU: 834 if var not in env and var in os.environ: 835 env[var] = os.environ[var] 836 837 # Print out the command before running. 838 if dryrun or print_cmd or log_output: 839 log = '' 840 if dryrun: 841 log += '(dryrun) ' 842 log += 'run: %s' % (CmdToStr(cmd),) 843 if cwd: 844 log += ' in %s' % (cwd,) 845 logging.log(debug_level, '%s', log) 846 847 cmd_result.args = cmd 848 849 # We want to still something in dryrun mode so we process all the options 850 # and return appropriate values (e.g. output with correct encoding). 851 popen_cmd = ['true'] if dryrun else cmd 852 853 proc = None 854 # Verify that the signals modules is actually usable, and won't segfault 855 # upon invocation of getsignal. See signals.SignalModuleUsable for the 856 # details and upstream python bug. 857 use_signals = signals.SignalModuleUsable() 858 try: 859 proc = _Popen(popen_cmd, cwd=cwd, stdin=stdin, stdout=popen_stdout, 860 stderr=popen_stderr, shell=False, env=env, 861 close_fds=True) 862 863 if use_signals: 864 if ignore_sigint: 865 old_sigint = signal.signal(signal.SIGINT, signal.SIG_IGN) 866 else: 867 old_sigint = signal.getsignal(signal.SIGINT) 868 signal.signal(signal.SIGINT, 869 functools.partial(_KillChildProcess, proc, int_timeout, 870 kill_timeout, cmd, old_sigint)) 871 872 old_sigterm = signal.getsignal(signal.SIGTERM) 873 signal.signal(signal.SIGTERM, 874 functools.partial(_KillChildProcess, proc, int_timeout, 875 kill_timeout, cmd, old_sigterm)) 876 877 try: 878 (cmd_result.stdout, cmd_result.stderr) = proc.communicate(input) 879 finally: 880 if use_signals: 881 signal.signal(signal.SIGINT, old_sigint) 882 signal.signal(signal.SIGTERM, old_sigterm) 883 884 if (popen_stdout and not isinstance(popen_stdout, int) and 885 not log_stdout_to_file): 886 popen_stdout.seek(0) 887 cmd_result.stdout = popen_stdout.read() 888 popen_stdout.close() 889 elif log_stdout_to_file: 890 popen_stdout.close() 891 892 if (popen_stderr and not isinstance(popen_stderr, int) and 893 not log_stderr_to_file): 894 popen_stderr.seek(0) 895 cmd_result.stderr = popen_stderr.read() 896 popen_stderr.close() 897 898 cmd_result.returncode = proc.returncode 899 900 # The try/finally block is a bit hairy. We normally want the logged 901 # output to be what gets passed back up. But if there's a decode error, 902 # we don't want it to break logging entirely. If the output had a lot of 903 # newlines, always logging it as bytes wouldn't be human readable. 904 try: 905 if encoding: 906 if cmd_result.stdout is not None: 907 cmd_result.stdout = cmd_result.stdout.decode(encoding, errors) 908 if cmd_result.stderr is not None: 909 cmd_result.stderr = cmd_result.stderr.decode(encoding, errors) 910 finally: 911 if log_output: 912 if cmd_result.stdout: 913 logging.log(debug_level, '(stdout):\n%s', cmd_result.stdout) 914 if cmd_result.stderr: 915 logging.log(debug_level, '(stderr):\n%s', cmd_result.stderr) 916 917 if check and proc.returncode: 918 msg = 'cmd=%s' % cmd 919 if cwd: 920 msg += ', cwd=%s' % cwd 921 if extra_env: 922 msg += ', extra env=%s' % extra_env 923 raise RunCommandError(msg, cmd_result) 924 except OSError as e: 925 estr = str(e) 926 if e.errno == errno.EACCES: 927 estr += '; does the program need `chmod a+x`?' 928 raise RunCommandError(estr, CommandResult(args=cmd), exception=e) 929 finally: 930 if proc is not None: 931 # Ensure the process is dead. 932 _KillChildProcess(proc, int_timeout, kill_timeout, cmd, None, None, None) 933 934 # We might capture stdout/stderr for internal reasons (like logging), but we 935 # don't want to let it leak back out to the callers. They only get output if 936 # they explicitly requested it. 937 if stdout is None: 938 cmd_result.stdout = None 939 if stderr is None: 940 cmd_result.stderr = None 941 942 return cmd_result 943# pylint: enable=redefined-builtin 944 945 946# Convenience run methods. 947# 948# We don't use functools.partial because it binds the methods at import time, 949# which doesn't work well with unit tests, since it bypasses the mock that may 950# be set up for run. 951 952def dbg_run(*args, **kwargs): 953 kwargs.setdefault('debug_level', logging.DEBUG) 954 return run(*args, **kwargs) 955 956 957class DieSystemExit(SystemExit): 958 """Custom Exception used so we can intercept this if necessary.""" 959 960 961def Die(message, *args, **kwargs): 962 """Emits an error message with a stack trace and halts execution. 963 964 Args: 965 message: The message to be emitted before exiting. 966 """ 967 logging.error(message, *args, **kwargs) 968 raise DieSystemExit(1) 969 970 971def GetSysrootToolPath(sysroot, tool_name): 972 """Returns the path to the sysroot specific version of a tool. 973 974 Does not check that the tool actually exists. 975 976 Args: 977 sysroot: build root of the system in question. 978 tool_name: string name of tool desired (e.g. 'equery'). 979 980 Returns: 981 string path to tool inside the sysroot. 982 """ 983 if sysroot == '/': 984 return os.path.join(sysroot, 'usr', 'bin', tool_name) 985 986 return os.path.join(sysroot, 'build', 'bin', tool_name) 987 988 989def IsInsideChroot(): 990 """Returns True if we are inside chroot.""" 991 return os.path.exists('/etc/cros_chroot_version') 992 993 994def IsOutsideChroot(): 995 """Returns True if we are outside chroot.""" 996 return not IsInsideChroot() 997 998 999def AssertInsideChroot(): 1000 """Die if we are outside the chroot""" 1001 if not IsInsideChroot(): 1002 Die('%s: please run inside the chroot', os.path.basename(sys.argv[0])) 1003 1004 1005def AssertOutsideChroot(): 1006 """Die if we are inside the chroot""" 1007 if IsInsideChroot(): 1008 Die('%s: please run outside the chroot', os.path.basename(sys.argv[0])) 1009 1010 1011def GetHostName(fully_qualified=False): 1012 """Return hostname of current machine, with domain if |fully_qualified|.""" 1013 hostname = socket.gethostname() 1014 try: 1015 hostname = socket.gethostbyaddr(hostname)[0] 1016 except (socket.gaierror, socket.herror) as e: 1017 logging.warning('please check your /etc/hosts file; resolving your hostname' 1018 ' (%s) failed: %s', hostname, e) 1019 1020 if fully_qualified: 1021 return hostname 1022 else: 1023 return hostname.partition('.')[0] 1024 1025 1026def GetHostDomain(): 1027 """Return domain of current machine. 1028 1029 If there is no domain, return 'localdomain'. 1030 """ 1031 1032 hostname = GetHostName(fully_qualified=True) 1033 domain = hostname.partition('.')[2] 1034 return domain if domain else 'localdomain' 1035 1036 1037def HostIsCIBuilder(fq_hostname=None, golo_only=False, gce_only=False): 1038 """Return True iff a host is a continuous-integration builder. 1039 1040 Args: 1041 fq_hostname: The fully qualified hostname. By default, we fetch it for you. 1042 golo_only: Only return True if the host is in the Chrome Golo. Defaults to 1043 False. 1044 gce_only: Only return True if the host is in the Chrome GCE block. Defaults 1045 to False. 1046 """ 1047 if not fq_hostname: 1048 fq_hostname = GetHostName(fully_qualified=True) 1049 in_golo = fq_hostname.endswith('.' + constants.GOLO_DOMAIN) 1050 in_gce = (fq_hostname.endswith('.' + constants.CHROME_DOMAIN) or 1051 fq_hostname.endswith('.' + constants.CHROMEOS_BOT_INTERNAL)) 1052 if golo_only: 1053 return in_golo 1054 elif gce_only: 1055 return in_gce 1056 else: 1057 return in_golo or in_gce 1058 1059 1060COMP_NONE = 0 1061COMP_GZIP = 1 1062COMP_BZIP2 = 2 1063COMP_XZ = 3 1064 1065 1066def FindCompressor(compression, chroot=None): 1067 """Locate a compressor utility program (possibly in a chroot). 1068 1069 Since we compress/decompress a lot, make it easy to locate a 1070 suitable utility program in a variety of locations. We favor 1071 the one in the chroot over /, and the parallel implementation 1072 over the single threaded one. 1073 1074 Args: 1075 compression: The type of compression desired. 1076 chroot: Optional path to a chroot to search. 1077 1078 Returns: 1079 Path to a compressor. 1080 1081 Raises: 1082 ValueError: If compression is unknown. 1083 """ 1084 if compression == COMP_XZ: 1085 return os.path.join(constants.CHROMITE_SCRIPTS_DIR, 'xz_auto') 1086 elif compression == COMP_GZIP: 1087 std = 'gzip' 1088 para = 'pigz' 1089 elif compression == COMP_BZIP2: 1090 std = 'bzip2' 1091 para = 'pbzip2' 1092 elif compression == COMP_NONE: 1093 return 'cat' 1094 else: 1095 raise ValueError('unknown compression') 1096 1097 roots = [] 1098 if chroot: 1099 roots.append(chroot) 1100 roots.append('/') 1101 1102 for prog in [para, std]: 1103 for root in roots: 1104 for subdir in ['', 'usr']: 1105 path = os.path.join(root, subdir, 'bin', prog) 1106 if os.path.exists(path): 1107 return path 1108 1109 return std 1110 1111 1112def CompressionStrToType(s): 1113 """Convert a compression string type to a constant. 1114 1115 Args: 1116 s: string to check 1117 1118 Returns: 1119 A constant, or None if the compression type is unknown. 1120 """ 1121 _COMP_STR = { 1122 'gz': COMP_GZIP, 1123 'bz2': COMP_BZIP2, 1124 'xz': COMP_XZ, 1125 } 1126 if s: 1127 return _COMP_STR.get(s) 1128 else: 1129 return COMP_NONE 1130 1131 1132def CompressionExtToType(file_name): 1133 """Retrieve a compression type constant from a compression file's name. 1134 1135 Args: 1136 file_name: Name of a compression file. 1137 1138 Returns: 1139 A constant, return COMP_NONE if the extension is unknown. 1140 """ 1141 ext = os.path.splitext(file_name)[-1] 1142 _COMP_EXT = { 1143 '.tgz': COMP_GZIP, 1144 '.gz': COMP_GZIP, 1145 '.tbz2': COMP_BZIP2, 1146 '.bz2': COMP_BZIP2, 1147 '.txz': COMP_XZ, 1148 '.xz': COMP_XZ, 1149 } 1150 return _COMP_EXT.get(ext, COMP_NONE) 1151 1152 1153def CompressFile(infile, outfile): 1154 """Compress a file using compressor specified by |outfile| suffix. 1155 1156 Args: 1157 infile: File to compress. 1158 outfile: Name of output file. Compression used is based on the 1159 type of suffix of the name specified (e.g.: .bz2). 1160 """ 1161 comp_type = CompressionExtToType(outfile) 1162 assert comp_type and comp_type != COMP_NONE 1163 comp = FindCompressor(comp_type) 1164 if os.path.basename(comp) == 'pixz': 1165 # pixz does not accept '-c'; instead an explicit '-i' indicates input file 1166 # should not be deleted, and '-o' specifies output file. 1167 cmd = [comp, '-i', infile, '-o', outfile] 1168 run(cmd) 1169 else: 1170 cmd = [comp, '-c', infile] 1171 run(cmd, stdout=outfile) 1172 1173 1174def UncompressFile(infile, outfile): 1175 """Uncompress a file using compressor specified by |infile| suffix. 1176 1177 Args: 1178 infile: File to uncompress. Compression used is based on the 1179 type of suffix of the name specified (e.g.: .bz2). 1180 outfile: Name of output file. 1181 """ 1182 comp_type = CompressionExtToType(infile) 1183 assert comp_type and comp_type != COMP_NONE 1184 comp = FindCompressor(comp_type) 1185 if os.path.basename(comp) == 'pixz': 1186 # pixz does not accept '-c'; instead an explicit '-i' indicates input file 1187 # should not be deleted, and '-o' specifies output file. 1188 cmd = [comp, '-d', '-i', infile, '-o', outfile] 1189 run(cmd) 1190 else: 1191 cmd = [comp, '-dc', infile] 1192 run(cmd, stdout=outfile) 1193 1194 1195class CreateTarballError(RunCommandError): 1196 """Error while running tar. 1197 1198 We may run tar multiple times because of "soft" errors. The result is from 1199 the last run instance. 1200 """ 1201 1202 1203def CreateTarball(target, cwd, sudo=False, compression=COMP_XZ, chroot=None, 1204 inputs=None, timeout=300, extra_args=None, **kwargs): 1205 """Create a tarball. Executes 'tar' on the commandline. 1206 1207 Args: 1208 target: The path of the tar file to generate. 1209 cwd: The directory to run the tar command. 1210 sudo: Whether to run with "sudo". 1211 compression: The type of compression desired. See the FindCompressor 1212 function for details. 1213 chroot: See FindCompressor(). 1214 inputs: A list of files or directories to add to the tarball. If unset, 1215 defaults to ".". 1216 timeout: The number of seconds to wait on soft failure. 1217 extra_args: A list of extra args to pass to "tar". 1218 kwargs: Any run options/overrides to use. 1219 1220 Returns: 1221 The cmd_result object returned by the run invocation. 1222 1223 Raises: 1224 CreateTarballError: if the tar command failed, possibly after retry. 1225 """ 1226 if inputs is None: 1227 inputs = ['.'] 1228 1229 if extra_args is None: 1230 extra_args = [] 1231 kwargs.setdefault('debug_level', logging.INFO) 1232 1233 comp = FindCompressor(compression, chroot=chroot) 1234 cmd = (['tar'] + 1235 extra_args + 1236 ['--sparse', '-I', comp, '-cf', target]) 1237 if len(inputs) > _THRESHOLD_TO_USE_T_FOR_TAR: 1238 cmd += ['--null', '-T', '/dev/stdin'] 1239 rc_input = b'\0'.join(x.encode('utf-8') for x in inputs) 1240 else: 1241 cmd += list(inputs) 1242 rc_input = None 1243 1244 rc_func = sudo_run if sudo else run 1245 1246 # If tar fails with status 1, retry twice. Once after timeout seconds and 1247 # again 2*timeout seconds after that. 1248 for try_count in range(3): 1249 try: 1250 result = rc_func(cmd, cwd=cwd, **dict(kwargs, check=False, 1251 input=rc_input)) 1252 except RunCommandError as rce: 1253 # There are cases where run never executes the command (cannot find tar, 1254 # cannot execute tar, such as when cwd does not exist). Although the run 1255 # command will show low-level problems, we also want to log the context 1256 # of what CreateTarball was trying to do. 1257 logging.error('CreateTarball unable to run tar for %s in %s. cmd={%s}', 1258 target, cwd, cmd) 1259 raise rce 1260 if result.returncode == 0: 1261 return result 1262 if result.returncode != 1 or try_count > 1: 1263 # Since the build is abandoned at this point, we will take 5 1264 # entire minutes to track down the competing process. 1265 # Error will have the low-level tar command error, so log the context 1266 # of the tar command (target file, current working dir). 1267 logging.error('CreateTarball failed creating %s in %s. cmd={%s}', 1268 target, cwd, cmd) 1269 raise CreateTarballError('CreateTarball', result) 1270 1271 assert result.returncode == 1 1272 time.sleep(timeout * (try_count + 1)) 1273 logging.warning('CreateTarball: tar: source modification time changed ' 1274 '(see crbug.com/547055), retrying') 1275 logging.PrintBuildbotStepWarnings() 1276 1277 1278def GetInput(prompt): 1279 """Helper function to grab input from a user. Makes testing easier.""" 1280 # We have people use GetInput() so they don't have to use these bad builtins 1281 # themselves or deal with version skews. 1282 # pylint: disable=bad-builtin,input-builtin,raw_input-builtin,undefined-variable 1283 if sys.version_info.major < 3: 1284 return raw_input(prompt) 1285 else: 1286 return input(prompt) 1287 1288 1289def GetChoice(title, options, group_size=0): 1290 """Ask user to choose an option from the list. 1291 1292 When |group_size| is 0, then all items in |options| will be extracted and 1293 shown at the same time. Otherwise, the items will be extracted |group_size| 1294 at a time, and then shown to the user. This makes it easier to support 1295 generators that are slow, extremely large, or people usually want to pick 1296 from the first few choices. 1297 1298 Args: 1299 title: The text to display before listing options. 1300 options: Iterable which provides options to display. 1301 group_size: How many options to show before asking the user to choose. 1302 1303 Returns: 1304 An integer of the index in |options| the user picked. 1305 """ 1306 def PromptForChoice(max_choice, more): 1307 prompt = 'Please choose an option [0-%d]' % max_choice 1308 if more: 1309 prompt += ' (Enter for more options)' 1310 prompt += ': ' 1311 1312 while True: 1313 choice = GetInput(prompt) 1314 if more and not choice.strip(): 1315 return None 1316 try: 1317 choice = int(choice) 1318 except ValueError: 1319 print('Input is not an integer') 1320 continue 1321 if choice < 0 or choice > max_choice: 1322 print('Choice %d out of range (0-%d)' % (choice, max_choice)) 1323 continue 1324 return choice 1325 1326 print(title) 1327 max_choice = 0 1328 for i, opt in enumerate(options): 1329 if i and group_size and not i % group_size: 1330 choice = PromptForChoice(i - 1, True) 1331 if choice is not None: 1332 return choice 1333 print(' [%d]: %s' % (i, opt)) 1334 max_choice = i 1335 1336 return PromptForChoice(max_choice, False) 1337 1338 1339def BooleanPrompt(prompt='Do you want to continue?', default=True, 1340 true_value='yes', false_value='no', prolog=None): 1341 """Helper function for processing boolean choice prompts. 1342 1343 Args: 1344 prompt: The question to present to the user. 1345 default: Boolean to return if the user just presses enter. 1346 true_value: The text to display that represents a True returned. 1347 false_value: The text to display that represents a False returned. 1348 prolog: The text to display before prompt. 1349 1350 Returns: 1351 True or False. 1352 """ 1353 true_value, false_value = true_value.lower(), false_value.lower() 1354 true_text, false_text = true_value, false_value 1355 if true_value == false_value: 1356 raise ValueError('true_value and false_value must differ: got %r' 1357 % true_value) 1358 1359 if default: 1360 true_text = true_text[0].upper() + true_text[1:] 1361 else: 1362 false_text = false_text[0].upper() + false_text[1:] 1363 1364 prompt = ('\n%s (%s/%s)? ' % (prompt, true_text, false_text)) 1365 1366 if prolog: 1367 prompt = ('\n%s\n%s' % (prolog, prompt)) 1368 1369 while True: 1370 try: 1371 response = GetInput(prompt).lower() 1372 except EOFError: 1373 # If the user hits CTRL+D, or stdin is disabled, use the default. 1374 print() 1375 response = None 1376 except KeyboardInterrupt: 1377 # If the user hits CTRL+C, just exit the process. 1378 print() 1379 Die('CTRL+C detected; exiting') 1380 1381 if not response: 1382 return default 1383 if true_value.startswith(response): 1384 if not false_value.startswith(response): 1385 return True 1386 # common prefix between the two... 1387 elif false_value.startswith(response): 1388 return False 1389 1390 1391def BooleanShellValue(sval, default, msg=None): 1392 """See if the string value is a value users typically consider as boolean 1393 1394 Often times people set shell variables to different values to mean "true" 1395 or "false". For example, they can do: 1396 export FOO=yes 1397 export BLAH=1 1398 export MOO=true 1399 Handle all that user ugliness here. 1400 1401 If the user picks an invalid value, you can use |msg| to display a non-fatal 1402 warning rather than raising an exception. 1403 1404 Args: 1405 sval: The string value we got from the user. 1406 default: If we can't figure out if the value is true or false, use this. 1407 msg: If |sval| is an unknown value, use |msg| to warn the user that we 1408 could not decode the input. Otherwise, raise ValueError(). 1409 1410 Returns: 1411 The interpreted boolean value of |sval|. 1412 1413 Raises: 1414 ValueError() if |sval| is an unknown value and |msg| is not set. 1415 """ 1416 if sval is None: 1417 return default 1418 1419 if isinstance(sval, six.string_types): 1420 s = sval.lower() 1421 if s in ('yes', 'y', '1', 'true'): 1422 return True 1423 elif s in ('no', 'n', '0', 'false'): 1424 return False 1425 1426 if msg is not None: 1427 logging.warning('%s: %r', msg, sval) 1428 return default 1429 else: 1430 raise ValueError('Could not decode as a boolean value: %r' % sval) 1431 1432 1433# Suppress whacked complaints about abstract class being unused. 1434class MasterPidContextManager(object): 1435 """Allow context managers to restrict their exit to within the same PID.""" 1436 1437 # In certain cases we actually want this ran outside 1438 # of the main pid- specifically in backup processes 1439 # doing cleanup. 1440 ALTERNATE_MASTER_PID = None 1441 1442 def __init__(self): 1443 self._invoking_pid = None 1444 1445 def __enter__(self): 1446 self._invoking_pid = os.getpid() 1447 return self._enter() 1448 1449 def __exit__(self, exc_type, exc, exc_tb): 1450 curpid = os.getpid() 1451 if curpid == self.ALTERNATE_MASTER_PID: 1452 self._invoking_pid = curpid 1453 if curpid == self._invoking_pid: 1454 return self._exit(exc_type, exc, exc_tb) 1455 1456 def _enter(self): 1457 raise NotImplementedError(self, '_enter') 1458 1459 def _exit(self, exc_type, exc, exc_tb): 1460 raise NotImplementedError(self, '_exit') 1461 1462 1463class ContextManagerStack(object): 1464 """Context manager that is designed to safely allow nesting and stacking. 1465 1466 Python2.7 directly supports a with syntax generally removing the need for 1467 this, although this form avoids indentation hell if there is a lot of context 1468 managers. It also permits more programmatic control and allowing conditional 1469 usage. 1470 1471 For Python2.6, see http://docs.python.org/library/contextlib.html; the short 1472 version is that there is a race in the available stdlib/language rules under 1473 2.6 when dealing w/ multiple context managers, thus this safe version was 1474 added. 1475 1476 For each context manager added to this instance, it will unwind them, 1477 invoking them as if it had been constructed as a set of manually nested 1478 with statements. 1479 """ 1480 1481 def __init__(self): 1482 self._stack = [] 1483 1484 def Add(self, functor, *args, **kwargs): 1485 """Add a context manager onto the stack. 1486 1487 Usage of this is essentially the following: 1488 >>> stack.add(Timeout, 60) 1489 1490 It must be done in this fashion, else there is a mild race that exists 1491 between context manager instantiation and initial __enter__. 1492 1493 Invoking it in the form specified eliminates that race. 1494 1495 Args: 1496 functor: A callable to instantiate a context manager. 1497 args and kwargs: positional and optional args to functor. 1498 1499 Returns: 1500 The newly created (and __enter__'d) context manager. 1501 Note: This is not the same value as the "with" statement -- that returns 1502 the value from the __enter__ function while this is the manager itself. 1503 """ 1504 obj = None 1505 try: 1506 obj = functor(*args, **kwargs) 1507 return obj 1508 finally: 1509 if obj is not None: 1510 obj.__enter__() 1511 self._stack.append(obj) 1512 1513 def __enter__(self): 1514 # Nothing to do in this case. The individual __enter__'s are done 1515 # when the context managers are added, which will likely be after 1516 # the __enter__ method of this stack is called. 1517 return self 1518 1519 def __exit__(self, exc_type, exc, exc_tb): 1520 # Exit each context manager in stack in reverse order, tracking the results 1521 # to know whether or not to suppress the exception raised (or to switch that 1522 # exception to a new one triggered by an individual handler's __exit__). 1523 for handler in reversed(self._stack): 1524 # pylint: disable=bare-except 1525 try: 1526 if handler.__exit__(exc_type, exc, exc_tb): 1527 exc_type = exc = exc_tb = None 1528 except: 1529 exc_type, exc, exc_tb = sys.exc_info() 1530 1531 self._stack = [] 1532 1533 # Return True if any exception was handled. 1534 if all(x is None for x in (exc_type, exc, exc_tb)): 1535 return True 1536 1537 # Raise any exception that is left over from exiting all context managers. 1538 # Normally a single context manager would return False to allow caller to 1539 # re-raise the exception itself, but here the exception might have been 1540 # raised during the exiting of one of the individual context managers. 1541 six.reraise(exc_type, exc, exc_tb) 1542 1543 1544def iflatten_instance(iterable, 1545 terminate_on_kls=(six.string_types, six.binary_type)): 1546 """Derivative of snakeoil.lists.iflatten_instance; flatten an object. 1547 1548 Given an object, flatten it into a single depth iterable- 1549 stopping descent on objects that either aren't iterable, or match 1550 isinstance(obj, terminate_on_kls). 1551 1552 Examples: 1553 >>> print list(iflatten_instance([1, 2, "as", ["4", 5])) 1554 [1, 2, "as", "4", 5] 1555 """ 1556 def descend_into(item): 1557 if isinstance(item, terminate_on_kls): 1558 return False 1559 try: 1560 iter(item) 1561 except TypeError: 1562 return False 1563 # Note strings can be infinitely descended through- thus this 1564 # recursion limiter. 1565 return not isinstance(item, six.string_types) or len(item) > 1 1566 1567 if not descend_into(iterable): 1568 yield iterable 1569 return 1570 for item in iterable: 1571 if not descend_into(item): 1572 yield item 1573 else: 1574 for subitem in iflatten_instance(item, terminate_on_kls): 1575 yield subitem 1576 1577 1578@contextlib.contextmanager 1579def Open(obj, mode='r'): 1580 """Convenience ctx that accepts a file path or an already open file object.""" 1581 if isinstance(obj, six.string_types): 1582 with open(obj, mode=mode) as f: 1583 yield f 1584 else: 1585 yield obj 1586 1587 1588def SafeRun(functors, combine_exceptions=False): 1589 """Executes a list of functors, continuing on exceptions. 1590 1591 Args: 1592 functors: An iterable of functors to call. 1593 combine_exceptions: If set, and multiple exceptions are encountered, 1594 SafeRun will raise a RuntimeError containing a list of all the exceptions. 1595 If only one exception is encountered, then the default behavior of 1596 re-raising the original exception with unmodified stack trace will be 1597 kept. 1598 1599 Raises: 1600 The first exception encountered, with corresponding backtrace, unless 1601 |combine_exceptions| is specified and there is more than one exception 1602 encountered, in which case a RuntimeError containing a list of all the 1603 exceptions that were encountered is raised. 1604 """ 1605 errors = [] 1606 1607 for f in functors: 1608 try: 1609 f() 1610 except Exception as e: 1611 # Append the exception object and the traceback. 1612 errors.append((e, sys.exc_info()[2])) 1613 1614 if errors: 1615 if len(errors) == 1 or not combine_exceptions: 1616 # To preserve the traceback. 1617 inst, tb = errors[0] 1618 six.reraise(type(inst), inst, tb) 1619 else: 1620 raise RuntimeError([e[0] for e in errors]) 1621 1622 1623def UserDateTimeFormat(timeval=None): 1624 """Format a date meant to be viewed by a user 1625 1626 The focus here is to have a format that is easily readable by humans, 1627 but still easy (and unambiguous) for a machine to parse. Hence, we 1628 use the RFC 2822 date format (with timezone name appended). 1629 1630 Args: 1631 timeval: Either a datetime object or a floating point time value as accepted 1632 by gmtime()/localtime(). If None, the current time is used. 1633 1634 Returns: 1635 A string format such as 'Wed, 20 Feb 2013 15:25:15 -0500 (EST)' 1636 """ 1637 if isinstance(timeval, datetime): 1638 timeval = time.mktime(timeval.timetuple()) 1639 return '%s (%s)' % (email.utils.formatdate(timeval=timeval, localtime=True), 1640 time.strftime('%Z', time.localtime(timeval))) 1641 1642 1643def ParseUserDateTimeFormat(time_string): 1644 """Parse a time string into a floating point time value. 1645 1646 This function is essentially the inverse of UserDateTimeFormat. 1647 1648 Args: 1649 time_string: A string datetime represetation in RFC 2822 format, such as 1650 'Wed, 20 Feb 2013 15:25:15 -0500 (EST)'. 1651 1652 Returns: 1653 Floating point Unix timestamp (seconds since epoch). 1654 """ 1655 return email.utils.mktime_tz(email.utils.parsedate_tz(time_string)) 1656 1657 1658def GetDefaultBoard(): 1659 """Gets the default board. 1660 1661 Returns: 1662 The default board (as a string), or None if either the default board 1663 file was missing or malformed. 1664 """ 1665 default_board_file_name = os.path.join(constants.SOURCE_ROOT, 'src', 1666 'scripts', '.default_board') 1667 try: 1668 with open(default_board_file_name) as default_board_file: 1669 default_board = default_board_file.read().strip() 1670 # Check for user typos like whitespace 1671 if not re.match('[a-zA-Z0-9-_]*$', default_board): 1672 logging.warning('Noticed invalid default board: |%s|. Ignoring this ' 1673 'default.', default_board) 1674 default_board = None 1675 except IOError: 1676 return None 1677 1678 return default_board 1679 1680 1681def SetDefaultBoard(board): 1682 """Set the default board. 1683 1684 Args: 1685 board (str): The name of the board to save as the default. 1686 1687 Returns: 1688 bool - True if successfully wrote default, False otherwise. 1689 """ 1690 config_path = os.path.join(constants.CROSUTILS_DIR, '.default_board') 1691 try: 1692 with open(config_path, 'w') as f: 1693 f.write(board) 1694 except IOError as e: 1695 logging.error('Unable to write default board: %s', e) 1696 return False 1697 1698 return True 1699 1700 1701def GetBoard(device_board, override_board=None, force=False, strict=False): 1702 """Gets the board name to use. 1703 1704 Ask user to confirm when |override_board| and |device_board| are 1705 both None. 1706 1707 Args: 1708 device_board: The board detected on the device. 1709 override_board: Overrides the board. 1710 force: Force using the default board if |device_board| is None. 1711 strict: If True, abort if no valid board can be found. 1712 1713 Returns: 1714 Returns the first non-None board in the following order: 1715 |override_board|, |device_board|, and GetDefaultBoard(). 1716 1717 Raises: 1718 DieSystemExit: If board is not set or user enters no. 1719 """ 1720 if override_board: 1721 return override_board 1722 1723 board = device_board or GetDefaultBoard() 1724 if not device_board: 1725 if not board and strict: 1726 Die('No board specified and no default board found.') 1727 msg = 'Cannot detect board name; using default board %s.' % board 1728 if not force and not BooleanPrompt(default=False, prolog=msg): 1729 Die('Exiting...') 1730 1731 logging.warning(msg) 1732 1733 return board 1734 1735 1736# Structure to hold the values produced by TimedSection. 1737# 1738# Attributes: 1739# start: The absolute start time as a datetime. 1740# finish: The absolute finish time as a datetime, or None if in progress. 1741# delta: The runtime as a timedelta, or None if in progress. 1742TimedResults = cros_collections.Collection( 1743 'TimedResults', start=None, finish=None, delta=None) 1744 1745 1746@contextlib.contextmanager 1747def TimedSection(): 1748 """Context manager to time how long a code block takes. 1749 1750 Examples: 1751 with cros_build_lib.TimedSection() as timer: 1752 DoWork() 1753 logging.info('DoWork took %s', timer.delta) 1754 1755 Context manager value will be a TimedResults instance. 1756 """ 1757 # Create our context manager value. 1758 times = TimedResults(start=datetime.now()) 1759 try: 1760 yield times 1761 finally: 1762 times.finish = datetime.now() 1763 times.delta = times.finish - times.start 1764 1765 1766def GetRandomString(): 1767 """Returns a random string. 1768 1769 It will be 32 characters long, although callers shouldn't rely on this. 1770 Only lowercase & numbers are used to avoid case-insensitive collisions. 1771 """ 1772 # Start with current time. This "scopes" the following random data. 1773 stamp = b'%x' % int(time.time()) 1774 # Add in some entropy. This reads more bytes than strictly necessary, but 1775 # it guarantees that we always have enough bytes below. 1776 data = os.urandom(16) 1777 # Then convert it to a lowercase base32 string of 32 characters. 1778 return base64.b32encode(stamp + data).decode('utf-8')[0:32].lower() 1779 1780 1781def MachineDetails(): 1782 """Returns a string to help identify the source of a job. 1783 1784 This is not meant for machines to parse; instead, we want content that is easy 1785 for humans to read when trying to figure out where "something" is coming from. 1786 For example, when a service has grabbed a lock in Google Storage, and we want 1787 to see what process actually triggered that (in case it is a test gone rogue), 1788 the content in here should help triage. 1789 1790 Note: none of the details included may be secret so they can be freely pasted 1791 into bug reports/chats/logs/etc... 1792 1793 Note: this content should not be large 1794 1795 Returns: 1796 A string with content that helps identify this system/process/etc... 1797 """ 1798 return '\n'.join(( 1799 'PROG=%s' % inspect.stack()[-1][1], 1800 'USER=%s' % getpass.getuser(), 1801 'HOSTNAME=%s' % GetHostName(fully_qualified=True), 1802 'PID=%s' % os.getpid(), 1803 'TIMESTAMP=%s' % UserDateTimeFormat(), 1804 'RANDOM_JUNK=%s' % GetRandomString(), 1805 )) + '\n' 1806 1807 1808def UnbufferedTemporaryFile(**kwargs): 1809 """Handle buffering changes in tempfile.TemporaryFile.""" 1810 assert 'bufsize' not in kwargs 1811 assert 'buffering' not in kwargs 1812 if sys.version_info.major < 3: 1813 kwargs['bufsize'] = 0 1814 else: 1815 kwargs['buffering'] = 0 1816 return tempfile.TemporaryFile(**kwargs) 1817 1818 1819def UnbufferedNamedTemporaryFile(**kwargs): 1820 """Handle buffering changes in tempfile.NamedTemporaryFile.""" 1821 assert 'bufsize' not in kwargs 1822 assert 'buffering' not in kwargs 1823 if sys.version_info.major < 3: 1824 kwargs['bufsize'] = 0 1825 else: 1826 kwargs['buffering'] = 0 1827 return tempfile.NamedTemporaryFile(**kwargs) 1828