xref: /aosp_15_r20/external/autotest/utils/frozen_chromite/lib/cros_build_lib.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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